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. OnPropertyChanged
fulfills the role of the INotifyPropertyChanged
interface by calling a PropertyChangedEventHandler
.
在我的视图中,我有一个列表框绑定到ViewModel的message属性(列表
I need to ensure that OnPropertyChanged
is called on the UI thread - how do I do this? I've tried the following:
我需要确保在UI线程上调用OnPropertyChanged—我如何做到这一点?我试过以下:
public Dispatcher Dispatcher { get; set; }
public MyViewModel()
{
this.Dispatcher = Dispatcher.CurrentDispatcher;
}
Then adding the following to the OnPropertyChanged
method:
然后在OnPropertyChanged方法中添加以下内容:
if (this.Dispatcher != Dispatcher.CurrentDispatcher)
{
this.Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadStart(delegate
{
OnPropertyChanged(propertyName);
}));
return;
}
but this did not work. Any ideas?
但这并不奏效。什么好主意吗?
5 个解决方案
#1
33
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 technique I blogged about a while back.
您可以自己手动编组add(参见下面的示例),或者使用类似我不久前在博客中介绍的技术。
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);
}
#2
8
I REALLY like Jeremy's answer: Dispatching In Silverlight
我真的很喜欢杰里米的回答:在银光下调度
Summary:
简介:
-
Placing Dispatcher in ViewModel seems inelegant
在ViewModel中放置分派器似乎不太合适
-
Creating an Action<Action> property, set it to just run the action in the VM constructor
创建操作
属性,将其设置为仅在VM构造函数中运行操作 - When using the VM from the V, set the Action property to invoke the Dispatcher
- 当使用来自V的VM时,设置动作属性以调用分派器
#3
3
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's及时返回结果。
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上的代码也是如此。带有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:
我发现这两个链接很有用:
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
#4
0
I handle the BackgroundWorker.ReportProgress event outside of my view model and pass the actual BackgroundWorker instance and the ViewModel into my class which defines the asynch method(s).
我处理BackgroundWorker。在视图模型之外的ReportProgress事件,并将实际的BackgroundWorker实例和ViewModel传递到定义异步方法的类中。
The asynch method then calls bgWorker.ReportProgress and passes a class which wraps a delegate as the userstate (as object). The delegate I write as an anonymous method.
然后,异步方法调用bgWorker。ReportProgress并传递一个类,该类将委托包装为userstate(作为对象)。我用匿名方法写的委托。
In the event handler, I cast it from object back to the wrapper type and then invoke the delegate inside.
在事件处理程序中,我将它从对象转换回包装器类型,然后调用内部的委托。
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 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
#5
0
This is more of an extension to the accepted answer but I did this with my event hander ...
这是对公认答案的扩展,但我是用我的事件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);
}
}
#1
33
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 technique I blogged about a while back.
您可以自己手动编组add(参见下面的示例),或者使用类似我不久前在博客中介绍的技术。
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);
}
#2
8
I REALLY like Jeremy's answer: Dispatching In Silverlight
我真的很喜欢杰里米的回答:在银光下调度
Summary:
简介:
-
Placing Dispatcher in ViewModel seems inelegant
在ViewModel中放置分派器似乎不太合适
-
Creating an Action<Action> property, set it to just run the action in the VM constructor
创建操作
属性,将其设置为仅在VM构造函数中运行操作 - When using the VM from the V, set the Action property to invoke the Dispatcher
- 当使用来自V的VM时,设置动作属性以调用分派器
#3
3
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's及时返回结果。
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上的代码也是如此。带有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:
我发现这两个链接很有用:
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
#4
0
I handle the BackgroundWorker.ReportProgress event outside of my view model and pass the actual BackgroundWorker instance and the ViewModel into my class which defines the asynch method(s).
我处理BackgroundWorker。在视图模型之外的ReportProgress事件,并将实际的BackgroundWorker实例和ViewModel传递到定义异步方法的类中。
The asynch method then calls bgWorker.ReportProgress and passes a class which wraps a delegate as the userstate (as object). The delegate I write as an anonymous method.
然后,异步方法调用bgWorker。ReportProgress并传递一个类,该类将委托包装为userstate(作为对象)。我用匿名方法写的委托。
In the event handler, I cast it from object back to the wrapper type and then invoke the delegate inside.
在事件处理程序中,我将它从对象转换回包装器类型,然后调用内部的委托。
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 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
#5
0
This is more of an extension to the accepted answer but I did this with my event hander ...
这是对公认答案的扩展,但我是用我的事件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);
}
}