¿Buena o mala práctica para Diálogos en wpf con MVVM?

Últimamente tuve el problema de crear y editar diálogos para mi aplicación wpf.

Todo lo que quiero hacer en mi código es algo como esto. (Uso principalmente el primer enfoque de viewmodel con mvvm)

ViewModel que llama a una ventana de diálogo:

var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM); // Do anything with the dialog result 

¿Como funciona?

Primero, creé un servicio de diálogo:

 public interface IUIWindowDialogService { bool? ShowDialog(string title, object datacontext); } public class WpfUIWindowDialogService : IUIWindowDialogService { public bool? ShowDialog(string title, object datacontext) { var win = new WindowDialog(); win.Title = title; win.DataContext = datacontext; return win.ShowDialog(); } } 

WindowDialog es una ventana especial pero simple. Necesito que contenga mi contenido:

     

Un problema con los diálogos en wpf es que el dialogresult = true del dialogresult = true solo se puede lograr en el código. Es por eso que creé una interfaz para mi dialogviewmodel para implementarlo.

 public class RequestCloseDialogEventArgs : EventArgs { public bool DialogResult { get; set; } public RequestCloseDialogEventArgs(bool dialogresult) { this.DialogResult = dialogresult; } } public interface IDialogResultVMHelper { event EventHandler RequestCloseDialog; } 

Cada vez que mi ViewModel cree que es hora de dialogresult = true , plantee este evento.

 public partial class DialogWindow : Window { // Note: If the window is closed, it has no DialogResult private bool _isClosed = false; public DialogWindow() { InitializeComponent(); this.DialogPresenter.DataContextChanged += DialogPresenterDataContextChanged; this.Closed += DialogWindowClosed; } void DialogWindowClosed(object sender, EventArgs e) { this._isClosed = true; } private void DialogPresenterDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) { var d = e.NewValue as IDialogResultVMHelper; if (d == null) return; d.RequestCloseDialog += new EventHandler (DialogResultTrueEvent).MakeWeak( eh => d.RequestCloseDialog -= eh;); } private void DialogResultTrueEvent(object sender, RequestCloseDialogEventArgs eventargs) { // Important: Do not set DialogResult for a closed window // GC clears windows anyways and with MakeWeak it // closes out with IDialogResultVMHelper if(_isClosed) return; this.DialogResult = eventargs.DialogResult; } } 

Ahora al menos tengo que crear un DataTemplate en mi archivo de recursos ( app.xaml o algo así):

    

Bueno, eso es todo, ahora puedo llamar a los diálogos de mis modelos de vista:

  var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM); 

Ahora mi pregunta, ¿ve algún problema con esta solución?

Editar: para completar. El ViewModel debe implementar IDialogResultVMHelper y luego puede subirlo dentro de un OkCommand o algo como esto:

 public class MyViewmodel : IDialogResultVMHelper { private readonly Lazy _okCommand; public MyViewmodel() { this._okCommand = new Lazy(() => new DelegateCommand(() => InvokeRequestCloseDialog( new RequestCloseDialogEventArgs(true)), () => YourConditionsGoesHere = true)); } public ICommand OkCommand { get { return this._okCommand.Value; } } public event EventHandler RequestCloseDialog; private void InvokeRequestCloseDialog(RequestCloseDialogEventArgs e) { var handler = RequestCloseDialog; if (handler != null) handler(this, e); } } 

