如何将UI调度程序传递给ViewModel

我应该能够访问属于视图我需要将其传递给ViewModel的分派器 。 但视图不应该了解任何有关ViewModel,所以你怎么通过它? 引入一个接口,而不是将它传递给实例创build一个全局的调度程序单身人士,将由视图? 你如何在你的MVVM应用程序和框架中解决这个问题?

编辑:请注意,因为我的ViewModels可能在后台线程中创build,我不能只是在ViewModel的构造函数中执行Dispatcher.Current

我使用接口IContext抽象了Dispatcher:

 public interface IContext { bool IsSynchronized { get; } void Invoke(Action action); void BeginInvoke(Action action); } 

这样做的好处是你可以更容易地对你的ViewModel进行unit testing。
我使用MEF(托pipe扩展框架)将接口注入到ViewModel中。 另一个可能性是一个构造函数的论点。 不过,我更喜欢使用MEF进行注射。

更新(来自评论中的pastebin链接的示例):

 public sealed class WpfContext : IContext { private readonly Dispatcher _dispatcher; public bool IsSynchronized { get { return this._dispatcher.Thread == Thread.CurrentThread; } } public WpfContext() : this(Dispatcher.CurrentDispatcher) { } public WpfContext(Dispatcher dispatcher) { Debug.Assert(dispatcher != null); this._dispatcher = dispatcher; } public void Invoke(Action action) { Debug.Assert(action != null); this._dispatcher.Invoke(action); } public void BeginInvoke(Action action) { Debug.Assert(action != null); this._dispatcher.BeginInvoke(action); } } 

你为什么不使用

  System.Windows.Application.Current.Dispatcher.Invoke( (Action)(() => {ObservableCollectionMemeberOfVM.Add("xx"); } )); 

而不是保持对GUI调度器的引用。

你可能不需要调度员。 如果将视图模型上的属性绑定到视图中的GUI元素,则WPF绑定机制会使用调度程序自动将GUI更新整理到GUI线程。


编辑:

这个编辑是为了回应Isak Savo的评论。

