Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do the processing and keep GUI refreshed using databinding?

History of the problem

This is continuation of my previous question

How to start a thread to keep GUI refreshed?

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

The problem

Two pieces:

  • CPU hungry heavy-weight processing as a library (back-end)
  • WPF GUI with databinding which serves as monitor for the processing (front-end)

Current situation -- library sends so many notifications about data changes that despite it works within its own thread it completely jams WPF data binding mechanism, and in result not only monitoring the data does not work (it is not refreshed) but entire GUI is frozen while processing the data.

The aim -- well-designed, polished way to keep GUI up to date -- I am not saying it should display the data immediately (it can skip some changes even), but it cannot freeze while doing computation.

Example

This is simplified example, but it shows the problem.

XAML part:

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

C# part (please NOTE this is one piece code, but there are two sections of it):

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 repost them as answers)

I sorted the list according how much I like the workaround, i.e. how much work it requires, limitations of it, etc.

  1. this is mine, it is ugly, but simplicity of it kills -- before sending notification freeze a thread -- Thread.Sleep(1) -- to let the potential receiver "breathe" -- it works, it is minimalistic, it is ugly though, and it ALWAYS slows down computation even if no GUI is there
  2. based on Jon idea -- give up with data binding COMPLETELY (one widget with databinding is enough for jamming), and instead check from time to time data and update the GUI manually -- well, I didn't learn WPF just to give up with it now ;-)
  3. Thomas idea -- insert proxy between library and frontend which would receiver all notifications from the library, and pass some of them to WPF, like for example every second -- the downside is you have to duplicate all objects that send notifications
  4. based on Jon idea - pass GUI dispatcher to library and use it for sending notifications -- why it is ugly? because it could be no GUI at all

My current "solution" is adding Sleep in the main loop. The slowdown is negligible, but it is enough for WPF to be refreshed (so it is even better than sleeping before each notification).

I am all ears for real solutions, not some tricks.

Remarks

Remark on giving up with databinding -- for me the design of it is broken, in WPF you have single channel of communication, you cannot bind directly to the source of the change. The databinding filters the source based on name (string!). This requires some computation even if you use some clever structure to keep all the strings.

Edit: Remark on abstractions -- call me old timer, but I started learning computer convinced, that computers should help humans. Repetitive tasks are domain of computers, not humans. No matter how you call it -- MVVM, abstractions, interface, single inheritance, if you write the same code, over and over, and you don't have way to automatize the things you do, you use broken tool. So for example lambdas are great (less work for me) but single inheritance is not (more work for me), data binding (as an idea) is great (less work) but the need of proxy layer for EVERY library I bind to is broken idea because it requires a lot of work.

like image 400
greenoldman Avatar asked Dec 23 '10 21:12

greenoldman


2 Answers

In my WPF applications I don't send the property change directly from the model to the GUI. It always goes via a proxy (ViewModel).

The property change events are put in a queue which is read from the GUI thread on a timer.

Don't understand how that can be so much more work. You just need another listener for your model's propertychange event.

Create a ViewModel class with a "Model" property which is your current datacontext. Change the databindings to "Model.Property" and add some code to hook up the 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);
}
like image 195
adrianm Avatar answered Sep 28 '22 05:09

adrianm


This isn't really a WPF issue per se. When you have a long-running operation that updates a set of data rapidly, keeping the UI updated - any UI, whether it's WPF or WinForms or just VT100 emulation - is going to present the same problem. UI updates are comparatively slow and complex, and integrating them with a fast-changing complex process without hurting that process requires a clean separation between the two.

That clean separation is even more important in WPF because the UI and the long-running operation need to run on separate threads so that the UI doesn't freeze while the operation is running.

How do you achieve that clean separation? By implementing them independently, providing a mechanism for periodically updating the UI from within the long-running process, and then testing everything to figure out how frequently that mechanism should be invoked.

In WPF, you'll have three components: 1) a view, which is the physical model of your UI, 2) a view model, which is the logical model of the data that is displayed in the UI, and that pushes changes in the data out to the UI through change notification, and 3) your long-running process.

The long-running process can be almost completely unaware of the UI, so long as it does two things. It needs to expose public properties and/or methods so that the view model can examine its state, and it needs to raise an event whenever the UI should be updated.

The view model listens to that event. When the event is raised, it copies state information from the process to its data model, and its built-in change notification pushes those out to the UI.

Multithreading complicates this, but only a bit. The process needs to run on a different thread than the UI, and when its progress-reporting event is handled, its data will be copied across threads.

Once you've built these three pieces, the multithreading is very straightforward to accomplish using WPF's BackgroundWorker. You create the object that's going to run the process, wire its progress-reporting event up with the BackgroundWorker's ReportProgress event, and marshal data from the object's properties to the view model in that event handler. Then fire off the object's long-running method in the BackgroundWorker's DoWork event handler and you're good to go.

like image 37
Robert Rossney Avatar answered Sep 28 '22 05:09

Robert Rossney