WPF update observableCollection in UI thread - c#

I have WinForms with WPF UserControl.
public partial class DynamicMediator : Form
{
public DynamicMediator()
{
InitializeComponent();
lmDynamicMediator = new LMDynamicMediator.MediatorMainWindow();
this.elementHost1.Child = this.lmDynamicMediator;
}
public MainWindowViewModel GetEditFormViewModel()
{
return lmDynamicMediator.Resources["ViewModel"] as MainWindowViewModel;
}
}
I start a new process in my ViewModel
after that I need to update my observableCollection in ViewModel
I use
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => HasListMessages.Add(item)));
but I have exception like this
This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread
if I use code like this
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => HasListMessages.Add(item)));
I have exception like this
System.Windows.Application.Current.get returned null
What I do wrong?
How I can get System.Windows.Application.Current in my ViwModel

Calling Dispatcher.CurrentDispatcher in a thread that is not yet associated with a Dispatcher will create a new Dispatcher, which is not the Dispatcher of the UI thread.
Use the Dispatcher of the thread in which the view model instance is created. Keep it in a field of the view model class
public class MainWindowViewModel
{
private readonly Dispatcher dispatcher;
public MainWindowViewModel()
{
dispatcher = Dispatcher.CurrentDispatcher;
}
...
}
and later use that field:
dispatcher.BeginInvoke(() => HasListMessages.Add(item));

Related

Using BindingOperations.EnableCollectionSynchronization

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

ManualResetEvent WaitOne blocks the owner Thread of my CollectionView

I've written a WPF WizardFramework which performs some actions in the background using some BackgroundWorker. While processing it can happen that I have to update an ObservableCollection which is bound to my UI.
For this case I've written a ThreadableObservableCollection, which provides threadsafe methods for Insert, Remove and RemoveAt. Though I'm using .NET 4.5 I was not able to get BindingOperations.EnableCollectionSynchronization working without many other invalid access exceptions. My Collection looks like:
public class ThreadableObservableCollection<T> : ObservableCollection<T>
{
private readonly Dispatcher _dispatcher;
public ThreadableObservableCollection()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
public void ThreadsafeInsert(int pos, T item, Action callback)
{
if (_dispatcher.CheckAccess())
{
Insert(pos, item);
callback();
}
else
{
_dispatcher.Invoke(() =>
{
Insert(pos, item);
callback();
});
}
}
[..]
}
This is working as expected, while I am using the wizard in my application. Now I'm using NUnit to write some integrationtests for the application.
There's a listener which waits for the WizardViewModel to finish it's work and looking for some pages which are injected in the Steps-Collection. After the asyncrone work is done I can use Validate to check the viewmodel state.
Unfortunately I'm using a ManualResetEvent to wait for the wizard to close. This looks like following:
public class WizardValidator : IValidator, IDisposable
{
private WizardViewModel _dialog;
private readonly ManualResetEvent _dialogClosed = new ManualResetEvent(false);
[..]
public void ListenTo(WizardViewModel dialog)
{
_dialog = dialog;
dialog.RequestClose += (sender, args) => _dialogClosed.Set();
dialog.StepsDefaultView.CurrentChanged += StepsDefaultViewOnCurrentChanged;
_dialogClosed.WaitOne();
}
[..]
}
Now there's a problem:
While the Application is running the UI Thread is not blocked, the Collection can be updated without any problems. But in my testcases the "main" Thread where I initialize the ViewModel (and because of that the Collections) is an AppDomainThread which is blocked by the testcode. Now my ThreadsafeInsert wants to update the collection but cannot use the AppDomain Thread.
But I have to wait for the wizard to finish, how can I solve this kind of deadlock? Or is there a more elegant solution for this one?
edit:
I worked around this problem with a check if there's a user interface, and only then I invoke on the Application-Thread, otherwise I change the collection intentionally on another thread. This does not prevent the exception, but it is not recognized from the test... the items are inserted nevertheless, only the NotifyCollectionChanged-Handler is not called (which is only used in the UI anyway).
if (Application.Current != null)
{
Application.Current.Dispatcher.Invoke(() =>
{
Steps.Insert(pos, step);
stepsView.MoveCurrentTo(step);
});
}
else
{
new Action(() => Steps.Insert(pos, step)).BeginInvoke(ar => stepsView.MoveCurrentToPosition(pos), null);
}
This is an ugly workaround and I am still interested in a clean solution.
Is there a way to use an alternate Dispatcher to create (e.g.) the whole ViewModel and use this to change my collection?
As I see the main problem that main thread is blocked and other operations are trying to be executed in main thread too? What about not to block main thread, like this:
// helper functions
public void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
new DispatcherOperationCallback(ExitFrame), frame);
Dispatcher.PushFrame(frame);
}
public object ExitFrame(object f)
{
((DispatcherFrame)f).Continue = false;
return null;
}
// in your code:
while(!_dialogClosed.WaitOne(200))
DoEvents();
If it will not help then I guess need to try some SynchronisationContext workarounds.
I think the problems boil down to the fact that you create ObservableCollection that is tied to Dispatcher object.
Involving Dispatcher object directly is almost never good idea(as you just witnessed). Instead I would suggest you to see how others have implemented ThreadSafeObservableCollection. This is a little example I put together, it should illustrate the point:
public class ThreadSafeObservableCollection<T> : ObservableCollection<T>
{
private readonly object _lock = new object();
public ThreadSafeObservableCollection()
{
BindingOperations.CollectionRegistering += CollectionRegistering;
}
protected override void InsertItem(int index, T item)
{
lock (_lock)
{
base.InsertItem(index, item);
}
}
private void CollectionRegistering(object sender, CollectionRegisteringEventArgs e)
{
if (e.Collection == this)
BindingOperations.EnableCollectionSynchronization(this, _lock);
}
}