EDIT 2: Utilicé el código de aquí para hacer que mi EventHandler se registre como débil:
http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx
(El sitio web ya no existe, WebArchive Mirror )

 public delegate void UnregisterCallback(EventHandler eventHandler) where TE : EventArgs; public interface IWeakEventHandler where TE : EventArgs { EventHandler Handler { get; } } public class WeakEventHandler : IWeakEventHandler where T : class where TE : EventArgs { private delegate void OpenEventHandler(T @this, object sender, TE e); private readonly WeakReference mTargetRef; private readonly OpenEventHandler mOpenHandler; private readonly EventHandler mHandler; private UnregisterCallback mUnregister; public WeakEventHandler(EventHandler eventHandler, UnregisterCallback unregister) { mTargetRef = new WeakReference(eventHandler.Target); mOpenHandler = (OpenEventHandler)Delegate.CreateDelegate( typeof(OpenEventHandler),null, eventHandler.Method); mHandler = Invoke; mUnregister = unregister; } public void Invoke(object sender, TE e) { T target = (T)mTargetRef.Target; if (target != null) mOpenHandler.Invoke(target, sender, e); else if (mUnregister != null) { mUnregister(mHandler); mUnregister = null; } } public EventHandler Handler { get { return mHandler; } } public static implicit operator EventHandler(WeakEventHandler weh) { return weh.mHandler; } } public static class EventHandlerUtils { public static EventHandler MakeWeak(this EventHandler eventHandler, UnregisterCallback unregister) where TE : EventArgs { if (eventHandler == null) throw new ArgumentNullException("eventHandler"); if (eventHandler.Method.IsStatic || eventHandler.Target == null) throw new ArgumentException("Only instance methods are supported.", "eventHandler"); var wehType = typeof(WeakEventHandler).MakeGenericType( eventHandler.Method.DeclaringType, typeof(TE)); var wehConstructor = wehType.GetConstructor(new Type[] { typeof(EventHandler), typeof(UnregisterCallback) }); IWeakEventHandler weh = (IWeakEventHandler)wehConstructor.Invoke( new object[] { eventHandler, unregister }); return weh.Handler; } } 

Este es un buen enfoque y utilicé otros similares en el pasado. ¡Ve a por ello!

Una cosa menor que definitivamente haría es hacer que el evento reciba un booleano para cuando necesite establecer “falso” en DialogResult.

 event EventHandler RequestCloseDialog; 

y la clase EventArgs:

 public class RequestCloseEventArgs : EventArgs { public RequestCloseEventArgs(bool dialogResult) { this.DialogResult = dialogResult; } public bool DialogResult { get; private set; } } 

He estado utilizando un enfoque casi idéntico durante varios meses, y estoy muy contento con él (es decir, todavía no he sentido el impulso de reescribirlo por completo …)

En mi implementación, uso un IDialogViewModel que expone cosas como el título, los botones de standad para mostrar (para tener una apariencia uniforme en todos los cuadros de diálogo), un evento RequestClose y algunas otras cosas para poder controlar la ventana tamaño y comportamiento

Si está hablando de ventanas de diálogo y no solo de los cuadros de mensaje emergentes, considere mi enfoque a continuación. Los puntos clave son:

  1. Paso una referencia al Module Controller al constructor de cada ViewModel (puede usar la inyección).
  2. Ese Module Controller tiene métodos públicos / internos para crear ventanas de diálogo (simplemente crear, sin devolver un resultado). De ahí que para abrir una ventana de diálogo en ViewModel escribo: controller.OpenDialogEntity(bla, bla...)
  3. Cada ventana de diálogo notifica sobre su resultado (como OK , Guardar , Cancelar , etc.) a través de Eventos Débiles . Si usa PRISM, entonces es más fácil publicar notificaciones usando este EventAggregator .
  4. Para manejar los resultados del diálogo, estoy usando la suscripción a las notificaciones (de nuevo Weak Events y EventAggregator en caso de PRISM). Para reducir la dependencia de tales notificaciones, use clases independientes con notificaciones estándar.

Pros:

  • Menos código. No me importa usar interfaces, pero he visto demasiados proyectos donde el uso excesivo de interfaces y capas de abstracción causa más problemas que ayuda.
  • Abrir ventanas de diálogo a través de Module Controller es una forma simple de evitar referencias fuertes y aún permite usar maquetas para probar.
  • La notificación a través de eventos débiles reduce la cantidad de posibles memory leaks.

Contras:

  • No es fácil distinguir la notificación requerida de los demás en el controlador. Dos soluciones:
    • enviar un token único al abrir una ventana de diálogo y verificar ese token en la suscripción
    • utilice clases de notificación genéricas donde T es la enumeración de las entidades (o por simplicidad puede ser el tipo de ViewModel).
  • Para un proyecto debe haber un acuerdo sobre el uso de clases de notificación para evitar su duplicación.
  • Para proyectos enormemente grandes, el Module Controller puede verse abrumado por métodos para crear ventanas. En este caso, es mejor dividirlo en varios módulos.

PD. He estado usando este enfoque desde hace bastante tiempo y estoy listo para defender su elegibilidad en los comentarios y proporcionar algunos ejemplos si es necesario.