2010-03-01 58 views
60

我应该能够访问属于视图的Dispatcher我需要将它传递给ViewModel。但视图不应该了解任何有关ViewModel的内容,那么你如何通过它?引入一个接口,或者不是将它传递给实例,而是创建一个将由View编写的全局调度器单例?你如何在你的MVVM应用程序和框架中解决这个问题?如何将UI调度程序传递给ViewModel

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

回答

40

我一直在使用的接口IContext抽象的调度。
我使用MEF(托管扩展框架)将接口注入到ViewModels中。另一种可能性是构造参数。 但是,我更喜欢使用MEF进行注射。

更新(例如从引擎收录链接在评论):

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); 
    } 
} 
+1

你能否提供一个wpf UserControl实现的例子? – Femaref 2010-06-10 09:37:38

+2

是的,可以提供有关实施的任何示例?谢谢。 – K2so 2010-11-01 13:46:35

+2

比从未更好地迟到;-)尝试以下方法:http://pastebin.com/eXTQf9vm – Matthias 2011-07-10 10:03:57

12

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

如果ViewModel是由视图创建的,那么您知道创建时的当前调度器将是View的调度器。

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

这样做的好处是可以更轻松地单元测试你的ViewModels:

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

在我的场景中,ViewModels是在线程中创建的。这就是我首先要问的原因。 – bitbonk 2010-03-01 07:57:29

+4

但单元测试存在问题。你如何编写这种代码和单元测试你的虚拟机? – 2010-03-01 16:52:55

+0

@Roy:看到这个问题http://stackoverflow.com/questions/1106881/using-the-wpf-dispatcher-in-unit-tests/1303762#1303762 – 2010-03-01 21:54:53

18

您可能实际上并不需要的调度。如果将viewmodel上的属性绑定到视图中的GUI元素,那么WPF绑定机制会自动使用调度程序将GUI更新整理到GUI线程。


编辑:

此编辑为响应伊萨克萨沃的评论。

微软内部的处理结合属性,你会发现下面的代码代码:

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线程。

+9

但是有很多情况,您可能需要这样做,想象一下绑定到UI的ObservableCollection,并且您试图调用_collection .Add()来自工作线程 – 2010-03-01 08:58:13

+0

我知道。当然,通常的考虑因素仍然适用。 – 2010-03-01 09:01:46

+3

目前,我们只需将调度程序添加到ObservableCollection中即可。 – bitbonk 2010-03-01 09:25:02

-1

我的一些WPF项目面临同样的情况。在我的MainViewModel(Singleton实例)中,我得到了我的CreateInstance()静态方法需要调度器。然后创建实例从视图中调用,以便我可以从那里传递分派器。 而ViewModel测试模块调用CreateInstance()无参数。

但是在复杂的多线程场景中,在View端有一个接口实现总是好的,以便获得当前窗口的正确Dispatcher。

0

如果你习惯uNhAddIns,你可以很容易做出的 - 异步行为。看看here

,我觉得需要一些修改,以使其在温莎城堡进行工作(无uNhAddIns)

1

喜也许我太晚了,因为它已经因为你的第一篇文章已有8个月〜 我在silverlight mvvm应用程序中遇到同样的问题。我发现我的解决方案是这样的。对于我有的每个模型和视图模型,我也有一个叫做控制器的类。 像

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

我MainController分管指挥和模型视图模型和之间的连接。在构造函数中,实例化视图及其视图模型,并将视图的datacontext设置为其视图模型。

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

//(在我的命名约定我有成员变量的前缀米)

我也有我的MainView的类型的公共属性。这样

public MainView View { get { return mMainView; } } 

(这mMainView是对公共财产的局部变量)

,现在我做。我只需要使用我的调度员对我的UI therad这样的...

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

(在这个例子中我是问我的控制器,让我的SharePoint 2010的登录名,但你可以做什么你的需要)

我们几乎做了你还需要定义你的根在App.xaml中视这样

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

帮我通过我的应用程序。也许它可以帮助你太...

4

另一种常见的模式(也就是现在看到的框架多大用处)为SynchronizationContext

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

+2

这是我认为最好也是最简单的方法。您可以将ViewModel的SynchronizationContext默认为创建ViewModel的线程当前上下文,并且如果UserControl需要更改它,则可以随意更改它。 – ken 2012-04-16 16:49:18

0

我已经找到另一个(最简单的)方式:

添加到浏览模式的行动,就是要在调度员可拨打:

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) ; 
    } 

现在你没有测试问题,而且很容易实现。 我已将它添加到我的site

1

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

App.Current.MainWindow.Dispatcher 

这将使您的ViewModel依赖于视图。根据您的应用程序,可能会也可能不会。

+0

MainWindow可能为null。 – Cologler 2015-06-02 01:42:54

26

你为什么不使用

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

,而不是保持参照GUI调度。

1

WPF和Windows应用商店的应用程序使用: -

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

保存参考GUI调度员是不是真的正确的方式。

如果不工作(例如在窗口电话8个应用的情况下),然后使用: -

 Deployment.Current.Dispatcher 
6

作为MVVM灯5.2的,库现在包括一个DispatcherHelperGalaSoft.MvvmLight.Threading命名空间暴露一个函数CheckBeginInvokeOnUI(),它接受一个委托并在UI线程上运行它。如果您的ViewModel正在运行一些影响您的UI元素所绑定的VM属性的工作线程,则非常方便。

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

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

注意类是当你通过的NuGet添加它默认不引用GalaSoft.MvvmLight.Platform库中定义。您必须手动添加对此库的引用。

0

从WPF版本4开始。5可以使用CurrentDispatcher

Dispatcher.CurrentDispatcher.Invoke(() => 
{ 
    // Do GUI related operations here 

}, DispatcherPriority.Normal); 
+0

这个单例在多线程场景中不能很好地工作。 – bitbonk 2016-05-17 15:18:58

0

也许我有点晚了这次讨论,但我发现1好文章https://msdn.microsoft.com/en-us/magazine/dn605875.aspx

有1款

此外,查看以外的所有代码图层(即ViewModel 和模型图层,服务等)不应取决于绑定到特定UI平台的任何类型 。任何直接使用Dispatcher (WPF/Xamarin/Windows Phone/Silverlight),CoreDispatcher(Windows 存储)或ISynchronizeInvoke(Windows窗体)是一个坏主意。 (SynchronizationContext略微好一点,但几乎没有。)对于 示例,Internet上有很多代码用于执行一些异步工作,然后使用Dispatcher更新UI;更多的 便携式和不太麻烦的解决方案是使用等待异步 工作和更新UI而不使用分派器。

假设您可以正确使用异步/等待,这不是问题。