如何将UI Dispatcher传递给ViewModel


74

我应该能够访问属于View的Dispatcher,我需要将其传递给ViewModel。但是View应该对ViewModel一无所知,那么如何传递它呢?引入接口还是将其传递给实例,而不是创建将由View编写的全局调度程序单例?您如何在MVVM应用程序和框架中解决此问题?

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

Answers:


47

我已经使用接口IContext抽象了Dispatcher :

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

这样的好处是您可以更轻松地对ViewModel进行单元测试。
我使用MEF(托管扩展框架)将接口注入到ViewModels中。另一种可能是构造函数参数。但是,我更喜欢使用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);
    }
}

2
您能否提供wpf UserControl中的实现示例?
Femaref 2010年

3
是的,可以在实施过程中提供任何示例吗?谢谢。
K2so

44

你为什么不使用

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

而不是保留对GUI调度程序的引用。


1
Strangly Dispatcher.CurrentDispatcher对我不起作用。Application.Current.Dispatcher工作了。
Abhijeet Nagre

19

您可能实际上并不需要调度程序。如果将视图模型上的属性绑定到视图中的GUI元素,则WPF绑定机制会使用分派器自动将GUI更新编组到GUI线程。


编辑:

此编辑是对Isak Savo的评论的回应。

在Microsoft处理绑定属性的代码中,您将找到以下代码:

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()
Jobi Joy 2010年

我知道。当然,通常仍需考虑常规的广告处理。
雅各布·克里斯滕森

3
当前,仅出于将项目添加到ObservableCollection的目的,我们需要调度程序。
bitbonk 2010年

3
嗨,Isak。您正确理解我,但您错了。如果通过Microsoft的WPF绑定代码进行调试(或使用Reflector查看),您将看到该代码检查您是否在GUI线程上,如果不是,则将使用Dispatcher在GUI线程上进行更新。我不知道它是否适用于ObservableCollection,但适用于“常规”属性。我为此写了一个博客文章(尽管是丹麦文)。在博客条目的底部显示了Microsoft的代码:dotninjas.dk/post/Flere-trade-og-binding-i-WPF.aspx
Jakob Christensen 2010年

1
雅各布:你是完全正确的。我将-1改为+1。我在.net 3.5和4.0中都尝试过,在后台线程上引发PropertyChanged似乎是完全可以的。
Isak Savo

15

我得到ViewModel来将当前调度程序存储为成员。

如果ViewModel是由视图创建的,则您知道在创建时当前的调度程序将是View的调度程序。

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

在我的场景中,ViewModels是在线程中创建的。这就是为什么我首先问的原因。
bitbonk 2010年

5
但是对于单元测试是有问题的。您如何编写此类代码并对VM进行单元测试?
Ray Booysen 2010年


是的,DispatcherFrame使您的单元测试笨拙而又难以编写。为什么不将其抽象化呢?
Ray Booysen 2011年

7

从MVVM Light 5.2开始,该库现在DispatcherHelperGalaSoft.MvvmLight.Threading名称空间中包含一个类,该类公开了一个CheckBeginInvokeOnUI()接受委托并在UI线程上运行该委托的函数。如果ViewModel运行的是一些工作线程,这些工作线程会影响与UI元素绑定的VM属性,则非常方便。

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

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

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


使用DispatcherHelper.CheckBeginInvokeOnUI,但datagrid加载大量数据,然后UI挂起,为什么?
Ghotekar Rahul

DispatcherHelper仅用于封送您对UI线程的调用。您可以在辅助线程完成工作之后使用它。它不会神奇地减少加载时间,也不会以某种方式使DataGrid加载数据更快。它只是摆脱了臭名昭著的“对UI的非法跨线程调用”异常。为了提高UI响应速度,请查看async/await模式以及WPF中的虚拟化支持DataGrid
dotNET

使用可观察的集合,Datagrid加载数据的速度非常慢
Ghotekar Rahul

不要同时在网格中加载数千行。另外,如果您不需要单元格级别的编辑,最好使用standard ListBox。如果必须使用DataGrid,请使用某种分页来最小化一次需要显示的数据量。
dotNET

顺便说一句,如果您需要特殊情况的帮助,请发布一个新问题。评论仅应与发布的问题或答案相关,或者在某些方面有所改进。
dotNET

5

另一个常见的模式(目前在框架中使用很多)是SynchronizationContext

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


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

3

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

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

}, DispatcherPriority.Normal); 

1
此单例在多线程方案中效果不佳。
bitbonk


1

嗨,也许我为时已晚,距离您发布第一篇文章已有8个月了……我在Silverlight mvvm应用程序中也遇到了同样的问题。我找到了这样的解决方案。对于我拥有的每个模型和视图模型,我也有一个称为控制器的类。像那样

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

我的MainController负责模型和视图模型之间的命令和连接。在构造函数中,我实例化视图及其视图模型,并将视图的数据上下文设置为其视图模型。

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

//(按照我的命名约定,我为成员变量添加了前缀m)

我的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;

这对我的申请有所帮助。也许它也可以帮助您...


1

您无需将UI Dispatcher传递给ViewModel。可从当前应用程序单例中获得UI Dispatcher。

App.Current.MainWindow.Dispatcher

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


1

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

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

保持对GUI调度程序的引用并不是正确的方法。

如果不起作用(例如Windows Phone 8应用),请使用:-

       Deployment.Current.Dispatcher

0

如果您使用过uNhAddIns,则可以轻松地进行非常行为。在这里看看

而且我认为需要进行一些修改才能使其在温莎城堡上运行(不带uNhAddIns)


0

我找到了另一种(最简单的)方法:

添加以查看应该在Dispatcher中调用的模型动作:

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

现在您对测试没有问题,并且易于实现。我已将其添加到我的网站


0

当您可以使用来访问应用程序调度程序时,无需通过调度程序

Dispatcher dis = Application.Current.Dispatcher 

-1

我在WPF的某些项目中也遇到过同样的情况。在我的MainViewModel(Singleton实例)中,我得到了CreateInstance()静态方法作为调度程序。然后从View调用create实例,以便我可以从那里传递Dispatcher。并且ViewModel测试模块无参数调用CreateInstance()。

但是在复杂的多线程方案中,最好在View端具有接口实现,以获取当前Window的正确Dispatcher。


-1

也许我对这个讨论有点晚了,但是我发现了一篇不错的文章https://msdn.microsoft.com/en-us/magazine/dn605875.aspx

有1段

此外,View层之外的所有代码(即ViewModel和Model层,服务等)都不应依赖于绑定到特定UI平台的任何类型。直接使用Dispatcher(WPF / Xamarin / Windows Phone / Silverlight),CoreDispatcher(Windows应用商店)或ISynchronizeInvoke(Windows窗体)都是不好的主意。(SynchronizationContext稍好一些,但几乎没有。)例如,Internet上有很多代码可以执行一些异步工作,然后使用Dispatcher来更新UI。一个更可移植,更不麻烦的解决方案是使用await进行异步工作并在不使用Dispatcher的情况下更新UI。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.