jrandomuser
jrandomuser

Reputation: 1720

WPF Two Way Binding and Updating UI from Background Thread

I fear I may already know the answer to this question but I'm holding out the smallest glimmer of hope that I am wrong.

I have repository that contains a Collection Property with a list of Items. This collection is updated from a webservice call that does 1 of 2 things. If it is the first call it will create a new instance of an Item for each row returned, populate it, and add it to the Collection. Subsequent calls instead look for that item in the collection and update its properties. The ViewModel, Item Class, and Repository implement INotifyPropertyChanged.

The call to the dataservice executes every 30 seconds updating the data using async / await with a Task similar to this: How to execute a method periodically from WPF client application using threading or timer. The ViewModel takes the items from the repositorys Collection and splits them into various collection properties. The View then binds via an ItemsControl to the individual items in each collection and properties are continually updated.

It all works beautifully... but, the DataService Call isn't in its own thread and despite the async/await the UI gets a little unresponsive about every 30 seconds. When I went to put the DataService call in a BackgroundWorker I realized I can't when it threw an exception about modifying from a different thread.

I am familar with this issue from WinForms but I was hoping to somehow sidestep it with WPF and twoway binding. Is there a way to make the UI more responsive with just async/await or is there a way put the updates in a thread without having to write the events to support a dispatcher to do the updates on the main thread?

Upvotes: 1

Views: 1800

Answers (2)

Troels Larsen
Troels Larsen

Reputation: 4651

Your issue may lie in notifying each property on each item indivually. Using WPFPref (still part of the Windows SDK, I believe), you can see dirty areas of your UI (areas that are being rendered). If you find that your entire UI is being render again and again that will bog down performance.

A solution to this could be to prevent the UI from being notified while you are doing your refresh. There are several options:

  1. Don't use INotifyPropertyChanged at all, but expose a LoadCompleted event on your viewmodel, and have your view subscribe to it. Then manually refresh the View on the DataGrid. I personally don't like this solution as it is a bit counter-MVVM, but it is efficient.
  2. Don't Notify each property individually, but when you are done with a row, call OnPropertyChanged with null as the property name. This will notify everything for that row. Normal property updated can be temporarily disabled by adding a boolean _deferRefresh to your viewmodels and have OnPropertyChanged() check this before raising the event.
  3. Don't notify each item individually, but disable notifications as in #2, and raise a PropertyChanged on the collection instead.
  4. If your only problem is in rendering, try setting a fixed height on your rows, and a fixed with on your cells. This will ensure that a new value in a cell doesn't cause the entire grid to be rendered again.

Upvotes: 2

Sivasubramanian
Sivasubramanian

Reputation: 965

use "Binding.IsAsync" Property in your binding. Set the IsAsync property to true when the get accessor of your binding source property might take a long time. When IsAsync property is true then the UI will not blocked until the value is bound. For more info : MSDN

One example is an image property with a get accessor that downloads from the Web. Setting IsAsync to true avoids blocking the UI while the download occurs.

Also you can use PriorityBinding to achieve your requirement. PriorityBinding in Windows Presentation Foundation (WPF) works by specifying a list of bindings. The list of bindings is ordered from highest priority to lowest priority. If the highest priority binding returns a value successfully when it is processed then there is never a need to process the other bindings in the list. It could be the case that the highest priority binding takes a long time to be evaluated, the next highest priority that returns a value successfully will be used until a binding of a higher priority returns a value successfully.

<PriorityBinding FallbackValue="defaultvalue">
        <Binding Path="SlowestDP" IsAsync="True"/>
        <Binding Path="SlowerDP" IsAsync="True"/>
        <Binding Path="FastDP" />
</PriorityBinding>

Detailed Info : PriorityBinding

Upvotes: 1

Related Questions