Search code examples
c#.netwpfeventsdata-binding

Update controller from OnCollectionChanged method in a static dependency property class


I'am trying to create a dependency property that allows me to two way bind a datagrid it's selected items to an property in my viewmodel.

So far i got it to work one way, when the selected item is changed in the datagrid my collection is updated, but thats where i ran into a snag

public static class MultiSelectorHelper
{
    public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached(
        "SelectedItems",
        typeof(IList),
        typeof(MultiSelectorHelper),
        new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemsChanged));

    public static IList GetSelectedItems(DependencyObject element)
    {
        return (IList)element.GetValue(SelectedItemsProperty);
    }

    public static void SetSelectedItems(DependencyObject element, IList value)
    {
        element.SetValue(SelectedItemsProperty, value);
    }

    private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var selector = (Selector)d;
        var newList = e.NewValue as IList;
        var obs = newList as INotifyCollectionChanged;
        obs.CollectionChanged += OnCollectionChanged;
        selector.SelectionChanged += OnSelectionChanged;
    }

    private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var dependency = (DependencyObject)sender;
        var items = GetSelectedItems(dependency);
        // items.Add/items.Remove
    }

    private static void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        // Update current datagrid selected items .add/.remove
    }

When the collection changes it throws me into the OnCollectionChanged method, but from there i got no way to update my datagrid.

I have tried a couple of things, the first thing i tried was to define the event directly in the OnSelectedItemsChanged by means of obs.CollectionChanged += (s, e2) => {all the code} which altho works due to the access of d it resulted in triggering when the datagrid made a change to the collection with no way to disable it.

Another method i tried was to add an extra parameter to my OnCollectionChanged by means of obs.CollectionChanged += (s, e2) => OnCollectionChanged(s, e2, d); But this also did not get me any further and basicly resulted in the same issues as the one above.

The last thing i tried/could find/could come up with was to use a PropertyChanged event in my OnCollectionChanged and fire it off for each item that had changed, this in the hopes to be able to trigger the UI to send out an event i could hook into and update from there (since it would contain a dependency object of the datagrid). This never worked, no events never triggered anything leaving me at a dead trail.

The question is as follows, How would i be able to set the selected items of a datagrid (or any MultiSelector/Listbox) when a change is made from for example a viewModel, it's not a simple 1 on 1 binding so it does require having an instance of the controller to change it.

For those wondering, the idea is that it doesnt work for just a datagrid, but an object that is a ListBox or of type MultiSelector, for that reason i'm not just extending but creating a completely separate dependency property.


Solution

  • You could use a local function that captures the selector:

    private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var selector = (Selector)d;
        var obs = e.NewValue as INotifyCollectionChanged;
        if (obs != null)
        {
            void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                var dataGrid = selector;
                //...
            }
    
            obs.CollectionChanged += CollectionChanged;
        }
    }
    

    The local function feature was introduced in C# 7.0. In earlier versions, you could use an anonymous method:

    NotifyCollectionChangedEventHandler handler = (sender, ee) =>
    {
        var dataGrid = selector;
        //..
    };
    
    obs.CollectionChanged += handler;