Using BindingOperations.EnableCollectionSynchroniz

2019-01-24 13:21发布

问题:

I have two WPF applications "UI", "Debugger" and one ClassLibrary "BL". UI references to Debugger and BL. Debugger references to BL. I have collection in BL called MyCollection. UI app starts the Debugger app and Debugger binds to a collection MyCollection in BL. When I try changing the MyCollection collection from UI app I am getting exception.

A first chance exception of type 'System.NotSupportedException' occurred in PresentationFramework.dll

Additional information: This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

I was googling around and found this: BindingOperations.EnableCollectionSynchronization I can't figure out how to use it. I don't want to reference to any UI dlls from my BL project. Can anybody assist me on that?

Thanks for the help!

回答1:

All the examples I've seen on Stack Overflow for this get it wrong. You must lock the collection when modifying it from another thread.

On dispatcher (UI) thread:

_itemsLock = new object();
Items = new ObservableCollection<Item>();
BindingOperations.EnableCollectionSynchronization(Items, _itemsLock);

Then from another thread:

lock (_itemsLock)
{
    // Once locked, you can manipulate the collection safely from another thread
    Items.Add(new Item());
    Items.RemoveAt(0);
}

More information in this article: http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux



回答2:

I am not sure if this will help but still you can give it a try.

Add a Property in Debugger which will hold the Collection from BL like

private ObservableCollection<string> _data = new ObservableCollection<string>();
private object _lock = new object();

public ObservableCollection<string> Data { get {return _data;} }

In the constructor just add the below line

BindingOperations.EnableCollectionSynchronization(_data, _lock);

this will above line will take care of thread safety.

Below is the example

ViewModel (Debugger)

internal class ViewModelClass : INotifyPropertyChanged
{
    private object _lock = new object ();
    private ObservableCollection<string> _data;

    public ObservableCollection<string> Data
    {
        get { return _data; }
        private set
        {
            _data = value;
            RaisePropertyChanged ("Data");
        }
    }

    private string _enteredText;
    public string EnteredText
    {
        get { return _enteredText; }
        set
        {
            _enteredText = value;
            _data.Add (value); RaisePropertyChanged ("EnteredText");
        }
    }

    private void RaisePropertyChanged (string name)
    {
        var pc = PropertyChanged;
        if (pc != null)
            pc (this, new PropertyChangedEventArgs (name));
    }

    public ViewModelClass ()
    {
        var _model = new ModelClass ();
        Data = _model.Data;
        _data.CollectionChanged += (s, e) => RaisePropertyChanged ("Data");
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Model(BL)

internal class ModelClass
{
    private ObservableCollection<string> _data;

    public ObservableCollection<string> Data
    {
        get { return _data; }
        private set { _data = value; }
    }

    public ModelClass ()
    {
        _data = new ObservableCollection<string> { "Test1", "Test2", "Test3" };
    }
}

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow ()
    {
        InitializeComponent ();
        this.DataContext = new ViewModelClass ();
    }
}

MainWindow.xaml

<Window x:Class="CollectionSynchronizationTest.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow"
            Height="350"
            Width="525">
<StackPanel>
    <ComboBox IsEditable="True"
                        ItemsSource="{Binding Data}"
                        Text="{Binding EnteredText, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
    <Button Content="Test" />
</StackPanel>

When the window loads just enter "SomeValue" in the ComboBox and then after pressing the Tab key you should find the new value in the ComboBox dropdown



回答3:

In this blog you find an easy tutorial how to work with BindingOperations...it is quite easy.



回答4:

I could not figure out how to use it, either, when I had the same problem.

I ended with my own collection type where I store the dispatcher and use it when necessary. Note that my naming was very poor, this collection is not threadsafe, far from it.

public class ThreadableObservableCollection<T> : ObservableCollection<T>
{
    private readonly Dispatcher _dispatcher;
    public ThreadableObservableCollection()
    {
      _dispatcher = Dispatcher.CurrentDispatcher;
    }

    public void ThreadsafeRemove(T item, Action callback)
    {
      if (_dispatcher.CheckAccess())
      {
        Remove(item);
        callback();
      }
      else
      {
        _dispatcher.Invoke(() =>
          {
            Remove(item);
            callback();
          });
      }
    }

    public void ThreadsafeInsert(int pos, T item, Action callback)
    {
      if (_dispatcher.CheckAccess())
      {
        Insert(pos, item);
        callback();
      }
      else
      {
        _dispatcher.Invoke(() =>
          {
            Insert(pos, item);
            callback();
          });
      }
    }
  }