38

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

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

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

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

在我看来,我有一个 ListBox 绑定到List<string>ViewModel 的 Messages 属性 (a)。 通过调用 aOnPropertyChanged来完成接口的作用。INotifyPropertyChangedPropertyChangedEventHandler

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

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;
}

但这没有用。有任何想法吗?

4

5 回答 5

36

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

您可以自己手动编组添加(请参见下面的示例),或者使用类似我不久前在博客中写过的这种技术。

手动编组:

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);
}
于 2009-02-26T13:46:27.133 回答
8

我真的很喜欢 Jeremy 的回答: 在 Silverlight 中调度

概括:

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

  • 创建一个 Action<Action> 属性,将其设置为仅在 VM 构造函数中运行该操作

  • 从 V 使用 VM 时,设置 Action 属性以调用 Dispatcher
于 2010-02-05T16:18:38.013 回答
3

本周我也遇到了类似的情况(这里也是 MVVM)。我有一个单独的类在做它的事情,在事件处理程序上报告状态。事件处理程序按预期被调用,我可以看到结果使用 Debug.WriteLine 准时返回

但是使用 WPF,无论我做什么,UI 都不会更新,直到该过程完成。该过程完成后,UI 将按预期更新。就好像它正在获取 PropertyChanged,但在一次执行 UI 更新之前等待线程完成。

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

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

//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

于 2009-04-26T04:30:50.223 回答
0

我在我的外部处理BackgroundWorker.ReportProgress事件ViewModel并将实际BackgroundWorker实例和ViewModel定义async方法的类传递给我的类。

然后该async方法调用bgWorker.ReportProgress并传递一个将委托包装为UserState(as object) 的类。我作为匿名方法编写的委托。

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

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

这更详细地解释了它:

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

于 2009-05-15T15:52:49.320 回答
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);
    }
}
于 2011-05-26T12:04:00.540 回答