Cómo pasar el Despachador de UI al ViewModel

Se supone que debo poder acceder al Dispatcher que pertenece a la Vista que necesito para pasarlo a ViewModel. Pero View no debería saber nada sobre ViewModel, entonces, ¿cómo lo pasa? Introduzca una interfaz o, en lugar de pasarla a las instancias, cree un singleton de despachador global que la vista escribirá. ¿Cómo resuelves esto en tus aplicaciones y frameworks MVVM?

EDITAR: Tenga en cuenta que dado que mis ViewModels pueden ser creados en hilos de fondo, no puedo hacer Dispatcher.Current en el constructor de ViewModel.

He abstraído el Dispatcher usando una interfaz IContext :

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

Esto tiene la ventaja de que puedes probar tu ViewModels de forma más sencilla.
Inyerto la interfaz en mi ViewModels usando MEF (Managed Extensibility Framework). Otra posibilidad sería un argumento de constructor. Sin embargo, me gusta más la inyección usando MEF.

Actualización (ejemplo del enlace pastebin en los comentarios):

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

por qué no usarías

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

en lugar de mantener la referencia al despachador de GUI.

Es posible que en realidad no necesite el despachador. Si vincula las propiedades de su modelo de vista a los elementos de la GUI en su vista, el mecanismo de enlace de WPF calcula automáticamente las actualizaciones de la GUI al hilo de la GUI utilizando el despachador.


EDITAR:

Esta edición es en respuesta al comentario de Isak Savo.

Dentro del código de Microsoft para manejar el enlace a las propiedades, encontrará el siguiente código:

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

Este código clasifica todas las actualizaciones de UI en el subproceso de la IU de subprocesos, de modo que incluso si actualiza las propiedades que forman parte del enlace de un subproceso diferente, WPF serializará automáticamente la llamada al subproceso de UI.

Obtengo ViewModel para almacenar el despachador actual como miembro.

Si ViewModel es creado por la vista, usted sabe que el despachador actual en el momento de la creación será el despachador de la Vista.

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

A partir de MVVM Light 5.2, la biblioteca ahora incluye una clase DispatcherHelper en GalaSoft.MvvmLight.Threading namespace que expone una función CheckBeginInvokeOnUI() que acepta un delegado y lo ejecuta en el subproceso de la interfaz de usuario. Resulta muy útil si su ViewModel está ejecutando algunos subprocesos de trabajo que afectan a las propiedades de VM a las que están vinculados sus elementos de UI.

DispatcherHelper debe inicializar llamando a DispatcherHelper.Initialize() en una etapa temprana de la vida de su aplicación (por ejemplo, App_Startup ). Luego puede ejecutar cualquier delegado (o lambda) usando la siguiente llamada:

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

Tenga en cuenta que la clase se define en la biblioteca GalaSoft.MvvmLight.Platform que no se hace referencia de manera predeterminada cuando la agrega a través de NuGet. Debe agregar manualmente una referencia a esta lib.

Otro patrón común (que está viendo mucho uso ahora en el marco) es el SynchronizationContext .

Le permite despachar sincrónicamente y de forma asíncrona. También puede configurar el SynchronizationContext actual en el hilo actual, lo que significa que se burla fácilmente. DispatcherSynchronizationContext es utilizado por las aplicaciones de WPF. WF4 y WCF utilizan otras implementaciones de SynchronizationContext.

Si solo necesita que el despachador modifique una colección enlazada en otro hilo, eche un vistazo a SynchronizationContextCollection aquí http://kentb.blogspot.com/2008/01/cross-thread-collection-binding-in-wpf.html

Funciona bien, el único problema que encontré es cuando uso View Models con propiedades SynchronizationContextCollection con contexto de sincronización ASP.NET, pero se solucionó fácilmente.

HTH Sam

hola, tal vez estoy demasiado tarde, ya que han pasado 8 meses desde su primer mensaje … tuve el mismo problema en una aplicación mvvm de Silverlight. y encontré mi solución así. para cada modelo y modelo de vista que tengo, también tengo una clase llamada controlador. como eso

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

