Using BindingOperations.EnableCollectionSynchronization - c#

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!

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

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

A WPF application can display a collection of data using an ItemsControl or one of its subclasses (ListBox, DataGrid, TreeView, ListView, etc.). WPF channels all its access to the collection through a subclass of CollectionView. Both the ItemsControl and the CollectionView have affinity to the thread on which the ItemsControl was created, meaning that using them on a different thread is forbidden and throws an exception. In effect, this restriction applies to the collection as well. You may want to use the collection on multiple threads. For example, you want to update the collection (add or remove items) on a "data-gathering" thread, while displaying the results on a "user interface" thread, so that the UI remains responsive while data-gathering is happening. In such a situation, you are responsible for ensuring synchronized ("thread-safe") access to the collection. This is typically done using either a simple lock mechanism or a more elaborate synchronization mechanism such as semaphores, reset events, etc. While you must synchronize your application's access to the collection, you must also guarantee that access from WPF (specifically from CollectionView) participates in the same synchronization mechanism. You do this by calling the EnableCollectionSynchronization method.
The DOC remark this very nice, I think you should have a look:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.data.bindingoperations.enablecollectionsynchronization?view=netcore-3.1

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

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();
});
}
}
}

Related

Observable Collection multithreading

I have an application where items being added to collections from multiple threads.
Randomly i get an
This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread. at System.Windows.Data.CollectionView.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
Now the collections are created in a class , the classes themselves are created on multiple threads.
Here is a class example
public class Example
{
public Example()
{
BindingOperations.EnableCollectionSynchronization(collection, COLLECTION_LOCK);
var defaultView = CollectionViewSource.GetDefaultView(collection);
defaultView.SortDescriptions.Add(new SortDescription("SomeProperty", ListSortDirection.Ascending));
if (defaultView is ICollectionViewLiveShaping liveShaping)
liveShaping.IsLiveSorting = true;
}
private readonly object COLLECTION_LOCK = new object();
private readonly ObservableCollection<object> collection = new ObservableCollection<object>();
public ObservableCollection<object> Collection
{
get
{
return collection;
}
}
private void AddItem(object item)
{
lock(COLLECTION_LOCK)
{
if(!Collection.Contains(item))
{
Collection.Add(item);
}
}
}
private void RemoveItem(object item)
{
lock (COLLECTION_LOCK)
{
if (Collection.Contains(item))
{
Collection.Remove(item);
}
}
}
}
I am using the BindingOperations.EnableCollectionSynchronization to allow cross thread operations and always use the specified lock to modify collection.
Still the error comes up randomly.
I have also tried to use BindingOperations.AccessCollection when accessing the collection but the error still happens randomly.
The MS documentation states that ObservableCollection must be created on a UI thread? Can someone confirm that its the case?
Also you can notice that i get the default collection view CollectionViewSource.GetDefaultView(collection)
The collection view is also created on same thread and technically as i understand its the source of the problem.
I have tried to simulate adding from different threads by creating thousands of tasks and modifying the collection with no error happening BUT again randomly error pops up out of nowhere, i tested with both where collection was not bound and bound to UI.
Any ideas?
Stack trace
System.NotSupportedException: This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
at System.Windows.Data.CollectionView.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
at System.Collections.ObjectModel.ObservableCollection`1.RemoveItem(Int32 index)
at System.Collections.ObjectModel.Collection`1.Remove(T item)
at Manager.ViewModels.HostViewModelBase.RemoveUser(IUserMemberViewModel user)
The collection view flags are
System.Windows.Data.CollectionView.CollectionViewFlags.ShouldProcessCollectionChanged | System.Windows.Data.CollectionView.CollectionViewFlags.IsCurrentBeforeFirst | System.Windows.Data.CollectionView.CollectionViewFlags.IsCurrentAfterLast | System.Windows.Data.CollectionView.CollectionViewFlags.IsDynamic | System.Windows.Data.CollectionView.CollectionViewFlags.AllowsCrossThreadChanges | System.Windows.Data.CollectionView.CollectionViewFlags.CachedIsEmpty
and AllowsCrossThreadChanges is true
One of the best ways to deal with that is to ditch ObservableCollection alltoghether. Its use case is very narrow and it's hard to work around the Dispatcher issue.
Go with DynamicData instead - once you get the hang of it, it becomes very powerfull and so natural to use:
ReadOnlyObservableCollection<TradeProxy> data;
var source = new SourceCollection<YourClass>();
source.Connect()
.Sort(SortExpressionComparer<YourClass>.Descending(t => t.SomeProperty))
.ObserveOnDispatcher() //ensure operation is on the UI thread
.Bind(out data) //Populate the observable collection
.Subscribe();
// you can do that in ANY THREAD you want and the view will update without any problems:
source.Add(yourClasse);
DynamicData also has filtering with very easy reaplying of the filter, paging, grouping,.... so many things. It is based on Rx, so on top of that you can easily throtle the change when working with big sets, and then make it all instant in UnitTests.
How about implementing a thread safe wrapper of the ObservableCollection?
public class ObservableCollectionWrapper<T> : ICollection<T>, INotifyCollectionChanged
{
private readonly ObservableCollection<T> _collection;
private readonly Dispatcher _dispatcher;
public event NotifyCollectionChangedEventHandler CollectionChanged;
public ObservableCollectionWrapper(ObservableCollection<T> collection, Dispatcher dispatcher)
{
_collection = collection;
_dispatcher = dispatcher;
collection.CollectionChanged += Internal_CollectionChanged;
}
private void Internal_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
_dispatcher.Invoke(() =>
{
this.CollectionChanged?.Invoke(sender, e);
});
}
public int Count => _collection.Count;
/* Implement the rest of the ICollection<T> interface */
}
Usage example:
var collectionWrapper = new ObservableCollectionWrapper<object>(collection, this.Dispatcher);
var defaultView = CollectionViewSource.GetDefaultView(collectionWrapper);

