Mage Xy Mage Xy - 3 months ago 4
C# Question

Why does my Dependency Property send null to my view model?

I'm running onto a problem that I'm having some trouble diagnosing.

I have created a custom DataGrid object (as detailed in Sandesh's answer here) in order to be able to bind to the various selected items of the data grid. However, even though it looks like the DependecyProperty itself is getting the right value when the selection changes, the view model never seems to receive the updated value (it gets null instead).

Custom DataGrid class:

class CustomDataGrid : DataGrid
{
public CustomDataGrid()
{
this.SelectionChanged += CustomDataGrid_SelectionChanged;
}

void CustomDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedItemsList = this.SelectedItems;
}
#region SelectedItemsList

// In the setter, the value is a valid List with the correct elements.
// For some reason it doesn't seem to be passed on to the view model.
public IList SelectedItemsList
{
get { return (IList)GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}

public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register("SelectedItemsList", typeof(IList), typeof(CustomDataGrid), new PropertyMetadata(OnSelectedItemsListChanged));

private static void OnSelectedItemsListChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Test");
}

#endregion
}


XAML file:

<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:TestViewModel />
</Window.DataContext>
<Grid>
<local:CustomDataGrid AutoGenerateColumns="True"
SelectionMode="Extended"
SelectionUnit="FullRow"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False"
ItemsSource="{Binding TestModels}"
SelectedItemsList="{Binding Path=SelectedTestModels, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Window>


View Model:

class TestViewModel : INotifyPropertyChanged
{
public TestViewModel()
{
TestModels = new List<TestModel>
{
new TestModel { Name = "Test 1", ID = 1 },
new TestModel { Name = "Test 2", ID = 2 },
new TestModel { Name = "Test 3", ID = 3 },
};
}

private IEnumerable<TestModel> _testModels;

public IEnumerable<TestModel> TestModels
{
get { return _testModels; }
set
{
_testModels = value;
OnPropertyChanged();
}
}

private IEnumerable<TestModel> _selectedTestModels;

public IEnumerable<TestModel> SelectedTestModels
{
get { return _selectedTestModels; }
set
{
// Right here I would expect value to have a non-null value... but it's always null
_selectedTestModels = value;
OnPropertyChanged();
}
}

public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var propChanged = PropertyChanged;
if (propChanged != null)
propChanged(this, new PropertyChangedEventArgs(propertyName));
}
}


The TestModel class (very simple POCO):

class TestModel
{
public string Name { get; set; }
public int ID { get; set; }
}

Answer

You're using the non-generic IList and IList interchangeably. You have a couple of options.

In your viewmodel you can change your property and field to IList.

private IList _selectedTestModels;

public IList SelectedTestModels
{
    get { return _selectedTestModels; }
    set
    {
        _selectedTestModels = value;
        OnPropertyChanged();
    }
}

Alternatively in your CustomControl.

public static readonly DependencyProperty SelectedItemsListProperty = DependencyProperty.Register("SelectedItemsList", typeof(IList<TestModel>), typeof(CustomDataGrid));

public IList<TestModel> SelectedItemsList
{
    get { return (IList<TestModel>)GetValue(SelectedItemsListProperty); }
    set { SetValue(SelectedItemsListProperty, value); }
}

void CustomDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    SetCurrentValue(SelectedItemsListProperty, SelectedItems.OfType<TestModel>().ToList());
}

As a side note, I also believe this bit of code may break your binding.

void CustomDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    this.SelectedItemsList = this.SelectedItems;
}

Try using SetCurrentValue to maintain your binding.

void CustomDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    SetCurrentValue(SelectedItemsListProperty, SelectedItems);
}
Comments