如何将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。