Why does the CollectionChanged event for ObserverableCollection not always work when the collection is used as a Property?

More about the question
First, let me clear the air... I'm somewhat new to C#. I bumped into this issue in an application I was working on. This particular class only had ObservableCollections exposed as Properties so my initial thought was that ObservervableCollection has it's own event, so I don't need the PropertyChanged event. The first attempt worked beautifully. Then I started cleaning up my code and found I didn't really need one of the backing vars, so I moved it all into the method...and it quit updating UI. Adding the INotifyPropertyChanged fixed the issue but I was left with a very big "WHY?"
Here is some test code I put together to see what I could figure out. Be advised, it is littered with bad practice, but I am really only trying to figure out when CollectionChanged can be depended on and when PropertyChagned must be added. Should I ever depend on CollectionChanged over PropertyChanged? If not, should we be using another List type, as we wouldn't really need the overhead of the ObservableCollection. It really seems like a waste to use both.
The bulk of the code
private TestClass test = new TestClass();
public MainWindow()
{
InitializeComponent();
this.DataContext = test;
}
internal class TestClass : INotifyPropertyChanged
{
public ObservableCollection<String> test1 = new ObservableCollection<String>();
public ObservableCollection<String> test2 = new ObservableCollection<String>() { "T2-A" };
public TestClass()
{
}
public ObservableCollection<String> Test1 { get => test1; set { } }
public ObservableCollection<String> Test2 { get => test2; set { this.test2 = value; } }
public ObservableCollection<String> Test3 { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void BT1_Click(object sender, RoutedEventArgs e)
{
//Both of these work fine
test.test1.Add("T1-A");
test.Test1.Add("T1-B");
//First line works...Second line breaks it... but there is a setter.
test.Test2.Add("T2-C");
test.Test2 = new ObservableCollection<String>() { "T2-A", "T2-B" };
test.test2.Add("T2-D");
//First Line throws Null exception... So it's not creating a backing var of it's own.
//test.Test3.Add("T3-A");
var t3 = new ObservableCollection<String>() { "T3-B", "T3-C" };
test.Test3 = t3;
test.Test3.Add("T3-D");
//It updates when I trigger the property changed...but what broke CollectionChanged
test.OnPropertyChanged(nameof(test.Test3));
}
Here is the XAML if anyone is interested
<ListBox ItemsSource="{Binding Test1}" HorizontalAlignment="Left" Height="205" VerticalAlignment="Top" Width="161" Margin="10,5,0,0" />
<ListBox ItemsSource="{Binding Test2}" Height="205" Margin="186,5,187,0" VerticalAlignment="Top" />
<ListBox ItemsSource="{Binding Test3}" Height="205" Margin="366,5,8,0" VerticalAlignment="Top" />
<Button x:Name="bT1" Content="Test It" HorizontalAlignment="Left" Height="33" Margin="10,215,0,0" VerticalAlignment="Top" Width="516" Click="BT1_Click" />
My Findings so far
It seems that if you want to use the ObservableCollection CollectionChanged event, you must create your backing var, and never alter the instance of Collection. In otherwords, use Clear() to wipe and rebuild rather than 'var col = new ObservableCollection()'. Is there something I am missing? I would think this would get rather flaky when you start looking at TwoWay data. How would you prevent someone from breaking your code downstream following the 2nd line of test2?
//First line works...Second line breaks it... but there is a setter.
test.Test2.Add("T2-C");
test.Test2 = new ObservableCollection<String>() { "T2-A", "T2-B" };
Well, lets look at the setter
public ObservableCollection<String> Test2 { get => test2; set { this.test2 = value; } }
That does set the value to the new collection, but what about the binding?
In order for the binding to know of the new object you need to raise the NotifyPropertyChanged event. So after you set that value currently the binding will still be pointing to the old collection (which still is listening for collection changes but on the previous value). Adding a notify property changed call to the setter will allow the binding to be updated:
private ObservableCollection<String> test2;
public ObservableCollection<String> Test2
{
get
{
return test2;
}
set
{
test2 = value;
OnPropertyChanged("Test2");
}
}
Add this example to each property and your bindings will update when you update the entire collection to a new object. i.e. property = new ObservableCollection... Then the ListBox will be looking for collection changes on the new object.
In response to your last paragraph, there is another way to go about it:
You can keep one instance of the collection and never change it. i.e.:
public ObservableCollection<String> Test2 { get; set; }
Then in the constructor of the class initialize your collection. (or initialize it in-line, either works, the key is to only initialize it one time)
Test2 = new ObservableCollection<String>();
Then, when you want to create a new list do not overwrite the object. Instead just clear the list and add the new values in:
public void UpdateCollection(List<String> newValues)
{
Test2.Clear(); //notifies the list box with the CollectionChanged event
foreach(var value in newValues)
{
Test2.Add(value); //notifies the list box with the new item in the collection
}
}

How to call method in window (.xaml.cs) from viewmodel (.cs) without introducing new references in wpf

I'm looking for a simple way to call a method in my Main Window, but I want to call it from my View Model. Basically, I'm looking for some king of "this.parent" sort of thing to put in the View Model to reference the Main Window.
Or, if you want to check out the reason I want to do this and tell me another way to go about my problem:
I'm working with an app that constantly gets information fed to it. In the viewmodel, the information is processed. I want to make a notification every time a piece of information comes in that satisfies some qualification.
Initially, I had a dictionary in the viewmodel that stored info about that information, and I accessed that dictionary in the MainWindow so that I could make the window flash and send other notifications. But I was getting issues with the viewmodel's dictionary being continuously changed while I was accessing it in the MainWindow.
Sorry if this question sounds stupid. I just started with WPF two months ago, and didn't have a great background in programming even before that, either.
VM should "know" nothing of your View or Window, the way VM typically "communicates" with V in WPF/MVVM is by rasing events. That way VM remains ignorant of/decoupled from the V and since VM is already DataContext of V it's not hard to subscribe to VM's event.
Example:
VM:
public event EventHandler<NotificationEventArgs<string>> DoSomething;
...
Notify(DoSomething, new NotificationEventArgs<string>("Message"));
V:
var vm = DataContext as SomeViewModel; //Get VM from view's DataContext
if (vm == null) return; //Check if conversion succeeded
vm.DoSomething += DoSomething; // Subscribe to event
private void DoSomething(object sender, NotificationEventArgs<string> e)
{
// Code
}
first of all, it's not a stupid question. Most of MVVM starters came from winforms and it's normal to have the tendency to bring in your winforms practices and work on code behind. Now all you have to do is forget that and think MVVM.
Going back to your question, you have a dictionary that your VM is processing and you are accessing that dictionary from the view. Your view should not have any idea about your viewmodel except through binding.
Making a window flash when there are changes in the viewmodel sounds like an attached behavior to me. Here's a good read about attached behavior.
http://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF
To make it easier, I'll try to give you a very simple example that will somehow be relevant to your case.
Create an attached behavior class where you have an IEnumerable where in whenever you add something a messagebox will appear on the screen. Just change the messagebox code to whatever flashing animation you would like to do on notify.
public class FlashNotificationBehavior
{
public static readonly DependencyProperty FlashNotificationsProperty =
DependencyProperty.RegisterAttached(
"FlashNotifications",
typeof(IEnumerable),
typeof(FlashNotificationBehavior),
new UIPropertyMetadata(null, OnFlashNotificationsChange));
private static void OnFlashNotificationsChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var collection = e.NewValue as INotifyCollectionChanged;
collection.CollectionChanged += (sender, args) =>
{
if (args.Action == NotifyCollectionChangedAction.Add)
{
foreach (var items in args.NewItems)
MessageBox.Show(items.ToString());
}
};
}
public static IEnumerable GetFlashNotifications(DependencyObject d)
{
return (IEnumerable)d.GetValue(FlashNotificationsProperty);
}
public static void SetFlashNotifications(DependencyObject d, IEnumerable value)
{
d.SetValue(FlashNotificationsProperty, value);
}
}
In your viewmodel, you can create an ObservableCollection property, you need an observable collection so there is a collection changed event notification. I also added a command for adding so that you can test it.
public class MainViewModel : ViewModelBase
{
ObservableCollection<string> notifications;
public ObservableCollection<string> Notifications
{
get { return notifications; }
set
{
if (notifications != value)
{
notifications = value;
base.RaisePropertyChanged(() => this.Notifications);
}
}
}
public ICommand AddCommand
{
get
{
return new RelayCommand(() => this.Notifications.Add("Hello World"));
}
}
public MainViewModel()
{
this.Notifications = new ObservableCollection<string>();
}
}
And here's a view where you can bind it the Notifications proeprty from your view model.
<Window x:Class="WpfApplication7.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfApplication7.ViewModel"
xmlns:local="clr-namespace:WpfApplication7"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<Grid>
<StackPanel>
<ListBox ItemsSource="{Binding Notifications}"
local:FlashNotificationBehavior.FlashNotifications="{Binding Notifications}"></ListBox>
<Button Command="{Binding AddCommand}" >Add Something</Button>
</StackPanel>
</Grid>
Everytime you add something in the ObservableCollection, you will get a messagebox notifying the user that something has been added to your collection.
I hope that I helped in your problem. Just tell me if you need some clarifications.

ListBox bound to ObservableCollection doesn't update

I am having trouble getting a ListBox binding to work as expected. I'm currently attempting to bind a ListBox to a singleton exposed ObservableCollection of items. The items are a separate class themselves. Currently, I am binding like this:
<Window x:Class="toxySharp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:classes="clr-namespace:MyApplication.Classes"
Title="MainWindow" Height="325" Width="400"
DataContext="{Binding Source={x:Static local:SingletonClass.Instance}}">
<Grid x:Name="LayoutRoot">
<ListBox x:Name="lstMyList" ItemsSource="{Binding Path=Objects, Mode=TwoWay}" DisplayMemberPath="Name" />
</Grid>
</Window>
My singleton is a basic implementation like this:
public class SomeObject : INotifyPropertyChanged
{
private Int32 m_vId;
private String m_vName;
public SomeObject() { }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public Int32 Id
{
get { return this.m_vId; }
set { this.m_vId = value; NotifyPropertyChanged("Id"); }
}
public String Name
{
get { return this.m_vName; }
set { this.m_vName = value; NotifyPropertyChanged("Name"); }
}
}
public class SingletonClass : INotifyPropertyChanged
{
private static SingletonClass m_vInstance;
private ObservableCollection<SomeObject> m_vObjects;
private SingletonClass()
{
this.m_vObjects = new ObservableCollection<SomeObject>();
for (int x = 0; x < 255; x++)
this.m_vObjects.Add(new SomeObject() { Id = x, Name = String.Format("{0} - new object", x) });
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public static SingletonClass Instance
{
get
{
if (m_vInstance == null)
m_vInstance = new SingletonClass();
return m_vInstance;
}
}
public ObservableCollection<SomeObject> Objects
{
get { return this.m_vObjects; }
set { this.m_vObjects = value; NotifyPropertyChanged("Objects"); }
}
}
Currently, the binding works on startup. The application will bind and properly show the names of each object. For example this is a test app doing the same implementation:
In my main actual application I have async methods being called (Socket stuff BeginConnect, BeginSend, etc.) that use callbacks that can update the collection. (It's a list of players so when certain packets are received the list is updated with their data.)
My problem is when the collection is updated inside one of the async callbacks it doesn't update on the list. The collection data is updated properly, setting a break in the main code anywhere shows the collection being updated but the listbox never updates to reflect the changes. So it just stays saying the same thing no matter what.
Did I overlook something?
I've tried using a CollectionViewSource as well to allow filtering and that has the same problem.
== EDIT ==
I've found the problem which lies in the singleton with how the collection is initialized. Instead of using the internal copy member when initializing the collection, I needed to use the exposed property to allow it to update the UI.
So using the following fixed it:
private SingletonClass()
{
this.Objects = new ObservableCollection<SomeObject>();
for (int x = 0; x < 255; x++)
this.Objects.Add(new SomeObject() { Id = x, Name = String.Format("{0} - new object", x) });
}
However now that the list binding works I want to be able to filter this based on another property inside the object class. (In the example SomeObject). I have a boolean stating if the object is active. Trying to bind to a CollectionViewSource leads me back to the not updating problems. So is there a way to filter this manually and keep the ui updated?
My problem is when the collection is updated inside one of the async
callbacks it doesn't update on the list.
Well thats the problem isnt it! Observable collections are not thread safe. You need to make them that way.
No TwoWay binding mode or UpdateSourceTrigger=PropertyChanged will help in this case as the problem lies with multi threading in your code...
Use this custom implementation of thread safe and faster observable collection for your ease...
Fast performing and thread safe observable collection
As far as INotifyPropertyChanged interface is concerned, its 'PropertyChangedevent is automatically dispatched to the UI thread. So any multithreaded context updating the properties of a class that implementsINotifyPropertyChanged` will update the GUI.
Let me know if this helps,
UpdateSourceTrigger=PropertyChanged is missing
<ListBox x:Name="lstMyList" ItemsSource="{Binding Path=Objects,UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" DisplayMemberPath="Name" />

BindingList<> ListChanged event

I have a BindingList<> of a class set to the DataSource property of a BindingSource, which is in turn set to the DataSource property of a DataGridView.
1.
It is my understanding that any additions to the list will fire a ListChanged event which will propagate through the BindingSource and then onto the DataGridView, which will update itself to display the change. This will happen because the events have been automatically hooked up. (Yes?)
This is all fine and good when all the work is done on the UI thread, but when the list is created and changed from a non-UI thread, ultimately a cross-thread exception occurs when the grid is updated. I can understand why this happens, but no how to fix it...
2.
What I am having a tough time understanding, is where should I best intercept the ListChanged event to try and marshal things onto the UI thread? I am guessing that I need a reference to the UI thread somehow to help do this?
I have read many posts/articles on this, but I'm struggling because I don't fully understand the mechanisms at work here.
I will never be changing any items once they are in the list, only adding them, and initially clearing the list.
(I am using .NET 2.0)
You can extend BindingList to use an ISynchronizeInvoke (implemented by System.Windows.Forms.Control) to marshal the event invokations onto the UI thread.
Then all you need to do is use the new list type and all is sorted.
public partial class Form1 : System.Windows.Forms.Form {
SyncList<object> _List;
public Form1() {
InitializeComponent();
_List = new SyncList<object>(this);
}
}
public class SyncList<T> : System.ComponentModel.BindingList<T> {
private System.ComponentModel.ISynchronizeInvoke _SyncObject;
private System.Action<System.ComponentModel.ListChangedEventArgs> _FireEventAction;
public SyncList() : this(null) {
}
public SyncList(System.ComponentModel.ISynchronizeInvoke syncObject) {
_SyncObject = syncObject;
_FireEventAction = FireEvent;
}
protected override void OnListChanged(System.ComponentModel.ListChangedEventArgs args) {
if(_SyncObject == null) {
FireEvent(args);
}
else {
_SyncObject.Invoke(_FireEventAction, new object[] {args});
}
}
private void FireEvent(System.ComponentModel.ListChangedEventArgs args) {
base.OnListChanged(args);
}
}
That view is fair enough. Under the covers, other objects such as CurrencyManager and Binding make sure controls are updated when the underlying data source changes.
Adding an item to a data bound BindingList triggers a series of events that end up trying to update the DataGridView. Since the UI can only be updated from the UI thread, you should add items to BindingList from the UI thread through Control.Invoke.
I assembled a quick sample creating a Form with a DataGridView, a BindingSource and a Button.
The button spins up another thread that simulates getting a new item for inclusion in the BindingList.
The inclusion itself is done back in the UI thread through Control.Invoke.
public partial class BindingListChangedForm : Form {
BindingList<Person> people = new BindingList<Person>();
Action<Person> personAdder;
public BindingListChangedForm() {
InitializeComponent();
this.dataGridView1.AutoGenerateColumns = true;
this.bindingSource1.DataSource = this.people;
this.personAdder = this.PersonAdder;
}
private void button1_Click(object sender, EventArgs e) {
Thread t = new Thread(this.GotANewPersononBackgroundThread);
t.Start();
}
// runs on the background thread.
private void GotANewPersononBackgroundThread() {
Person person = new Person { Id = 1, Name = "Foo" };
//Invokes the delegate on the UI thread.
this.Invoke(this.personAdder, person);
}
//Called on the UI thread.
void PersonAdder(Person person) {
this.people.Add(person);
}
}
public class Person {
public int Id { get; set; }
public string Name { get; set; }
}

Categories

Resources