How to process and save a GUI updated using data binding?

Problem history

This is a continuation of my previous question.

How to start a thread to update the GUI?

but since John shed new light on the problem, I would have to completely rewrite the original question that would make this topic unreadable. So, a new, very specific question.

Problem

Two parts:

  • CPU hungry heavy processing as a library (back-end)
  • WPF GUI with data binding, which serves as a monitor for processing (front-end)

The current situation - the library sends so many notifications about data changes, which, despite the fact that it works within its flow, completely hides the WPF data binding mechanism, and as a result, not only does the data monitoring not work (it does not update), but also The entire GUI is frozen during data processing.

The goal is a well-thought-out, polished way to constantly update the graphical interface - I am not saying that it should immediately display data (it may even skip some changes), but it cannot freeze when performing calculations.

Example

This is a simplified example, but it shows a problem.

XAML part:

<StackPanel Orientation="Vertical"> <Button Click="Button_Click">Start</Button> <TextBlock Text="{Binding Path=Counter}"/> </StackPanel> 

Part C # (please note that this is the code of one part, but there are two sections):

 public partial class MainWindow : Window,INotifyPropertyChanged { // GUI part public MainWindow() { InitializeComponent(); DataContext = this; } private void Button_Click(object sender, RoutedEventArgs e) { var thread = new Thread(doProcessing); thread.IsBackground = true; thread.Start(); } // this is non-GUI part -- do not mess with GUI here public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string property_name) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(property_name)); } long counter; public long Counter { get { return counter; } set { if (counter != value) { counter = value; OnPropertyChanged("Counter"); } } } void doProcessing() { var tmp = 10000.0; for (Counter = 0; Counter < 10000000; ++Counter) { if (Counter % 2 == 0) tmp = Math.Sqrt(tmp); else tmp = Math.Pow(tmp, 2.0); } } } 

Known workarounds

(Please do not send them as responses)

I sorted the list according to how much I like the workaround, i.e. how much work is required, its limitations, etc.

  • This is mine, it is ugly, but the simplicity kills it - before sending a notification to freeze the thread - Thread.Sleep (1) - so that the potential receiver "breathes" - it works, it is minimalistic, it is ugly, and it ALWAYS slows down the calculation, even if the graphical interface absent
  • based on John’s idea - to abandon data binding COMPLETELY (one widget with data binding is enough to jam), and instead check the data from time to time and update the GUI manually - I did not teach WPF just to refuse it now; -)
  • Thomas's idea is to insert a proxy between the library and the interface, which will receive all notifications from the library and send some of them to WPF, for example, every second - the disadvantage is that you have to duplicate all objects sending notifications
  • based on Jon’s idea - pass the GUI manager to the library and use it to send notifications - why is it ugly? because there can be no GUI at all

My current “solution” adds Sleep to the main loop. The slowdown is slight, but enough to keep WPF up to date (so it's even better than sleeping before each notification).

I'm all ears for real solutions, not some tricks.

Notes

A note about refusing data binding - for me its design is broken, in WPF you have a single communication channel, you cannot directly bind to the source of the change. Data binding filters the source based on the name (string!). This requires some computation, even if you use some kind of smart structure to save all the lines.

Edit: A note on abstractions - give me an old timer, but I began to study the computer, convinced that computers should help people. Repetitive tasks are an area of ​​computers, not people. No matter what you call it - MVVM, abstractions, interface, one inheritance, if you write the same code again and again, and you have no way to automate what you do, you use a broken tool. So, for example, lambdas are excellent (less work for me), but inheritance is not (more work for me), data binding (as an idea) is great (less work), and the need for a proxy level for EVERY library that I link - This is a broken idea because it requires a lot of work.

+14
multithreading c # data-binding wpf
Dec 23 '10 at 21:24
source share
7 answers

In my WPF applications, I do not send property changes directly from the model to the GUI. It always goes through a proxy (ViewModel).

Property change events are placed in a queue that is read from a GUI thread on a timer.

I don’t understand how it can be much more. You just need one more listener for your changetime model event.

Create a ViewModel class with the Model property, which is your current data text. Change the data binding to "Model.Property" and add code to connect events.

It looks something like this:

 public MyModel Model { get; private set; } public MyViewModel() { Model = new MyModel(); Model.PropertyChanged += (s,e) => SomethingChangedInModel(e.PropertyName); } private HashSet<string> _propertyChanges = new HashSet<string>(); public void SomethingChangedInModel(string propertyName) { lock (_propertyChanges) { if (_propertyChanges.Count == 0) _timer.Start(); _propertyChanges.Add(propertyName ?? ""); } } // this is connected to the DispatherTimer private void TimerCallback(object sender, EventArgs e) { List<string> changes = null; lock (_propertyChanges) { _Timer.Stop(); // doing this in callback is safe and disables timer if (!_propertyChanges.Contain("")) changes = new List<string>(_propertyChanges); _propertyChanges.Clear(); } if (changes == null) OnPropertyChange(null); else foreach (string property in changes) OnPropertyChanged(property); } 
+5
Dec 25 '10 at 17:02
source share

This is not a WPF issue per se. When you have a multi-year operation that quickly updates a dataset, updating the user interface — any user interface, be it WPF or WinForms or just VT100 emulation, will pose the same problem. User interface updates are relatively slow and complex, and their integration with a rapidly changing complex process without sacrificing this process requires a clear separation between them.

This clean separation is even more important in WPF because the user interface and lengthy work must be performed on separate threads so that the user interface does not freeze during the operation.

How do you achieve this pure separation? Implementing them yourself, providing a mechanism for periodically updating the user interface from a long-term process, and then checking everything to find out how often this mechanism should be called.

In WPF, you will have three components: 1) a view, which is the physical model of your user interface, 2) a view model, which is the logical data model displayed in the user interface, and that pushes changes to the data to the user interface through a change notification, and 3) your lengthy process.