mi MainController está a cargo de la comandancia y la conexión entre el modelo y el modelo de vista. en el constructor, instanciamos la vista y su modelo de vista y establecemos el contexto de datos de la vista en su modelo de vista.

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

// (en mi convención de nombres tengo un prefijo m para las variables miembro)

También tengo una propiedad pública en el tipo de mi MainView. como eso

 public MainView View { get { return mMainView; } } 

(este mMainView es una variable local para la propiedad pública)

y ahora estoy hecho. solo necesito usar mi despachador para mi ui therad así …

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

(en este ejemplo, le pedí a mi controlador que obtuviera mi nombre de inicio de sesión de SharePoint 2010 pero puede hacer lo que necesite)

casi hemos terminado, también necesitas definir tu visual raíz en la aplicación.xaml como esta

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

esto me ayudó con mi solicitud. tal vez te pueda ayudar a ti también …

No es necesario que pase el Despachador de UI al ViewModel. El Despachador de UI está disponible desde el singleton de la aplicación actual.

 App.Current.MainWindow.Dispatcher 

Esto hará que su ViewModel dependa de la Vista. Dependiendo de su aplicación, eso puede estar o no estar bien.

para las aplicaciones de WPF y Windows store use:

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

mantener la referencia al despachador de GUI no es realmente la manera correcta.

si eso no funciona (como en el caso de las aplicaciones de Windows Phone 8), utilice:

  Deployment.Current.Dispatcher 

A partir de WPF versión 4.5 uno puede usar CurrentDispatcher

 Dispatcher.CurrentDispatcher.Invoke(() => { // Do GUI related operations here }, DispatcherPriority.Normal); 

si se usa ADHESIVOS , puede realizar un comportamiento asincrónico fácilmente. mira aquí

Y creo que necesito algunas modificaciones para que funcione en Castle Windsor (sin uNhAddIns)

He encontrado otra manera (la más simple):

Agregar para ver la acción del modelo que se debe llamar en Dispatcher:

 public class MyViewModel { public Action CallWithDispatcher; public void SomeMultithreadMethod() { if(CallWithDispatcher != null) CallWithDispatcher(() => DoSomethingMetod(SomeParameters)); } } 

Y agregue este controlador de acción en el constructor de vista:

  public View() { var model = new MyViewModel(); DataContext = model; InitializeComponent(); // Here model.CallWithDispatcher += act => _taskbarIcon.Dispatcher .BeginInvoke(DispatcherPriority.Normal, act) ; } 

Ahora no tienes problemas con las pruebas, y es fácil de implementar. Lo agregué a mi sitio

Tal vez llegue un poco tarde a esta discusión, pero encontré 1 artículo bonito https://msdn.microsoft.com/en-us/magazine/dn605875.aspx

Hay 1 párrafo

Además, todos los códigos que están fuera de la capa de Vista (es decir, las capas de Modelo de Vista y Modelo, servicios, etc.) no deberían depender de ningún tipo vinculado a una plataforma de IU específica. Cualquier uso directo de Dispatcher (WPF / Xamarin / Windows Phone / Silverlight), CoreDispatcher (Windows Store) o ISynchronizeInvoke (Windows Forms) es una mala idea. (SynchronizationContext es marginalmente mejor, pero apenas.) Por ejemplo, hay un montón de código en Internet que realiza un trabajo asincrónico y luego utiliza Dispatcher para actualizar la interfaz de usuario; una solución más portátil y menos engorrosa es usar aguardar para el trabajo asincrónico y actualizar la interfaz de usuario sin usar Dispatcher.

Suponga que puede usar async / await correctamente, esto no es un problema.

Algunos de mis proyectos de WPF me he enfrentado a la misma situación. En mi MainViewModel (instancia de Singleton), obtuve mi método estático CreateInstance () toma el despachador. Y se llama a la instancia de creación desde la Vista para que pueda pasar el Dispatcher desde allí. Y el módulo de prueba ViewModel llama a CreateInstance () sin parámetros.

Pero en un escenario complejo de múltiples subprocesos siempre es bueno tener una implementación de interfaz en el lado de la Vista para obtener el Despachador apropiado de la Ventana actual.