.net 确保在 MVVM WPF 应用程序中的 UI 线程上调用 OnPropertyChanged()
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/590590/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Making sure OnPropertyChanged() is called on UI thread in MVVM WPF app
提问by Adam Barney
In a WPF app that I'm writing using the MVVM pattern, I have a background process that doing it's thing, but need to get status updates from it out to the UI.
在我使用 MVVM 模式编写的 WPF 应用程序中,我有一个后台进程在做这件事,但需要从中获取状态更新到 UI。
I'm using the MVVM pattern, so my ViewModel knows virtually nothing of the view (UI) that is presenting the model to the user.
我使用的是 MVVM 模式,因此我的 ViewModel 几乎不知道向用户呈现模型的视图 (UI)。
Say I have the following method in my ViewModel:
假设我的 ViewModel 中有以下方法:
public void backgroundWorker_ReportProgress(object sender, ReportProgressArgs e)
{
this.Messages.Add(e.Message);
OnPropertyChanged("Messages");
}
In my view, I have a ListBox bound to the Messages property (a List<string>) of the ViewModel. OnPropertyChangedfulfills the role of the INotifyPropertyChangedinterface by calling a PropertyChangedEventHandler.
在我看来,我有一个绑定到List<string>ViewModel的 Messages 属性 (a ) 的ListBox 。 通过调用 aOnPropertyChanged来完成INotifyPropertyChanged接口的角色PropertyChangedEventHandler。
I need to ensure that OnPropertyChangedis called on the UI thread - how do I do this? I've tried the following:
我需要确保OnPropertyChanged在 UI 线程上调用 - 我该怎么做?我尝试了以下方法:
public Dispatcher Dispatcher { get; set; }
public MyViewModel()
{
this.Dispatcher = Dispatcher.CurrentDispatcher;
}
Then adding the following to the OnPropertyChangedmethod:
然后在OnPropertyChanged方法中添加以下内容:
if (this.Dispatcher != Dispatcher.CurrentDispatcher)
{
this.Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadStart(delegate
{
OnPropertyChanged(propertyName);
}));
return;
}
but this did not work. Any ideas?
但这不起作用。有任何想法吗?
采纳答案by Kent Boogaart
WPF automatically marshals property changes to the UI thread. However, it does not marshal collection changes, so I suspect your adding a message is causing the failure.
WPF 会自动将属性更改封送至 UI 线程。但是,它不会封送集合更改,因此我怀疑您添加的消息会导致失败。
You can marshal the add manually yourself (see example below), or use something like this techniqueI blogged about a while back.
您可以自己手动编组添加(请参见下面的示例),或者使用类似我不久前在博客中发表的这种技术。
Manually marshalling:
手动编组:
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);
}
回答by Mike Graham
I REALLY like Jeremy's answer: Dispatching In Silverlight
我真的很喜欢 Jeremy 的回答: 在 Silverlight 中调度
Summary:
概括:
Placing Dispatcher in ViewModel seems inelegant
Creating an Action<Action> property, set it to just run the action in the VM constructor
- When using the VM from the V, set the Action property to invoke the Dispatcher
在 ViewModel 中放置 Dispatcher 似乎不雅
创建 Action<Action> 属性,将其设置为仅在 VM 构造函数中运行操作
- 从 V 使用 VM 时,设置 Action 属性以调用 Dispatcher
回答by Jeffrey Knight
I had a similar scenario just this week (MVVM here too). I had a separate class doing its thing, reporting back status on an event handler. The event handler was being called as expected, and I could see the results coming back right on time with Debug.WriteLine's.
就在本周,我遇到了类似的情况(这里也有 MVVM)。我有一个单独的类在做它的事情,报告事件处理程序的状态。事件处理程序按预期被调用,我可以看到结果通过 Debug.WriteLine 的.
But with WPF, no matter what I did, the UI would not update until the process was complete. As soon as the process finished, the UI would update as expected. It was as if it was getting PropertyChanged, but waiting for the thread to complete before doing the UI updates all at once.
但是使用 WPF,无论我做什么,UI 都不会在该过程完成之前更新。该过程完成后,UI 将按预期更新。就好像它正在获取 PropertyChanged,但是在一次完成 UI 更新之前等待线程完成。
(Much to my dismay, the same code in Windows.Forms with a DoEvents and .Refresh() worked like a charm.)
(令我沮丧的是,Windows.Forms 中带有 DoEvents 和 .Refresh() 的相同代码工作起来很有吸引力。)
So far, I've resolved this by starting the process on its own thread:
到目前为止,我已经通过在自己的线程上启动进程来解决这个问题:
//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();
and then in the event handler:
然后在事件处理程序中:
private void MyEventHandler(object sender, MyEventArgs e) {
....
Application.Current.Dispatcher.Invoke(
DispatcherPriority.Send,
(DispatcherOperationCallback)(arg =>
{
//do UI updating here ...
}), null);
I'm not recommending this code, since I'm still trying to understand the WPF thread model, how Dispatcher works, and why in my case the UI wouldn't update until the process was complete even with event handler getting called as expected (by design?). But this has worked for me so far.
我不推荐此代码,因为我仍在尝试了解 WPF 线程模型、Dispatcher 的工作方式,以及为什么在我的情况下,即使事件处理程序按预期调用,UI 也不会在进程完成之前更新(按设计?)。但到目前为止,这对我有用。
I found these two links helpful:
我发现这两个链接很有帮助:
回答by Luke Puplett
I handle the BackgroundWorker.ReportProgressevent outside of my ViewModeland pass the actual BackgroundWorkerinstance and the ViewModelinto my class which defines the asyncmethod(s).
我在我的BackgroundWorker.ReportProgress外部处理事件ViewModel并将实际BackgroundWorker实例和 传递ViewModel到我定义async方法的类中。
The asyncmethod then calls bgWorker.ReportProgressand passes a class which wraps a delegate as the UserState(as object). The delegate I write as an anonymous method.
async然后该方法调用bgWorker.ReportProgress并传递一个类,该类将委托包装为UserState(as object)。我作为匿名方法编写的委托。
In the event handler, I cast it from objectback to the wrapper type and then invoke the delegate inside.
在事件处理程序中,我将它从object回转换为包装器类型,然后调用内部的委托。
All this means that I can code UI changes directly from the code that's running asynchronously, but it just has this wrapping around it.
所有这一切意味着我可以直接从异步运行的代码中编写 UI 更改,但它只是围绕它进行了包装。
This explains it in more detail:
这更详细地解释了它:
http://lukepuplett.blogspot.com/2009/05/updating-ui-from-asynchronous-ops.html
http://lukepuplett.blogspot.com/2009/05/updating-ui-from-asynchronous-ops.html
回答by Jodrell
This is more of an extension to the accepted answer but I did this with my event hander ...
这更像是对已接受答案的扩展,但我用我的事件处理程序做到了这一点......
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);
}
}