A long-term process may be almost completely unaware of the user interface if it has two functions. It should disclose public properties and / or methods so that the view model can examine its state, and it should raise an event whenever the user interface needs to be updated.

The view model listens for this event. When an event is raised, it copies state information from the process into its data model, and its built-in change notification displays them in the user interface.

Multithreading complicates this, but only a little. The process must be executed in a different thread than in the user interface, and when its progress report is processed, its data will be copied over the threads.

After you have built these three parts, multithreading is very easy to do using WPF BackgroundWorker . You create an object that will start the process, pass its report on the work done with the BackgroundWorker ReportProgress event, and marshal the data from the properties of the object to the view model in this event handler. Then run the object’s long-running method in the BackgroundWorker DoWork event handler, and you will go well.

+4
Dec 24 '10 at 18:55
source share

A user interface that changes faster than the human eye can observe (~ 25 updates / sec) is not a convenient user interface. A typical user will observe the spectacle for no more than a minute before completely abandoning it. You walked past this if you did freeze the UI thread.

You must design for a person, not a car.

+3
Dec 23 '10 at 22:06
source share

Since too many notifications for the user interface are being processed, why not just activate the notifications a bit? This seems to work fine:

  if (value % 500 == 0) OnPropertyChanged("Counter"); 

You can also limit the frequency of notifications using a timer:

  public SO4522583() { InitializeComponent(); _timer = new DispatcherTimer(); _timer.Interval = TimeSpan.FromMilliseconds(50); _timer.Tick += new EventHandler(_timer_Tick); _timer.Start(); DataContext = this; } private bool _notified = false; private DispatcherTimer _timer; void _timer_Tick(object sender, EventArgs e) { _notified = false; } ... long counter; public long Counter { get { return counter; } set { if (counter != value) { counter = value; if (!_notified) { _notified = true; OnPropertyChanged("Counter"); } } } } 



EDIT: if you cannot skip notifications because they are used by other parts of your code, here is a solution that does not require big changes in your code:

  • create a new UICounter property that throttles notifications as shown above.
  • in the Counter customizer, update the UICounter
  • in the user interface, bind to UICounter , not Counter
+1
Dec 23 '10 at 21:46
source share

A layer is required between the UI and the library. This ensures that you can test the interaction, and also allow you to change the library with another implementation in the future without much change. This is not duplication, but a way of providing an interface to the UI layer for communication. This layer will receive objects from the library, convert them to specific data transfer objects and transfer them to another level, which will be responsible for throttling updates and converting them to specific VM objects. My opinion is that virtual machines should be as dumb as possible, and their sole responsibility should be to provide data for viewing.

0
Dec 25 '10 at 13:48
source share

Your sound is like slow-down-refresh-rate-of-bound-datagrid . At least the answers are similar

  • Have a shadow copy of the data bound to the gui element, instead of binding the original data.
  • Add an event handler that updates the shadow copy with a certain delay from the source data.
0
Dec 25 '10 at 19:42
source share

You need to disconnect the notification source from the notification target. Now, as you configure it, every time the value changes, you look through the entire update cycle (which, I believe, also blocks your processing function). This is not what you want.

Provide an output stream for your processing function, which it will use to record its notifications.

On the monitoring side, attach an input stream to this output stream and use it as a data source for the user interface component. Thus, the processing of notification events is completely absent - the processing is performed as quickly as possible, outputting the monitor data to the output stream that you provide. The user interface of the monitor simply conveys what it receives in the input stream.

You will need a stream to read continuously from the input stream. If there is no data, then it should be blocked. If it reads some data, it should upload it to the user interface.

Hello,

Rodney

0
Dec 05 '12 at 23:10
source share



All Articles