Updating ObservableCollection on dispatcher thread causes UI to freeze

When updating my ObservableCollection, I was getting this error:
This type of CollectionView does not support changes to its
SourceCollection from a thread different from the Dispatcher thread.
Using this answer as a guide, I thought this code would work:
private ObservableCollection<string> _userMessages = new ObservableCollection<string>();
public void AddUserMessage(string message)
{
lock (_locker)
{
Action action = () =>
{
this._userMessages.Add(message);
};
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, action);
}
}
However, my UI now freezes when calling Dispatcher.Invoke(). What am I doing incorrectly?
Note: I needed to do this because I'm (sometimes) updating my ObservableCollection from events.
Try this:
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, action);
You invoke your action synchronously and it causes UI to freeze.

Update bound control from own background thread

I have WPF with MVVM app. ViewModel and View are connected. Controls from View are bound to ViewModel, and ViewModel inherits INotifyChanged. Simple control update in ViewModel works fine.
I'd like to create some info class that contains some info fields. Next I want to create my own thread that maps fields from Info class to fields in ViewModel that updates View. Object of Info class will be used as arg of function called in ViewModel.
private int someControl;
public SomeControl {
get{return someControl;}
set
{
someControl = value;
OnPropChanged("SomeControl");
}
private InfoClass info = new InfoClass();
Thread thread = null;
public ViewModel()
{
Thread thread = new Thread(new ThreadStart(update));
thread.IsBackground = true;
thread.start();
someLongFunction(info);
}
private void update()
{
SomeControl = info.someField;
thread.sleep(1000);
update();
}
What should I add or change to get update periodically? Now, update is only when someLongFunction ends its job.
If what you actually want is continuous view updates then you don't have to create a thread for that because then you will have to make those updates on the Dispatcher thread (UI Thread). Instead you can use the DispatcherTimer class which was built exactly for this kind of situation where you can provide an interval and the DispatcherTimer will call your method periodically at that interval and will do that automatically on the Dispatcher Thread.

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