Search code examples
c#wpfdata-bindingwpf-controls

why my SelectedItems dependency property always returns null to bound property


I created a UserControl1 that wraps a DataGrid (this is simplified for test purposes, the real scenario involves a third-party control but the issue is the same). The UserControl1 is used in the MainWindow of the test app like so:

<test:UserControl1 ItemsSource="{Binding People,Mode=OneWay,ElementName=Self}"
                             SelectedItems="{Binding SelectedPeople, Mode=TwoWay, ElementName=Self}"/>

Everything works as expected except that when a row is selected in the DataGrid, the SelectedPeople property is always set to null.

The row selection flow is roughly: UserControl1.DataGrid -> UserControl1.DataGrid_OnSelectionChanged -> UserControl1.SelectedItems -> MainWindow.SelectedPeople

Debugging shows the IList with the selected item from the DataGrid is being passed to the SetValue call of the SelectedItems dependency property. But when the SelectedPeople setter is subsequently called (as part of the binding process) the value passed to it is always null.

Here's the relevant UserControl1 XAML:

<Grid>
    <DataGrid x:Name="dataGrid" SelectionChanged="DataGrid_OnSelectionChanged" />
</Grid>

In the code-behind of UserControl1 are the following definitions for the SelectedItems dependency properties and the DataGrid SelectionChanged handler:

    public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register("SelectedItems", typeof(IList), typeof(UserControl1), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemsChanged));
    public IList SelectedItems
    {
        get { return (IList)GetValue(SelectedItemsProperty); }

        set
        {
            SetValue(SelectedItemsProperty, value);
        }
    }

    private bool _isUpdatingSelectedItems;

    private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var ctrl = d as UserControl1;

        if ((ctrl != null) && !ctrl._isUpdatingSelectedItems)
        {
            ctrl._isUpdatingSelectedItems = true;

            try
            {
                ctrl.dataGrid.SelectedItems.Clear();
                var selectedItems = e.NewValue as IList;

                if (selectedItems != null)
                {
                    var validSelectedItems = selectedItems.Cast<object>().Where(item => ctrl.ItemsSource.Contains(item) && !ctrl.dataGrid.SelectedItems.Contains(item)).ToList();
                    validSelectedItems.ForEach(item => ctrl.dataGrid.SelectedItems.Add(item));
                }
            }
            finally
            {
                ctrl._isUpdatingSelectedItems = false;
            }
        }
    }

    private void DataGrid_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (!_isUpdatingSelectedItems && sender is DataGrid)
        {
            _isUpdatingSelectedItems = true;

            try
            {
                var x = dataGrid.SelectedItems;
                SelectedItems = new List<object>(x.Cast<object>());
            }
            finally
            {
                _isUpdatingSelectedItems = false;
            }
        }
    }

Here is definition of SomePeople from MainWindow code-behind:

    private ObservableCollection<Person> _selectedPeople;
    public ObservableCollection<Person> SelectedPeople
    {
        get { return _selectedPeople; }
        set { SetProperty(ref _selectedPeople, value); }
    }    

    public class Person
    {
        public Person(string first, string last)
        {
            First = first;
            Last = last;
        }

        public string First { get; set; }
        public string Last { get; set; }
    }

Solution

  • I faced the same problem, i dont know reason, but i resolved it like this:

    1) DP

    public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register("SelectedItems", typeof(object), typeof(UserControl1),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemsChanged));
    
        public object SelectedItems
        {
            get { return (object) GetValue(SelectedItemsProperty); }
            set { SetValue(SelectedItemsProperty, value); }
        }
    

    2) Grid event

    private void DataGrid_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var SelectedItemsCasted = SelectedItems as IList<object>;
            if (SelectedItemsCasted == null)
                return;
    
            foreach (object addedItem in e.AddedItems)
            {
                SelectedItemsCasted.Add(addedItem);
            }
    
            foreach (object removedItem in e.RemovedItems)
            {
                SelectedItemsCasted.Remove(removedItem);
            }
        }
    

    3) In UC which contain UserControl1

    Property:

    public IList<object> SelectedPeople { get; set; }
    

    Constructor:

        public MainViewModel()
        {
            SelectedPeople = new List<object>();
        }