在微软处理绑定到属性的代码中,你会发现下面的代码:

 if (Dispatcher.Thread == Thread.CurrentThread) { PW.OnPropertyChangedAtLevel(level); } else { // otherwise invoke an operation to do the work on the right context SetTransferIsPending(true); Dispatcher.BeginInvoke( DispatcherPriority.DataBind, new DispatcherOperationCallback(ScheduleTransferOperation), new object[]{o, propName}); } 

此代码将任何UI更新编组到线程UI线程,以便即使更新属性作为来自不同线程的绑定的一部分,WPF也会自动将调用序列化到UI线程。

我得到的ViewModel存储当前调度作为成员。

如果ViewModel是由视图创build的,则知道创build时的当前调度器将是View的调度器。

 class MyViewModel { readonly Dispatcher _dispatcher; public MyViewModel() { _dispatcher = Dispatcher.CurrentDispatcher; } } 

从MVVM Light 5.2开始,库现在在GalaSoft.MvvmLight.Threading命名空间中包含一个DispatcherHelper类,它公开一个接受委托并在UI线程上运行的函数CheckBeginInvokeOnUI() 。 如果您的ViewModel正在运行一些影响您的UI元素所绑定到的VM属性的工作线程,则非常方便。

DispatcherHelper必须在应用程序生命周期的早期阶段(例如App_Startup )通过调用DispatcherHelper.Initialize()来初始化。 然后可以使用以下调用运行任何委托(或lambda):

 DispatcherHelper.CheckBeginInvokeOnUI( () => { //Your code here }); 

请注意,该类是在GalaSoft.MvvmLight.Platform库中定义的,当您通过NuGet添加它时,它在默认情况下不会被引用。 您必须手动添加对此库的引用。

另一个常见的模式(现在在框架中看到了很多使用)是SynchronizationContext 。

它使您能够同步和asynchronous分派。 您也可以在当前线程上设置当前的SynchronizationContext,这意味着它很容易被模拟。 DispatcherSynchronizationContext由WPF应用程序使用。 WCF和WF4使用SynchronizationContext的其他实现。

如果您只需要调度程序在另一个线程中修改绑定集合,请查看此处的SynchronizationContextCollection http://kentb.blogspot.com/2008/01/cross-thread-collection-binding-in-wpf.html

工作得很好,只有我发现的问题是使用带有ASP.NET同步上下文SynchronizationContextCollection属性的视图模型,但很容易解决。

HTH山姆

嗨,也许我太晚了,因为它已经8个月以来,你的第一篇文章…我有同样的问题在silverlight mvvm申请。 我发现我的解决scheme是这样的。 对于我有的每个模型和视图模型,我也有一个类叫做控制器。 像那样

 public class MainView : UserControl // (because it is a silverlight user controll) public class MainViewModel public class MainController 

我的MainController负责模型和视图模型之间的命令和连接。 在构造函数中,我实例化view和viewmodel,并将view的datacontext设置为viewmodel。

 mMainView = new MainView(); mMainViewModel = new MainViewModel(); mMainView.DataContext = mMainViewModel; 

//(在我的命名约定中,我有一个前缀m的成员variables)

我也有我的MainViewtypes的公共属性。 像那样

 public MainView View { get { return mMainView; } } 

(这个mMainView是公共属性的局部variables)

现在我完成了。 我只需要使用我的调度员为我的ui therad像这样…

 mMainView.Dispatcher.BeginInvoke( () => MessageBox.Show(mSpWeb.CurrentUser.LoginName)); 

(在这个例子中,我问我的控制器得到我的SharePoint 2010的login名,但你可以做你的需要)

我们差不多完成了,你还需要像这样在app.xaml中定义你的根视觉

 var mainController = new MainController(); RootVisual = mainController.View; 

这帮助了我的申请。 也许它也可以帮助你…

您不需要将UI调度程序传递给ViewModel。 UI调度程序可从当前的应用程序单例中获得。

 App.Current.MainWindow.Dispatcher 

这将使您的ViewModel依赖于视图。 根据您的申请,这可能会或可能不会很好。

为WPF和Windows商店应用程序使用: –

  System.Windows.Application.Current.Dispatcher.Invoke((Action)(() => {ObservableCollectionMemeberOfVM.Add("xx"); } )); 

保持对GUI调度器的参考是不正确的。

如果这不起作用(例如在Windows Phone 8应用程序的情况下),然后使用: –

  Deployment.Current.Dispatcher 

如果您使用uNhAddIns ,则可以轻松实现asynchronous行为。 看看这里

我想需要一些修改,使其在温莎城堡(没有uNhAddIns)

我find了另一种(最简单的)方式:

添加到查看应在调度程序中调用的模型操作:

 public class MyViewModel { public Action<Action> CallWithDispatcher; public void SomeMultithreadMethod() { if(CallWithDispatcher != null) CallWithDispatcher(() => DoSomethingMetod(SomeParameters)); } } 

并添加此视图构造函数中的动作处理程序:

  public View() { var model = new MyViewModel(); DataContext = model; InitializeComponent(); // Here model.CallWithDispatcher += act => _taskbarIcon.Dispatcher .BeginInvoke(DispatcherPriority.Normal, act) ; } 

现在你没有testing的问题,而且很容易实现。 我已经添加到我的网站

从WPF版本4.5开始,可以使用CurrentDispatcher

 Dispatcher.CurrentDispatcher.Invoke(() => { // Do GUI related operations here }, DispatcherPriority.Normal); 

也许我对这个讨论有点迟,但是我发现了一篇很好的文章https://msdn.microsoft.com/zh-cn/magazine/dn605875.aspx

有1段

此外,View层以外的所有代码(即ViewModel和Model层,服务等)不应依赖于绑定到特定UI平台的任何types。 Dispatcher(WPF / Xamarin / Windows Phone / Silverlight),CoreDispatcher(Windows Store)或ISynchronizeInvoke(Windows Forms)的任何直接使用都是一个坏主意。 (SynchronizationContext稍微好一点,但几乎没有。)例如,Internet上有很多代码执行一些asynchronous工作,然后使用Dispatcher更新UI; 一个更便携,更麻烦的解决scheme是使用等待asynchronous工作和更新UI而不使用分派器。

假设您可以正确使用asynchronous/等待,这不是一个问题。

我的一些WPF项目面临着同样的情况。 在我的MainViewModel(单例实例),我得到我的CreateInstance()静态方法采取调度。 并且从View中调用创build实例,以便我可以从那里传递Dispatcher。 ViewModeltesting模块调用CreateInstance()无参数。

但是在一个复杂的multithreading场景中,在View端有一个接口实现总是好的,以便获得当前窗口的合适Dispatcher。