我必须坚持不懈地寻找,因为这是我无法解决的另一个看似常见的问题。
这是我的问题 - 我正在使用WPF和MVVM,并且我有一个在模型中执行的状态机。如果发生错误,我需要将信息传递给ViewModel以显示错误。这部分似乎工作正常。当用户单击所需行为时,模型中的代码将继续,并查看用户与之交互的对象以确定下一步操作。
问题是模型需要重新加载文件,该文件使用所述文件的内容更新GUI。因为模型在一个线程中执行,你可以想象我接下来要问的问题 - 你到底该如何正确地与GUI同步?在MFC中,我会使用SendMessage或PostMessage来完成GUI更新。
我read articles for WinForms建议使用InvokeRequired在必要时自动调用BeginInvoke。我实际上并不知道BeginInvoke会完成我想要的东西,所以鼓励我学习这个。
如何从我的模型中实际调用BeginInvoke?这个方法是否适用于WPF? 我继续实现了一个委托,然后调用了Invoke,但是我得到了同样的错误,告诉我无法从这个线程修改集合。我也尝试过BeginInvoke,但我认为这也行不通,因为它无论如何都会从另一个线程启动。
混淆。如果我错过了一些关于互联网上发布的非常明显的内容,请继续给我一个口头抨击,我可能应该得到它。
编辑 - 我可能还应该补充一点,我正在寻找除了计时器或基于BackgroundWorker的解决方案以外的其他解决方案,除非这是唯一的方式在WPF中解决这个问题/ MVVM。另外,我想知道是否有任何MVVM工具包已经有了这类工具......
答案 0 :(得分:8)
如果要将后台线程中的某些工作安排到WPF中的UI线程,请使用DispatcherObject
。这是一篇关于如何Build More Responsive Apps with the Dispatcher的好文章。
更新:请注意,如果您使用事件将模型中的通知发送到ViewModel,则仍需要切换到某处的UI线程。该开关应该在模型中还是ViewModel中是一个很好的设计讨论,但它与您的问题是正交的。
将在相应的Dispatcher线程上引发该事件。由于您需要访问UI线程,因此需要使用在UI线程上创建的Dispatcher。获得一个的最简单方法是在其中一个UI元素上使用DispatcherObject.Dispatcher
属性。另一种方法是在Model或ViewModel中创建一个。如果您是设计纯粹主义者,我建议您在模型中创建Dispatcher,并在引发ViewModel正在侦听的事件之前将调用分派回UI线程。这样,所有线程切换和管理都包含在您的Model中,而ViewModel仅作为UI线程上的单线程工作。
答案 1 :(得分:4)
我认为你的ViewModel真的不应该知道关于View的任何信息,包括它是否是WPF UI,或者该UI是否具有Dispatcher线程的概念,所以红旗应该尽快飞行当您开始在ViewModel中编写代码时,会尝试CheckAccess()或InvokeRequired,以便将一些代码封送到UI线程。相反,我会让模型引发一个View可以监听并相应更新自身的事件,或者让ViewModel公开一个View简单绑定的属性(例如bool FileIsLoading),以便检测并显示模型是什么异步执行,ViewModel负责确保该属性的值是准确的。
例如:
public partial class MainWindow : Window {
private ViewModel _model = new ViewModel();
public MainWindow() {
InitializeComponent();
DataContext = _model;
}
private void Button_Click(object sender, RoutedEventArgs e) {
_model.Run();
}
}
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Click="Button_Click"
Content="Run"
IsEnabled="{Binding IsIdle}" />
</Grid>
</Window>
public class ViewModel : INotifyPropertyChanged {
private bool _isIdle = true;
public bool IsIdle {
get { return _isIdle; }
set {
_isIdle = value;
OnPropertyChanged("IsIdle");
}
}
public void Run() {
ThreadPool.QueueUserWorkItem((state) => {
IsIdle = false;
Thread.Sleep(10000);
IsIdle = true;
});
}
#region INotifyPropertyChanged Implementation
protected void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if (propertyChanged != null) {
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
答案 2 :(得分:1)
我有另一种似乎有效的方法,我只是想把它扔出去得到一些评论(如果有人甚至再读这个问题了!)。
我开始使用MVVM Light Toolkit的Messenger类,它似乎对我来说非常好用。例如,我们以ProgressBar为例。我在ViewModel中注册了两条消息,用于设置进度值和进度最大值。然后在我的模型中,当它设置任务和整个过程时,它会发送这些消息。当VM收到消息时,它只是更新数据绑定值,我的GUI会自动更新!这很容易,但我想知道你们对这种方法的看法。是否有其他人这样做而没有发生事故?