Hi Sebatian, starting with .NET 4.5 I use NotifyPropertyChanged in non-UI-thread without dispatching to inform the UI for refreshing, only if I don't consume PropertyChanged event in my own code.
Dispatcher required for changing WPF bound properties?
Hello,
From time to time I read some articles that state that if WPF binds to a property that implements INotifyProperyChanged, such property can be changed from any thread without a dispatcher, because WPF would automatically dispatch the INotifyProperyChanged event to the UI-thread.
On the other hand, all Microsoft samples that I found are still using the dispatcher even for INotifyProperyChanged implementing properties.
To be very clear here, I'm not talking about collections, nor dependency properties.
Some links that talk about such "auto-dispatching" are:
https://stackoverflow.com/questions/8994714/updating-bound-properties-from-a-background-thread
https://learn.microsoft.com/en-us/archive/msdn-magazine/2014/april/mvvm-multithreading-and-dispatching-in-mvvm-applications (2nd paragraph under 'Dispatching in MVVM applications')
https://metashapes.com/blog/not-shooting-foot-wpf-best-practices/ (Item #4)
If this would be true, it would ease programming a lot.
Currently, I'm dispatching everything, and I would only change this if I can be sure that this is an official feature and will still work on .Net 5.
So, any information, especially official sources, are highly appreciated.
Thanks, Sebastian
3 answers
Sort by: Most helpful
-
Peter Fleischer (former MVP) 19,326 Reputation points
2020-04-01T15:47:42.623+00:00 -
Darius Geiß 1 Reputation point
2020-04-03T08:42:21.827+00:00 Hello PeterFleischer,
I checked your code and found the reason for the InvalidOperationException. It has nothing to do with the PropertyChanged, that you throw from a background thread. The reason for the exception is, that you are trying to modify a UI bound collection from a background thread (see codebehind line 19).
If you want to modify a UI bound collection from a background thread, than you have to use either the UI dispatcher or BindingOperations.EnableCollectionSynchronization.
Here is the modified Window97 class:
Public Class Window97 Private uiDispatcher As Dispatcher = Dispatcher.CurrentDispatcher Public Sub New() ' This call is required by the designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call. Dim vm As New Window97VM(AddressOf prot) Me.DataContext = vm End Sub Private Sub prot(msg As String) uiDispatcher.Invoke(New Action(Sub() Me.lbProt.Items.Add(msg) End Sub)) End Sub End Class
-
Peter Fleischer (former MVP) 19,326 Reputation points
2020-04-02T19:23:46.347+00:00 Hi Sebastian,
try following demo with and without comments.
XAML:
<Window x:Class="Window97" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="Window97" Height="450" Width="800"> <StackPanel> <ListBox ItemsSource="{Binding View}" Height="200"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock> <Hyperlink NavigateUri="{Binding NaviUri}" IsEnabled="{Binding Ready}"> <Run Text="{Binding LinkName}"/> </Hyperlink> </TextBlock> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <ListBox x:Name="lbProt" Height="200"/> </StackPanel> </Window>
and code:
Imports System.Collections.ObjectModel Imports System.ComponentModel Imports System.Runtime.CompilerServices Imports System.Threading Imports System.Windows.Threading Public Class Window97 Public Sub New() ' This call is required by the designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call. Dim vm As New Window97VM(AddressOf prot) Me.DataContext = vm End Sub Private Sub prot(msg As String) Me.lbProt.Items.Add(msg) End Sub End Class Public Class Window97VM Private rnd As New Random Public Sub New(p As Action(Of String)) For i = 1 To 100 Dim d As New Window97Data With {.LinkName = $"Link {i} noready", .NaviUri = "http://www.microsoft.com", .Ready = False} AddHandler d.PropertyChanged, Sub(sender, e) ' comment this out for test without UI dispatcher p($"NotifyPropertyChanged in {e.PropertyName}") End Sub col.Add(d) Next cvs.Source = col ' ThreadPool.SetMaxThreads(3, 3) For Each item As Window97Data In col ThreadPool.QueueUserWorkItem(Sub(state) item.Execute(CType(state, Integer)) End Sub, rnd.Next(500, 2000)) Next End Sub Private col As New ObservableCollection(Of Window97Data) Private cvs As New CollectionViewSource Public ReadOnly Property View As ICollectionView Get Return cvs.View End Get End Property End Class Public Class Window97Data Implements INotifyPropertyChanged Public Property LinkName As String Public Property NaviUri As String Public Property Ready As Boolean Private uiDispatcher As Dispatcher = Dispatcher.CurrentDispatcher Friend Sub Execute(par As Integer) Thread.Sleep(par) LinkName = LinkName.Replace("noready", "ready") Ready = True ' comment tis out for test without UI dispatcher 'uiDispatcher.Invoke(New Action(Sub() OnPropChanged(NameOf(LinkName)) OnPropChanged(NameOf(Ready)) 'End Sub)) End Sub Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged Private Sub OnPropChanged(<CallerMemberName> Optional propName As String = "") RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName)) End Sub End Class