Threading and WPF Binding

Situation

I get the following inconsistent behavior in my application: one of about 20 executions, the WPFToolkit DataGrid tied to a DataTable , will not display all rows without missing anything from 1 to 3 of the total 4 rows that were expected.

Internal work

  • DataGrid bound to DataTable , D1 , which is a property of the custom class C1 .
  • When the user stimulates the presentation, we must retrieve the data from the back-end, which may take some time. To do this, we create a stream (in fact, for this we use BackgroundWorker , but it seems there is no difference that it uses one or the other), which runs the M1 method, which opens the connections and request data. This thread is used to avoid the use of an unresponsive application.
  • M1 retrieves the data and first saves it on the DTO. After that, he asks C1 to clear the table. C1 does this (by calling a D1.Clear() ) and raises NotifyPropertyChanged() (from the stream).
  • M1 passes the new DataTable backend to C1 , which inserts row by row into D1. After the insert of the C1 rows is NotifyPropertyChanged() , NotifyPropertyChanged() occurs. The thread ends.

So, in other words, I clear the table, notify WPF, insert data, notify WPF, and exit.

In my opinion, while the last Notify is correctly consumed from the user interface, it should always show all the lines.

In addition to the DataTable , there are a large number of properties (mainly strings and int) that are updated and thus notified. We do not observe this behavior in any other case, only with a DataTable .

I know this goes deeper into WPF mechanisms for binding, but I hope someone can shed some light here. Any information on WPF binding or multithreading with WPF is welcome.

+4
source share
4 answers

The DataTable precedes WPF and thus does not implement INotifyCollectionChanged , which shows how WPF tracks collection changes. You have two options:

  • Replace the existing DataTable with a new DataTable (after you set the rows). Then run the property change notification.
  • Change the value of the DataTable to ObservableCollection. The collection will generate a change notification at any time when you change the list of items. (Please note that it will not work if you change the contents of one of the elements that are already in the list)

INotifyPropertyChanged notifies when a property has changed, and not when the internal state (whether it is a property or collection) has changed. When you fire the Property Changed event, WPF only restores the controls if the property is another object since the last data binding. This does not allow updating the entire screen when you change only one property several layers down in the graph of objects.

+3
source

Are you loading new data into the same DataTable instance that is already bound to the DataGrid?

If so, then (a) every time you make a change to the DataTable from your background code, it triggers notifications from the wrong stream, which is no-no; and (b) when you start PropertyChanged at the end, the DataGrid can be smart enough to notice that the link has not actually changed, so nothing needs to be done. (I don't know if DataGrid is trying to be smart, but that would not be unreasonable, especially considering how WPF builds views on top of collections - and this can help explain the symptoms you see.)

Try creating a new DataTable instance every time you need to update, and then when you finish populating this instance from your background thread, then assign a new (fully filled) link to your notification property and run PropertyChanged (and, of course, be sure to assignment + PropertyChanged from user interface thread).

+2
source

Based on Asti's third point, I often come across a cross-flow SilverChanged script and for this has a basic presentation model. The view model is based on PRISM NotificationObject , but of course you can implement the INotifyPropertyChanged interface directly if you do not want to use PRISM. Works just as well for Silverlight if you ever use it.

 namespace WPF.ViewModel { using System.Windows; using System.Windows.Threading; using Microsoft.Practices.Prism.ViewModel; /// <summary>The async notification object.</summary> public abstract class AsyncNotificationObject : NotificationObject { #region Constructors and Destructors /// <summary>Initializes a new instance of the <see cref="AsyncNotificationObject"/> class.</summary> protected AsyncNotificationObject() { Dispatcher = Application.Current.Dispatcher; } #endregion #region Properties /// <summary>Gets or sets Dispatcher.</summary> protected Dispatcher Dispatcher { get; set; } #endregion #region Methods /// <summary>The raise property changed.</summary> /// <param name="propertyName">The property name.</param> protected override void RaisePropertyChanged(string propertyName) { if (Dispatcher.CheckAccess()) base.RaisePropertyChanged(propertyName); else Dispatcher.BeginInvoke(() => base.RaisePropertyChanged(propertyName)); } #endregion } } 
+2
source
  • Instead of directly binding the DataTable, always bind the DataView tables. In versions of a table view, a DataView has a ListChanged , and a DataRowView has a PropertyChanged .
  • WPF supports updates down to line level. If you change the value of a string, it will immediately propagate.
  • PropertyChanged not thread safe. You cannot invoke any changes to run PropertyChanged in another thread. This must be done on the dispatcher, so go through the dispatcher. For example, instead of Model.Data = newData you should use Dispatcher.Invoke(new Action(model => model.Data = newData), Model) or similar.
+1
source

Source: https://habr.com/ru/post/1380900/


All Articles