I have a DataGrid
inside of a UserControl
which in turn lies inside of another UserControl
. This is due to other needs of the project and I can't change this nested architecture. I'm binding a list of Person
class to this DataGrid
. This is a dumbed-down version without using a VM
, but in my real project I am using a VM
.
UserControl
with the DataGrid
:<Grid>
<DataGrid x:Name="MyDg"
ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=local:UCDataGrid}, UpdateSourceTrigger=PropertyChanged}"
MouseDoubleClick="MyDg_MouseDoubleClick"
SelectedValue="{Binding SelectedValue, RelativeSource={RelativeSource AncestorType=local:UCDataGrid}, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
Code Behind:
public partial class UCDataGrid : UserControl
{
public event RoutedEventHandler RoutedDataGridDoubleClick;
public UCDataGrid()
{
InitializeComponent();
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(object), typeof(UCDataGrid), new PropertyMetadata(null));
public object ItemsSource
{
get { return GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty SelectedValueProperty = DependencyProperty.Register("SelectedValue", typeof(object), typeof(UCDataGrid), new PropertyMetadata(null));
public object SelectedValue
{
get { return GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
private void MyDg_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
RoutedDataGridDoubleClick?.Invoke(this, new RoutedEventArgs());
}
}
UserControl
that contains the above control:<Grid>
<ContentControl Content="{Binding MyDataGrid, ElementName=ucDisplay}"/>
</Grid>
ucDisplay
is simply the Name
property value of this UserControl
.
Code Behind:
Nothing fancy here.
public partial class UCDisplay : UserControl
{
public UCDisplay()
{
InitializeComponent();
}
public static readonly DependencyProperty MyDataGridProperty = DependencyProperty.Register("MyDataGrid", typeof(object), typeof(UCDisplay), new PropertyMetadata(null));
public object MyDataGrid
{
get { return GetValue(MyDataGridProperty); }
set { SetValue(MyDataGridProperty, value); }
}
}
In my Main Window, I bind my People
list as well as SelectedPerson
instance, like so:
<Grid>
<local:UCDisplay>
<local:UCDisplay.MyDataGrid>
<local:UCDataGrid ItemsSource="{Binding People}"
SelectedValue="{Binding SelectedPerson, UpdateSourceTrigger=PropertyChanged}"
RoutedDataGridDoubleClick="UCDataGrid_RoutedDataGridDoubleClick"/>
</local:UCDisplay.MyDataGrid>
</local:UCDisplay>
</Grid>
Code Behind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
private List<Person> people;
public List<Person> People
{
get => people;
set => SetField(ref people, value);
}
private Person selectedPerson;
public Person SelectedPerson
{
get => selectedPerson;
set => SetField(ref selectedPerson, value);
}
public MainWindow()
{
InitializeComponent();
People = GetPeople();
DataContext = this;
}
private void UCDataGrid_RoutedDataGridDoubleClick(object sender, RoutedEventArgs e)
{
}
private List<Person> GetPeople()
{
return new List<Person>
{
new Person() { Name = "A" },
new Person() { Name = "B" },
new Person() { Name = "C" }
};
}
public class Person
{
public string Name { get; set; }
}
}
Again, in reality I'm using a VM
, this is only to keep things simple.
Now when I run this I can display my list content just fine. But when I double-click an item in my DataGrid
, in the corresponding in my Main Window code behind, the SelectedPerson
remains null
, although its binding is identical to the People
list. I confirm this by using a break point in the main code behind:
But if I debug and see the value in the code behind of my innermost UserControl
, you see that the SelectedValue
there has the correct selected items value.
So what am I doing wrong here? Why can't I seem to bind the SelectedValue
although I do it exactly the same as my ItemsSource
binding, but the latter works?
SelectedValue
is supposed to be used in conjunction with SelectedValuePath
. You should use SelectedItem
instead.
Besides that, you are missing a TwoWay
Binding. Either explicitly declare the SelectedItem
Binding TwoWay
<DataGrid x:Name="MyDg"
ItemsSource="{Binding ItemsSource,
RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedItem="{Binding SelectedItem,
RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay}"/>
or register the property to bind TwoWay by default:
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(
nameof(SelectedItem), typeof(object), typeof(UCDataGrid),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
Also note that setting UpdateSourceTrigger=PropertyChanged
is pointless in all your Bindings.