确保在MVVM WPF应用程序的UI线程上调用OnPropertyChanged()

时间:2009-02-26 13:39:43

标签: .net wpf multithreading mvvm

在我正在使用MVVM模式编写的WPF应用程序中,我有一个后台进程可以做到这一点,但是需要从中获取状态更新到UI。

我正在使用MVVM模式,因此我的ViewModel几乎不知道将模型呈现给用户的视图(UI)。

假设我的ViewModel中有以下方法:

public void backgroundWorker_ReportProgress(object sender, ReportProgressArgs e)
{
    this.Messages.Add(e.Message);
    OnPropertyChanged("Messages");
}

在我看来,我有一个ListBox绑定到ViewModel的Messages属性(List<string>)。 OnPropertyChanged通过调用INotifyPropertyChanged来履行PropertyChangedEventHandler界面的角色。

我需要确保在UI线程上调用OnPropertyChanged - 我该怎么做?我尝试了以下内容:

public Dispatcher Dispatcher { get; set; }
public MyViewModel()
{ 
    this.Dispatcher = Dispatcher.CurrentDispatcher;
}

然后将以下内容添加到OnPropertyChanged方法中:

if (this.Dispatcher != Dispatcher.CurrentDispatcher)
{
    this.Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadStart(delegate
    {
        OnPropertyChanged(propertyName);
    }));
    return;
}

但这不起作用。有什么想法吗?

5 个答案:

答案 0 :(得分:34)

WPF会自动将属性更改封送到UI线程。但是,它不会编组集合更改,因此我怀疑您添加消息会导致失败。

您可以自己手动填充添加内容(请参阅下面的示例),或使用类似this technique之类的内容。我在一段时间后发表了博客。

手动编组:

public void backgroundWorker_ReportProgress(object sender, ReportProgressArgs e)
{
    Dispatcher.Invoke(new Action<string>(AddMessage), e.Message);
    OnPropertyChanged("Messages");
}

private void AddMessage(string message)
{
    Dispatcher.VerifyAccess();
    Messages.Add(message);
}

答案 1 :(得分:8)

我真的很喜欢杰里米的回答: Dispatching In Silverlight

要点:

  • 在ViewModel中放置Dispatcher似乎不太优雅

  • 创建动作&lt;动作&gt;属性,将其设置为仅在VM构造函数中运行操作

  • 使用V中的VM时,请设置Action属性以调用Dispatcher

答案 2 :(得分:3)

本周我有一个类似的场景(MVVM也在这里)。我有一个单独的类做它的事情,报告事件处理程序的状态。正在按预期调用事件处理程序,我可以通过Debug.WriteLine的看到正确的结果。

但是对于WPF,无论我做什么,UI都会在流程完成之前更新。一旦该过程完成,UI将按预期更新。就好像它正在获取PropertyChanged,但等待线程完成,然后立即进行UI更新。

(令我沮丧的是,带有DoEvents和.Refresh()的Windows.Forms中的相同代码就像魅力一样。)

到目前为止,我已经通过在自己的线程上启动进程来解决这个问题:

//hook up event handler
myProcess.MyEvent += new EventHandler<MyEventArgs>(MyEventHandler); 

//start it on a thread ...
ThreadStart threadStart = new ThreadStart(myProcess.Start);

Thread thread = new Thread(threadStart);

thread.Start();

然后在事件处理程序中:

private void MyEventHandler(object sender, MyEventArgs e) { 
....
Application.Current.Dispatcher.Invoke(
                DispatcherPriority.Send,
                (DispatcherOperationCallback)(arg =>
                { 
         //do UI updating here ...
        }), null);

我不推荐这段代码,因为我仍然试图理解WPF线程模型,Dispatcher如何工作,以及为什么在我的情况下,即使事件处理程序被调用,UI也不会更新,直到进程完成正如所料(按设计?)。但到目前为止,这对我有用。

我发现这两个链接很有用:

http://www.nbdtech.com/blog/archive/2007/08/01/Passing-Wpf-Objects-Between-Threads-With-Source-Code.aspx

http://srtsolutions.com/blogs/mikewoelmer/archive/2009/04/17/dealing-with-unhandled-exceptions-in-wpf.aspx

答案 3 :(得分:0)

我处理BackgroundWorker.ReportProgress之外的ViewModel事件,并将实际的BackgroundWorker实例和ViewModel传递到定义async方法的类中(或多个)。

async方法然后调用bgWorker.ReportProgress并传递一个包含委托作为UserState(作为object)的类。我作为匿名方法写的代表。

在事件处理程序中,我将它从object转换回包装器类型,然后调用其中的委托。

所有这些意味着我可以直接从异步运行的代码中编写UI更改,但它只是围绕它进行编码。

这更详细地解释了它:

http://lukepuplett.blogspot.com/2009/05/updating-ui-from-asynchronous-ops.html

答案 4 :(得分:0)

这更像是对已接受答案的延伸,但我是在事件处理中做到的......

using System.Threading;

private void Handler(object sender, RoutedEventArgs e)
{
    if (Thread.CurrentThread == this.Dispatcher.Thread)
    {
        //do stuff to this
    }
    else
    {
        this.Dispatcher.Invoke(
            new Action<object, RoutedEventArgs>(Handler),
            sender,
            e);
    }
}