如何使用数据绑定进行处理并保持GUI刷新?

gre*_*man 14 c# data-binding wpf multithreading

问题的历史

这是我之前的问题的延续

如何启动线程以保持GUI刷新?

但是,由于Jon对这个问题有了新的认识,我将不得不完全重写原始问题,这会使该主题难以理解.所以,新的,非常具体的问题.

问题

两件:

  • CPU饥饿的重量级处理作为库(后端)
  • 带数据绑定的WPF GUI,用作处理的监视器(前端)

当前情况 - 库发送了很多关于数据更改的通知,尽管它在自己的线程中工作,它完全阻塞了WPF数据绑定机制,结果不仅监视数据不起作用(它没有刷新),但整个GUI被冻结在处理数据时.

目标 - 精心设计,抛光的方式使GUI保持最新 - 我并不是说它应该立即显示数据(它甚至可以跳过一些变化),但它在计算时不能冻结.

这是简化的示例,但它显示了问题.

XAML部分:

    <StackPanel Orientation="Vertical">
        <Button Click="Button_Click">Start</Button>
        <TextBlock Text="{Binding Path=Counter}"/>
    </StackPanel>
Run Code Online (Sandbox Code Playgroud)

C#部分(请注意这是一个代码,但有两个部分):

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

已知的解决方法

(请不要将它们作为答案重新发布)

我按照我喜欢的解决方法排序列表,即它需要多少工作,限制等等.

  1. 这是我的,它是丑陋的,但它的简单性杀死 - 在发送通知冻结线程之前 - Thread.Sleep(1) - 让潜在的接收者"呼吸" - 它有效,它是简约的,它是虽然很难看,但即使没有GUI,它总是会减慢计算速度
  2. 基于Jon的想法 - 放弃数据绑定COMPLETELY(一个带数据绑定的小部件足以干扰),而是不时检查数据并手动更新GUI - 好吧,我没有学习WPF只是为了放弃现在用它;-)
  3. 托马斯的想法 - 在库和前端之间插入代理,它将接收来自库的所有通知,并将其中一些通知传递给WPF,例如每秒 - 缺点是您必须复制发送通知的所有对象
  4. 基于Jon的想法 - 将GUI调度程序传递给库并使用它来发送通知 - 为什么它很难看?因为它可能根本就没有GUI

我目前的"解决方案"是在主循环中添加Sleep.减速可以忽略不计,但它足以刷新WPF(所以它甚至比每次通知之前的睡眠更好).

我真的很聆听真正的解决方案,而不是一些技巧.

备注

备注放弃数据绑定 - 对我而言,它的设计被破坏,在WPF中你有单一的通信渠道,你不能直接绑定到变更的来源.数据绑定根据名称过滤源(字符串!).即使您使用一些聪明的结构来保留所有字符串,这也需要一些计算.

编辑:关于抽象的评论 - 叫我老计时器,但我开始学习计算机说服,计算机应该帮助人类.重复性任务是计算机领域,而不是人类.无论你怎么称呼它 - MVVM,抽象,接口,单继承,如果你一遍又一遍地编写相同的代码,并且你没有办法自动化你所做的事情,你就会使用破坏的工具.所以例如lambda是伟大的(对我来说工作较少),但单继承不是(对我来说更多的工作),数据绑定(作为一个想法)是伟大的(较少的工作),但我需要绑定到每个库的代理层是破碎的想法,因为它需要大量的工作.

adr*_*anm 5

在我的WPF应用程序中,我不直接将属性更改从模型发送到GUI.它总是通过代理(ViewModel).

属性更改事件放在队列中,该队列从计时器上的GUI线程读取.

不明白这是多么多的工作.您只需要另一个用于模型的propertychange事件的侦听器.

使用"Model"属性创建一个ViewModel类,该属性是您当前的datacontext.将数据绑定更改为"Model.Property"并添加一些代码以挂钩事件.

它看起来像这样:

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);
}
Run Code Online (Sandbox Code Playgroud)