如何将 UI Dispatcher 传递给 ViewModel
Posted
技术标签:
【中文标题】如何将 UI Dispatcher 传递给 ViewModel【英文标题】:How to pass the UI Dispatcher to the ViewModel 【发布时间】:2011-01-22 04:54:02 【问题描述】:我应该能够访问属于我需要将其传递给 ViewModel 的视图的Dispatcher。但是View应该对ViewModel一无所知,那怎么传呢?引入一个接口还是不将其传递给实例,而是创建一个将由 View 编写的全局调度程序单例?您如何在 MVVM 应用程序和框架中解决这个问题?
编辑:请注意,由于我的 ViewModel 可能是在后台线程中创建的,因此我不能只在 ViewModel 的构造函数中执行 Dispatcher.Current
。
【问题讨论】:
【参考方案1】:我已经使用接口 IContext 抽象了 Dispatcher:
public interface IContext
bool IsSynchronized get;
void Invoke(Action action);
void BeginInvoke(Action action);
这样做的好处是您可以更轻松地对 ViewModel 进行单元测试。 我使用 MEF(托管可扩展性框架)将接口注入到我的 ViewModel 中。另一种可能性是构造函数参数。 但是,我更喜欢使用 MEF 进行注射。
更新(来自 cmets 中的 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);
【讨论】:
您能否提供一个 wpf UserControl 中的实现示例? 是的,可以提供任何关于实现的示例吗?谢谢。【参考方案2】:你为什么不使用
System.Windows.Application.Current.Dispatcher.Invoke(
(Action)(() => ObservableCollectionMemeberOfVM.Add("xx"); ));
而不是保留对 GUI 调度程序的引用。
【讨论】:
奇怪的 Dispatcher.CurrentDispatcher 对我不起作用。 Application.Current.Dispatcher 工作。【参考方案3】:您可能实际上并不需要调度程序。如果将视图模型上的属性绑定到视图中的 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 线程的调用。
【讨论】:
但在很多情况下,您可能需要这样做,想象一下绑定到 UI 的 ObservableCollection 并且您尝试从工作线程调用 _collection.Add() 我知道。当然,通常的主题注意事项仍然适用。 目前我们需要调度程序来将项目添加到 ObservableCollection。 嗨,伊萨克。你正确地理解了我,但你错了。如果您通过 Microsoft 的 WPF 绑定代码进行调试(或使用 Reflector 查看它),您将看到代码检查您是否在 GUI 线程上,如果不在,它将使用 Dispatcher 在 GUI 线程上更新。我不知道它是否适用于 ObservableCollection 但它适用于“正常”属性。我为此写了一篇博客文章(虽然是丹麦语)。在博客条目的底部显示了微软的代码:dotninjas.dk/post/Flere-trade-og-binding-i-WPF.aspx 雅各布:你完全正确。我已将我的 -1 修改为 +1。我在 .net 3.5 和 4.0 中都试过了,似乎在后台线程上引发 PropertyChanged 是完全可以的。【参考方案4】:我让 ViewModel 将当前调度程序存储为成员。
如果 ViewModel 是由视图创建的,则您知道在创建时当前的调度程序将是视图的调度程序。
class MyViewModel
readonly Dispatcher _dispatcher;
public MyViewModel()
_dispatcher = Dispatcher.CurrentDispatcher;
【讨论】:
在我的场景中,ViewModel 是在线程中创建的。这就是我一开始问的原因。 但是单元测试有问题。你如何编写这种代码并对你的虚拟机进行单元测试? @Roy:看到这个问题***.com/questions/1106881/… 是的,DispatcherFrame 使您的单元测试变得笨拙且难以编写。为什么不把它抽象掉呢?【参考方案5】:从 MVVM Light 5.2 开始,该库现在在 GalaSoft.MvvmLight.Threading
命名空间中包含一个 DispatcherHelper
类,该类公开了一个函数 CheckBeginInvokeOnUI()
,该函数接受一个委托并在 UI 线程上运行它。如果您的 ViewModel 正在运行一些影响您的 UI 元素绑定到的 VM 属性的工作线程,则非常方便。
DispatcherHelper
必须在应用程序生命周期的早期阶段通过调用DispatcherHelper.Initialize()
来初始化(例如App_Startup
)。然后,您可以使用以下调用运行任何委托(或 lambda):
DispatcherHelper.CheckBeginInvokeOnUI(
() =>
//Your code here
);
请注意,该类是在GalaSoft.MvvmLight.Platform
库中定义的,当您通过 NuGet 添加它时,默认情况下不会引用该库。您必须手动添加对此库的引用。
【讨论】:
DispatcherHelper.CheckBeginInvokeOnUI 已使用但 datagrid 加载大量数据然后 UI 挂起为什么?DispatcherHelper
仅用于将您的调用编组到 UI 线程。在工作线程完成工作后使用它。它不会神奇地减少加载时间,也不会以某种方式使DataGrid
加载数据更快。它只是摆脱了臭名昭著的“对 UI 的非法跨线程调用”异常。为了提高您的 UI 响应能力,请查看 async
/await
模式以及 WPF DataGrid
中的虚拟化支持。
Datagrid 使用 observable 集合加载数据非常慢
不要同时在网格中加载数千行。此外,如果您不需要单元格级别的编辑,您最好不要使用标准的ListBox
。如果您必须使用DataGrid
,请使用某种分页来最大限度地减少一次需要显示的数据量。
顺便说一句,如果您需要针对特定场景的帮助,请发布一个新问题。评论只应与发布的问题或答案相关,或在某些方面使其更好。【参考方案6】:
另一个常见的模式(现在在框架中得到了广泛的使用)是SynchronizationContext。
它使您能够同步和异步调度。您还可以在当前线程上设置当前 SynchronizationContext,这意味着它很容易被模拟。 WPF 应用程序使用 DispatcherSynchronizationContext。 SynchronizationContext 的其他实现由 WCF 和 WF4 使用。
【讨论】:
我认为这是最好和最简单的方法。您可以默认 ViewModel 的 SynchronizationContext 为创建 ViewModel 的线程的当前上下文,如果 UserControl 需要更改它,他们可以随意更改它。【参考方案7】:从 WPF 4.5 版开始,可以使用CurrentDispatcher
Dispatcher.CurrentDispatcher.Invoke(() =>
// Do GUI related operations here
, DispatcherPriority.Normal);
【讨论】:
这个单例在多线程场景中效果不是很好。【参考方案8】:如果您只需要调度程序来修改另一个线程中的绑定集合,请查看这里的 SynchronizationContextCollection http://kentb.blogspot.com/2008/01/cross-thread-collection-binding-in-wpf.html
效果很好,我发现的唯一问题是当使用带有 ASP.NET 同步上下文的 SynchronizationContextCollection 属性的视图模型时,但很容易解决。
HTH 山姆
【讨论】:
【参考方案9】:嗨,也许我来得太晚了,因为距离你第一次发帖已经 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;
这对我的申请有所帮助。也许它也可以帮助你......
【讨论】:
【参考方案10】:您不需要将 UI Dispatcher 传递给 ViewModel。 UI Dispatcher 可从当前应用程序单例中获得。
App.Current.MainWindow.Dispatcher
这将使您的 ViewModel 依赖于 View。根据您的应用程序,这可能会也可能不会。
【讨论】:
【参考方案11】:对于 WPF 和 Windows 商店应用使用:-
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() => ObservableCollectionMemeberOfVM.Add("xx"); ));
保留对 GUI 调度程序的引用并不是真正正确的方法。
如果这不起作用(例如在 windows phone 8 应用程序的情况下),请使用:-
Deployment.Current.Dispatcher
【讨论】:
【参考方案12】:如果您使用uNhAddIns,您可以轻松地进行异步行为。看看here
我认为需要进行一些修改才能使其在温莎城堡上运行(没有 uNhAddIns)
【讨论】:
【参考方案13】:我找到了另一种(最简单的)方法:
添加到应在 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) ;
现在测试没有问题,而且很容易实现。 我已将其添加到我的site
【讨论】:
【参考方案14】:当您可以使用应用程序调度程序访问时,无需传递调度程序
Dispatcher dis = Application.Current.Dispatcher
【讨论】:
【参考方案15】:我的一些 WPF 项目遇到了同样的情况。在我的 MainViewModel(单例实例)中,我的 CreateInstance() 静态方法采用了调度程序。并且从视图调用创建实例,以便我可以从那里传递调度程序。 并且 ViewModel 测试模块调用 CreateInstance() 无参数。
但在复杂的多线程场景中,在 View 端有一个接口实现总是好的,以便获得当前 Window 的正确 Dispatcher。
【讨论】:
【参考方案16】:也许我这个讨论有点晚了,但我找到了一篇不错的文章https://msdn.microsoft.com/en-us/magazine/dn605875.aspx
有1段
此外,View 层之外的所有代码(即 ViewModel 和模型层、服务等)不应依赖于任何类型 绑定到特定的 UI 平台。任何直接使用 Dispatcher (WPF/Xamarin/Windows Phone/Silverlight)、CoreDispatcher (Windows Store)或 ISynchronizeInvoke(Windows 窗体)是一个坏主意。 (SynchronizationContext 稍微好一点,但几乎没有。)对于 例如,互联网上有很多代码可以做一些 异步工作,然后使用 Dispatcher 更新 UI;一个更 可移植且不那么繁琐的解决方案是使用 await 进行异步 在不使用 Dispatcher 的情况下工作和更新 UI。
假设您可以正确使用 async/await,这不是问题。
【讨论】:
以上是关于如何将 UI Dispatcher 传递给 ViewModel的主要内容,如果未能解决你的问题,请参考以下文章
姜戈。如何将下一个变量传递给 auth_views.login
如何将选项更改传递给 Angular-UI ui-select2?
c#winform不是用this.dispatcher.invoke来异步更新ui吗?
如何使用 styled-components 将 prop 传递给 Material-UI 组件
JSF 2:如何将包含要调用的参数的动作传递给 Facelets 子视图(使用 ui:include 和 ui:param)?