我应该能够访问属于View的Dispatcher,我需要将其传递给ViewModel。但是View应该对ViewModel一无所知,那么如何传递它呢?引入接口还是将其传递给实例,而不是创建将由View编写的全局调度程序单例?您如何在MVVM应用程序和框架中解决此问题?
编辑:请注意,由于我的ViewModels可能是在后台线程中创建的,所以我不能只Dispatcher.Current
在ViewModel的构造函数中做。
我应该能够访问属于View的Dispatcher,我需要将其传递给ViewModel。但是View应该对ViewModel一无所知,那么如何传递它呢?引入接口还是将其传递给实例,而不是创建将由View编写的全局调度程序单例?您如何在MVVM应用程序和框架中解决此问题?
编辑:请注意,由于我的ViewModels可能是在后台线程中创建的,所以我不能只Dispatcher.Current
在ViewModel的构造函数中做。
Answers:
我已经使用接口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);
}
}
你为什么不使用
System.Windows.Application.Current.Dispatcher.Invoke(
(Action)(() => {ObservableCollectionMemeberOfVM.Add("xx"); } ));
而不是保留对GUI调度程序的引用。
您可能实际上并不需要调度程序。如果将视图模型上的属性绑定到视图中的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线程的调用序列化。
我得到ViewModel来将当前调度程序存储为成员。
如果ViewModel是由视图创建的,则您知道在创建时当前的调度程序将是View的调度程序。
class MyViewModel
{
readonly Dispatcher _dispatcher;
public MyViewModel()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
}
从MVVM Light 5.2开始,该库现在DispatcherHelper
在GalaSoft.MvvmLight.Threading
名称空间中包含一个类,该类公开了一个CheckBeginInvokeOnUI()
接受委托并在UI线程上运行该委托的函数。如果ViewModel运行的是一些工作线程,这些工作线程会影响与UI元素绑定的VM属性,则非常方便。
DispatcherHelper
必须DispatcherHelper.Initialize()
在应用程序生命周期的早期阶段(例如App_Startup
)进行调用来初始化。然后,您可以使用以下调用来运行任何委托(或lambda):
DispatcherHelper.CheckBeginInvokeOnUI(
() =>
{
//Your code here
});
请注意,该类在GalaSoft.MvvmLight.Platform
库中定义,当您通过NuGet添加它时,默认情况下未引用该类。您必须手动添加对此库的引用。
DispatcherHelper
仅用于封送您对UI线程的调用。您可以在辅助线程完成工作之后使用它。它不会神奇地减少加载时间,也不会以某种方式使DataGrid
加载数据更快。它只是摆脱了臭名昭著的“对UI的非法跨线程调用”异常。为了提高UI响应速度,请查看async
/await
模式以及WPF中的虚拟化支持DataGrid
。
ListBox
。如果必须使用DataGrid
,请使用某种分页来最小化一次需要显示的数据量。
另一个常见的模式(目前在框架中使用很多)是SynchronizationContext。
它使您可以同步和异步调度。您还可以在当前线程上设置当前SynchronizationContext,这意味着它很容易被嘲笑。WPF应用程序使用DispatcherSynchronizationContext。WCF和WF4使用SynchronizationContext的其他实现。
从WPF 4.5版开始,可以使用CurrentDispatcher
Dispatcher.CurrentDispatcher.Invoke(() =>
{
// Do GUI related operations here
}, DispatcherPriority.Normal);
如果只需要调度程序来修改另一个线程中的绑定集合,请在此处查看SynchronizationContextCollection http://kentb.blogspot.com/2008/01/cross-thread-collection-binding-in-wpf.html
效果很好,我发现的唯一问题是将带有SynchronizationContextCollection属性的视图模型与ASP.NET同步上下文一起使用时,但很容易解决。
山姆
嗨,也许我为时已晚,距离您发布第一篇文章已有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;
这对我的申请有所帮助。也许它也可以帮助您...
您无需将UI Dispatcher传递给ViewModel。可从当前应用程序单例中获得UI Dispatcher。
App.Current.MainWindow.Dispatcher
这将使您的ViewModel依赖于View。根据您的应用程序,可能会也可能不会。
WPF和Windows应用商店应用程序使用:-
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() => {ObservableCollectionMemeberOfVM.Add("xx"); } ));
保持对GUI调度程序的引用并不是正确的方法。
如果不起作用(例如Windows Phone 8应用),请使用:-
Deployment.Current.Dispatcher
我找到了另一种(最简单的)方法:
添加以查看应该在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) ;
}
现在您对测试没有问题,并且易于实现。我已将其添加到我的网站
也许我对这个讨论有点晚了,但是我发现了一篇不错的文章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。
假设您可以正确使用异步/等待,这不是问题。