Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TwoWay Collection Binding Sync/Lock

What is the best way to synchronize 2 sets of data via Binding?

Target = Custom Setters - raises custom events whenever something changed
Source = ObservableCollection - raises events whenever collection changed

Now my question is, when I receive an update from one collection (e.g. Source.CollectionChanged event) I need to call the custom TargetSetters, and ignore the events called which originated from my update.

And also the other way, when the Target custom events get fired, i need to update the source, but ignore the CollectionChanged event.

At the moment, I am keeping a reference to my handlers, and removing that before updating any of the collections. e.g.

private void ObservableCollection_OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
    CustomObject.SelectionChanged -= CustomObject_SelectionChanged;
    // Do change logic and update Custom Object....
    CustomObject.SelectionChanged += CustomObject_SelectionChanged;
}

void CustomObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    ObservableCollection.CollectionChanged -= ObservableCollection_OnCollectionChanged;
    // Do change logic and update ObservableCollection...
    ObservableCollection.CollectionChanged += ObservableCollection_OnCollectionChanged;
}

I have seen that you can use an if statement to check if the updates are from source, and if they are ignore them. e.g.

private void ObservableCollection_OnCollectionChanged2(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
    if (BindingTargetUpdating) return;
    BindingSourceUpdating = true;
    // Do change logic and update Custom Object....
    BindingSourceUpdating = false;
}

void CustomObject_SelectionChanged2(object sender, SelectionChangedEventArgs e)
{
    if (BindingSourceUpdating) return;
    BindingTargetUpdating = true;
    // Do change logic and update ObservableCollection...
    BindingTargetUpdating = false;
}

After Google + SO Search came back with nothing, I wanted to see how other people are doing this, and is there something really simple I am missing here that solves this problem? (I know that the examples are not thread-safe)

If not, what is the preferred way? Removing and attaching handlers, or setting a boolean flag? What is more performant (yes i know this is highly unlikely to cause a bottleneck but out of curiosity)

Reason I am asking is because, currently I am implementing Attached Behaviours and for each behaviour, I am creating 2 sets of Dictionaries which hold the references to the handlers for each object as state has to be passed around.

I can't seem to find the the source code for the binding mechanism of the .NET Binding classes, to see how MS implemented it. If anyone has a link to those it would be greatly appreciated.

like image 319
Michal Ciechan Avatar asked Oct 20 '22 23:10

Michal Ciechan


1 Answers

The mechanism you're using - having a boolean which tracks when updates occur, and blocking around it, is the most common approach.

Personally, I prefer to wrap that logic into a small utility that implements IDisposable. This makes it easier to guarantee that you'll always clean up after yourself.

A utility you can use for this would look something like:

class Guard : IDisposable
{
    readonly Func<bool> getter;
    readonly Action<bool> setter;

    readonly bool acquired = false;
    public Guard(Func<bool> getter, Action<bool> setter)
    {
        this.getter = getter;
        this.setter = setter;

        if (this.getter() == false)
        {
            this.setter(true);
            this.acquired = true;
        }
    }

    public bool Acquired { get { return this.acquired; } }

    void IDisposable.Dispose()
    {
        if (acquired)
        {
            this.setter(false);
        }
    }
}

You can then write:

private void ObservableCollection_OnCollectionChanged2(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
    using(var guard = new Guard(() => BindingTargetUpdating, v => BindingTargetUpdating = value))
    {
       if (guard.Acquired)
       {
           // Do change logic and update Custom Object....
       }
    }
}

This isn't necessarily any shorter - its probably longer to write, but does provide guarantees that you'll release your blocks if exceptions occur. You can always subclass Guard to shrink the usage down if you'll be using it frequently.

like image 118
Reed Copsey Avatar answered Oct 22 '22 12:10

Reed Copsey