2010-09-27 9 views
127

Últimamente tuve el problema de crear y editar cuadros de diálogo para mi aplicación wpf.¿Buena o mala práctica para los cuadros de diálogo en wpf con MVVM?

Todo lo que quiero hacer en mi código es algo como esto. (Utilizo sobre todo viewmodel primer acercamiento con MVVM)

modelo de vista que requiere una ventana de diálogo:

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

¿Cómo funciona?

En primer lugar, he creado 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 sencilla. Lo necesito para mantener mi contenido:

<Window x:Class="WindowDialog" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    Title="WindowDialog" 
    WindowStyle="SingleBorderWindow" 
    WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight"> 
    <ContentPresenter x:Name="DialogPresenter" Content="{Binding .}"> 

    </ContentPresenter> 
</Window> 

Un problema con diálogos en WPF es el dialogresult = true sólo puede lograrse 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<RequestCloseDialogEventArgs> RequestCloseDialog; 
} 

Cada vez que mi modelo de vista piensa que es hora de dialogresult = true, a continuación, elevar 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<RequestCloseDialogEventArgs> 
            (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 tiene que crear un DataTemplate en mi archivo de recursos (app.xaml o algo así):

<DataTemplate DataType="{x:Type DialogViewModel:EditOrNewAuswahlItemVM}" > 
     <DialogView:EditOrNewAuswahlItem/> 
</DataTemplate> 

Bueno eso es todo, ahora puedo llamar a diálogos de mis ViewModels:

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

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

Editar: para completar. El modelo de vista debe aplicar IDialogResultVMHelper y luego se puede plantear que dentro de un OkCommand o menos así:

public class MyViewmodel : IDialogResultVMHelper 
{ 
    private readonly Lazy<DelegateCommand> _okCommand; 

    public MyViewmodel() 
    { 
     this._okCommand = new Lazy<DelegateCommand>(() => 
      new DelegateCommand(() => 
       InvokeRequestCloseDialog(
        new RequestCloseDialogEventArgs(true)),() => 
         YourConditionsGoesHere = true)); 
    } 

    public ICommand OkCommand 
    { 
     get { return this._okCommand.Value; } 
    } 

    public event EventHandler<RequestCloseDialogEventArgs> RequestCloseDialog; 
    private void InvokeRequestCloseDialog(RequestCloseDialogEventArgs e) 
    { 
     var handler = RequestCloseDialog; 
     if (handler != null) 
      handler(this, e); 
    } 
} 

EDIT 2: He utilizado el código de aquí para hacer mi manejador de sucesos se registra débil:
http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx
(La página web sin ya existe, WebArchive Mirror)

public delegate void UnregisterCallback<TE>(EventHandler<TE> eventHandler) 
    where TE : EventArgs; 

public interface IWeakEventHandler<TE> 
    where TE : EventArgs 
{ 
    EventHandler<TE> Handler { get; } 
} 

public class WeakEventHandler<T, TE> : IWeakEventHandler<TE> 
    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<TE> mHandler; 
    private UnregisterCallback<TE> mUnregister; 

    public WeakEventHandler(EventHandler<TE> eventHandler, 
           UnregisterCallback<TE> 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<TE> Handler 
    { 
     get { return mHandler; } 
    } 

    public static implicit operator EventHandler<TE>(WeakEventHandler<T, TE> weh) 
    { 
     return weh.mHandler; 
    } 
} 

public static class EventHandlerUtils 
{ 
    public static EventHandler<TE> MakeWeak<TE>(this EventHandler<TE> eventHandler, 
                UnregisterCallback<TE> 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<TE>), typeof(UnregisterCallback<TE>) 
          }); 

     IWeakEventHandler<TE> weh = (IWeakEventHandler<TE>)wehConstructor.Invoke(
             new object[] { eventHandler, unregister }); 

     return weh.Handler; 
    } 
} 
+1

es probable que falte la referencia xmlns: x = "http://schemas.microsoft.com/winfx/2006/xaml" en su WindowDialog XAML. –

+0

En realidad, el espacio de nombre es xmlns: x = "[http: //] schemas.microsoft.com/winfx/2006/xaml" sin los corchetes – reggaeguitar

+0

ver http://stackoverflow.com/questions/16993433/mvvm-light-wpf -binding-multiple-instances-of-a-window-to-a-viewmodel/16994523 # 16994523 – reggaeguitar

Respuesta

43

Este es un buen enfoque y utiliza 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<RequestCloseEventArgs> RequestCloseDialog; 

y la clase EventArgs:

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

    public bool DialogResult { get; private set; } 
} 
+0

thx, cambiaré mi evento :) – blindmeis

+7

Creo que, en lugar de 'bool', debe haber un EventArgs personalizado derivado de la clase' EventArgs' base que contiene una propiedad 'bool'. El delegado 'EventHandler' tiene una restricción de clase en el parámetro genérico que requiere que el tipo se derive de' EventArgs'. Con 'bool' como parámetro genérico, esto no se compila (al menos no en VS2010, no sé si esto posiblemente cambió de versiones anteriores). – Slauma

+0

tiene la razón, he reparado el código de muestra. Gracias –

15

He estado usando un enfoque casi idéntico desde hace varios meses, y estoy muy contento con él (es decir, que todavía no he sentido la impulso de reescribirlo completamente ...

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

+0

thx, el título debería ir realmente en mi IDialogViewModel. las otras propiedades como el tamaño, el botón estándar me iré, porque todo esto proviene de la plantilla de datos al menos. – blindmeis

+1

Eso es lo que hice al principio también, simplemente use SizeToContent para controlar el tamaño de la ventana. Pero en un caso necesité cambiar el tamaño de la ventana, así que tuve que ajustarlo un poco ... –

+0

mhh thx para esta información :) – blindmeis

2

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

  1. me pasa una referencia a Module Controller en el constructor de cada ViewModel (se puede utilizar la inyección).
  2. Eso 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 el que escribo ViewModel: controller.OpenDialogEntity(bla, bla...) ventana
  3. Cada diálogo notifica acerca de su resultado (como OK, Guardar, Cancelar , etc.) a través Weak Events. Si usa PRISM, entonces es más fácil publicar notificaciones usando this EventAggregator.
  4. Para manejar los resultados del diálogo, estoy usando suscripción a notificaciones (nuevamente 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 manera simple de evitar referencias fuertes y aún permite usar maquetas para probar.
  • La notificación a través de eventos débiles reduce el número de posibles pérdidas de memoria.

Contras:

  • No es fácil distinguir la notificación requerida de los demás en el controlador. Dos soluciones:
    • envían un token único en la apertura de una ventana de diálogo y compruebe que token en la suscripción
    • utilizar las clases de notificación genérica <T> donde T es la enumeración de las entidades (o por simplicidad puede ser el tipo de modelo de vista).
  • 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.

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

Cuestiones relacionadas