Manejo de cuadros de diálogo en WPF con MVVM

235

En el patrón MVVM para WPF, el manejo de diálogos es una de las operaciones más complejas. Como su modelo de vista no sabe nada sobre la vista, la comunicación de diálogo puede ser interesante. Puedo exponer y ICommandque cuando la vista lo invoca, puede aparecer un diálogo.

¿Alguien sabe de una buena manera de manejar los resultados de los diálogos? Estoy hablando de diálogos de Windows como MessageBox.

Una de las formas en que lo hicimos fue tener un evento en el modelo de vista al que la vista se suscribiría cuando se requiriera un diálogo.

public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;

Esto está bien, pero significa que la vista requiere un código que es algo de lo que me gustaría alejarme.

Ray Booysen
fuente
¿Por qué no unirse a un objeto auxiliar en la Vista?
Paul Williams
1
No estoy seguro de lo que quieres decir.
Ray Booysen
1
Si entiendo la pregunta, no desea que los cuadros de diálogo emergentes de VM, y no desea código subyacente en la Vista. Además, parece que prefieres comandos a eventos. Estoy de acuerdo con todo esto, por lo que utilizo una clase auxiliar en la Vista que expone un comando para manejar el diálogo. Respondí esta pregunta en otro hilo aquí: stackoverflow.com/a/23303267/420400 . Sin embargo, la última oración hace que parezca que no desea ningún código, en ningún lugar de la Vista. Entiendo esa preocupación, pero el código en cuestión es solo condicional y no es probable que cambie.
Paul Williams
44
El modelo de vista siempre debe ser responsable de la lógica detrás de la creación del cuadro de diálogo, esa es toda la razón de su existencia en primer lugar. Dicho esto, no (y no debería) hacer el trabajo pesado de crear la vista en sí. Escribí un artículo sobre este tema en codeproject.com/Articles/820324/… donde demuestro que todo el ciclo de vida de los cuadros de diálogo se puede administrar a través del enlace de datos WPF regular y sin romper el patrón MVVM.
Mark Feldman

Respuestas:

131

Sugiero renunciar a los cuadros de diálogo modales de la década de 1990 y, en su lugar, implementar un control como una superposición (lienzo + posicionamiento absoluto) con visibilidad vinculada a un booleano en la VM. Más cerca de un control de tipo ajax.

Esto es muy útil:

<BooleanToVisibilityConverter x:Key="booltoVis" />

como en:

<my:ErrorControl Visibility="{Binding Path=ThereWasAnError, Mode=TwoWay, Converter={StaticResource booltoVis}, UpdateSourceTrigger=PropertyChanged}"/>

Así es como tengo uno implementado como control de usuario. Al hacer clic en la 'x' se cierra el control en una línea de código en el código del control de usuario detrás. (Como tengo mis Vistas en un .exe y ViewModels en un dll, no me siento mal por el código que manipula la IU).

Cuadro de diálogo Wpf

Jeffrey Knight
fuente
20
Sí, también me gusta esta idea, pero me gustaría ver algún ejemplo de este control en términos de cómo mostrarlo, recuperar el resultado del diálogo, etc. Especialmente en el escenario MVVM en Silverlight.
Roboblob
16
¿Cómo evita que el usuario interactúe con los controles debajo de esta superposición de diálogo?
Andrew Garrison
17
El problema con este enfoque es que no se puede abrir un segundo diálogo modal del primero, al menos no sin algunas modificaciones pesadas en el sistema de superposición ...
Thomas Levesque
66
Otro problema con este enfoque es que el "diálogo" no se puede mover. En nuestras aplicaciones, debemos tener un diálogo móvil para que el usuario pueda ver lo que hay detrás.
JAB
13
Este enfoque me parece terrible. ¿Qué me estoy perdiendo? ¿Cómo es esto mejor que un cuadro de diálogo real?
Jonathan Wood
51

Deberías usar un mediador para esto. Mediator es un patrón de diseño común también conocido como Messenger en algunas de sus implementaciones. Es un paradigma de tipo Registrarse / Notificar y permite que su ViewModel y Vistas se comuniquen a través de un mecanismo de mensajería de bajo acoplamiento.

Debería consultar el grupo de Discípulos de Google WPF y simplemente buscar Mediador. Estarás muy contento con las respuestas ...

Sin embargo, puede comenzar con esto:

http://joshsmithonwpf.wordpress.com/2009/04/06/a-mediator-prototype-for-wpf-apps/

¡A disfrutar!

Editar: puede ver la respuesta a este problema con el MVVM Light Toolkit aquí:

http://mvvmlight.codeplex.com/Thread/View.aspx?ThreadId=209338

Roubachof
fuente
2
Marlon grech acaba de publicar una nueva implementación del mediador: marlongrech.wordpress.com/2009/04/16/…
Roubachof
21
Solo una observación: el patrón de Mediador no fue introducido por los Discípulos de WPF, es un patrón clásico de GoF ... ( dofactory.com/Patterns/PatternMediator.aspx ). Buena respuesta de lo contrario;)
Thomas Levesque
10
Por favor, dios, no uses un mediador o un mensajero maldito. Ese tipo de código con docenas de mensajes volando se vuelve muy difícil de depurar a menos que pueda recordar de alguna manera todos los puntos en toda su base de código que se suscriben y manejan cada evento. Se convierte en una pesadilla para los nuevos desarrolladores. De hecho, considero que toda la biblioteca MvvMLight es un antipatrón masivo por su uso omnipresente e innecesario de mensajes asíncronos. La solución es simple: llame a un servicio de diálogo separado (es decir, IDialogService) de su diseño. La interfaz tiene métodos y eventos para devoluciones de llamada.
Chris Bordeman
34

Un buen diálogo MVVM debería:

  1. Se declarará solo con XAML.
  2. Obtenga todo su comportamiento del enlace de datos.

Desafortunadamente, WPF no proporciona estas características. Mostrar un diálogo requiere una llamada de código subyacente a ShowDialog(). La clase Window, que admite diálogos, no se puede declarar en XAML, por lo que no se puede enlazar fácilmente a los datos DataContext.

Para resolver esto, escribí un control de código auxiliar XAML que se encuentra en el árbol lógico y retransmite el enlace de datos a Windowy maneja mostrar y ocultar el diálogo. Puede encontrarlo aquí: http://www.codeproject.com/KB/WPF/XAMLDialog.aspx

Es realmente simple de usar y no requiere ningún cambio extraño en su ViewModel y no requiere eventos o mensajes. La llamada básica se ve así:

<dialog:Dialog Content="{Binding Path=DialogViewModel}" Showing="True" />

Probablemente desee agregar un estilo que establezca Showing. Lo explico en mi artículo. Espero que esto te ayude.

usuario92541
fuente
2
Ese es un enfoque realmente interesante para el problema de mostrar ventanas de diálogo en MVVM.
dthrasher
2
"Showing a dialog requires a code-behind"mmm puedes llamarlo en ViewModel
Brock Hensley
Añadiría el punto 3: puede unirse a otros objetos dentro de la vista. Dejar el código del cuadro de diálogo vacío implica que no hay ningún código C # en ninguna parte de la vista, y el enlace de datos no implica el enlace a la VM.
Paul Williams
25

Utilizo este enfoque para los diálogos con MVVM.

Todo lo que tengo que hacer ahora es llamar a lo siguiente desde mi modelo de vista.

var result = this.uiDialogService.ShowDialog("Dialogwindow title goes here", dialogwindowVM);
blindmeis
fuente
¿De qué biblioteca proviene uiDialogService?
aggietech
1
No hay biblioteca. es solo una pequeña interfaz e implementación: stackoverflow.com/questions/3801681/… . para ser justos, tiene algunas sobrecargas para mis necesidades :) (altura, ancho,
configuración de
16

Mi solución actual resuelve la mayoría de los problemas que mencionó, pero está completamente abstraída de las cosas específicas de la plataforma y puede reutilizarse. También no utilicé ningún enlace de código subyacente solo con DelegateCommands que implementan ICommand. El diálogo es básicamente una vista: un control separado que tiene su propio ViewModel y se muestra desde el ViewModel de la pantalla principal, pero se activa desde la interfaz de usuario a través del enlace DelagateCommand.

Vea la solución completa de Silverlight 4 aquí Diálogos modales con MVVM y Silverlight 4

Roboblob
fuente
Al igual que el enfoque de @Elad Katz, su respuesta carece del contenido vinculado; mejore su respuesta insertándola, ya que eso es lo que se considera una buena respuesta aquí en SO. No obstante, ¡gracias por tu contribución! :)
Yoda
6

Realmente luché con este concepto por un tiempo cuando aprendí (todavía estoy aprendiendo) MVVM. Lo que decidí, y lo que creo que otros ya decidieron pero que no estaba claro para mí es esto:

Mi pensamiento original era que un ViewModel no debería poder llamar a un cuadro de diálogo directamente, ya que no tiene nada que decidir cómo debería aparecer un diálogo. Debido a esto, comencé a pensar en cómo podría pasar mensajes como lo haría en MVP (es decir, View.ShowSaveFileDialog ()). Sin embargo, creo que este es el enfoque equivocado.

Está bien que un ViewModel llame a un diálogo directamente. Sin embargo, cuando está probando un ViewModel, eso significa que el cuadro de diálogo aparecerá durante la prueba o fallará por completo (nunca lo intentó).

Entonces, lo que debe suceder es que mientras se realiza la prueba, se debe usar una versión de "prueba" de su diálogo. Esto significa que para cada diálogo que tenga, debe crear una interfaz y simular la respuesta del diálogo o crear una simulación de prueba que tendrá un comportamiento predeterminado.

Ya debería estar utilizando algún tipo de Localizador de servicios o IoC que pueda configurar para proporcionarle la versión correcta según el contexto.

Con este enfoque, su ViewModel aún es comprobable y, dependiendo de cómo se burle de sus cuadros de diálogo, puede controlar el comportamiento.

Espero que esto ayude.

Mike Rowley
fuente
6

Hay dos buenas maneras de hacer esto: 1) un servicio de diálogo (fácil, limpio) y 2) vista asistida. Ver asistido proporciona algunas características interesantes, pero generalmente no vale la pena.

SERVICIO DE DIÁLOGO

a) una interfaz de servicio de diálogo como vía constructor o algún contenedor de dependencia:

interface IDialogService { Task ShowDialogAsync(DialogViewModel dlgVm); }

b) Su implementación de IDialogService debería abrir una ventana (o inyectar algún control en la ventana activa), crear una vista correspondiente al nombre del tipo dlgVm dado (usar registro o convención de contenedor o un ContentPresenter con plantillas de datos asociadas al tipo). ShowDialogAsync debería crear una TaskCompletionSource y devolver su .Task proptery. La clase DialogViewModel en sí misma necesita un evento que pueda invocar en la clase derivada cuando desee cerrar, y mirar en la vista de diálogo para cerrar / ocultar el diálogo y completar el TaskCompletionSource.

b) Para usar, simplemente llame a await this.DialogService.ShowDialog (myDlgVm) en su instancia de alguna clase derivada de DialogViewModel. Después de esperar el regreso, observe las propiedades que ha agregado en su máquina virtual de diálogo para determinar qué sucedió; Ni siquiera necesita una devolución de llamada.

VISTA ASISTIDA

Esto hace que tu vista escuche un evento en el modelo de vista. Todo esto podría estar envuelto en un Comportamiento de mezcla para evitar el código detrás y el uso de recursos si así lo desea (FMI, subclase la clase "Comportamiento" para ver una especie de propiedad adjunta Blendable en esteroides). Por ahora, haremos esto manualmente en cada vista:

a) Cree un OpenXXXXXDialogEvent con una carga personalizada (una clase derivada de DialogViewModel).

b) Haga que la vista se suscriba al evento en su evento OnDataContextChanged. Asegúrese de ocultar y cancelar la suscripción si el valor anterior! = Nulo y en el evento Descargado de Windows.

c) Cuando se active el evento, haga que la vista abra su vista, que podría estar en un recurso en su página, o podría ubicarla por convención en otro lugar (como en el enfoque del servicio de diálogo).

Este enfoque es más flexible, pero requiere más trabajo para usar. No lo uso mucho. La única ventaja agradable es la capacidad de colocar la vista físicamente dentro de una pestaña, por ejemplo. He usado un algoritmo para colocarlo en los límites del control de usuario actual, o si no es lo suficientemente grande, atravesar el árbol visual hasta que se encuentre un contenedor lo suficientemente grande.

Esto permite que los cuadros de diálogo estén cerca del lugar donde se usan realmente, solo atenúa la parte de la aplicación relacionada con la actividad actual y permite que el usuario se mueva dentro de la aplicación sin tener que empujar los cuadros de diálogo manualmente, incluso tener múltiples cuasi Los cuadros de diálogo modales se abren en diferentes pestañas o subvistas.

Chris Bordeman
fuente
Un servicio de diálogo es mucho más fácil, ciertamente, y lo que suelo hacer. También facilita el cierre del cuadro de diálogo de la vista desde el modelo de vista principal, que es necesario cuando el modelo de vista principal se está cerrando o cancelando.
Chris Bordeman
4

Use un comando congelable

<Grid>
        <Grid.DataContext>
            <WpfApplication1:ViewModel />
        </Grid.DataContext>


        <Button Content="Text">
            <Button.Command>
                <WpfApplication1:MessageBoxCommand YesCommand="{Binding MyViewModelCommand}" />
            </Button.Command>
        </Button>

</Grid>
public class MessageBoxCommand : Freezable, ICommand
{
    public static readonly DependencyProperty YesCommandProperty = DependencyProperty.Register(
        "YesCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty OKCommandProperty = DependencyProperty.Register(
        "OKCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty CancelCommandProperty = DependencyProperty.Register(
        "CancelCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty NoCommandProperty = DependencyProperty.Register(
        "NoCommand",
        typeof (ICommand),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata(null)
        );


    public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
        "Message",
        typeof (string),
        typeof (MessageBoxCommand),
        new FrameworkPropertyMetadata("")
        );

    public static readonly DependencyProperty MessageBoxButtonsProperty = DependencyProperty.Register(
        "MessageBoxButtons",
        typeof(MessageBoxButton),
        typeof(MessageBoxCommand),
        new FrameworkPropertyMetadata(MessageBoxButton.OKCancel)
        );

    public ICommand YesCommand
    {
        get { return (ICommand) GetValue(YesCommandProperty); }
        set { SetValue(YesCommandProperty, value); }
    }

    public ICommand OKCommand
    {
        get { return (ICommand) GetValue(OKCommandProperty); }
        set { SetValue(OKCommandProperty, value); }
    }

    public ICommand CancelCommand
    {
        get { return (ICommand) GetValue(CancelCommandProperty); }
        set { SetValue(CancelCommandProperty, value); }
    }

    public ICommand NoCommand
    {
        get { return (ICommand) GetValue(NoCommandProperty); }
        set { SetValue(NoCommandProperty, value); }
    }

    public MessageBoxButton MessageBoxButtons
    {
        get { return (MessageBoxButton)GetValue(MessageBoxButtonsProperty); }
        set { SetValue(MessageBoxButtonsProperty, value); }
    }

    public string Message
    {
        get { return (string) GetValue(MessageProperty); }
        set { SetValue(MessageProperty, value); }
    }

    public void Execute(object parameter)
    {
        var messageBoxResult = MessageBox.Show(Message);
        switch (messageBoxResult)
        {
            case MessageBoxResult.OK:
                OKCommand.Execute(null);
                break;
            case MessageBoxResult.Yes:
                YesCommand.Execute(null);
                break;
            case MessageBoxResult.No:
                NoCommand.Execute(null);
                break;
            case MessageBoxResult.Cancel:
                if (CancelCommand != null) CancelCommand.Execute(null); //Cancel usually means do nothing ,so can be null
                break;

        }
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;


    protected override Freezable CreateInstanceCore()
    {
        throw new NotImplementedException();
    }
}
Maxm007
fuente
Este código necesita algo de trabajo, pero es la mejor idea con diferencia, especialmente para los cuadros de diálogo del sistema, como los cuadros de diálogo de archivos o impresoras. Los cuadros de diálogo pertenecen a Ver si algo lo hace. Para los diálogos de archivo, el resultado (nombre de archivo seleccionado) se puede pasar al comando interno como su parámetro.
Anton Tykhyy
3

Creo que el manejo de un diálogo debería ser responsabilidad de la vista, y la vista debe tener un código que lo respalde.

Si cambia la interacción ViewModel - View para manejar diálogos, entonces ViewModel depende de esa implementación. La forma más sencilla de abordar este problema es hacer que la Vista sea responsable de realizar la tarea. Si eso significa mostrar un diálogo, entonces está bien, pero también podría ser un mensaje de estado en la barra de estado, etc.

Mi punto es que todo el punto del patrón MVVM es separar la lógica empresarial de la GUI, por lo que no debería mezclar la lógica GUI (para mostrar un diálogo) en la capa empresarial (ViewModel).

Cameron MacFarland
fuente
2
La VM nunca manejaría el cuadro de diálogo, en mi ejemplo, simplemente tendría un evento que requeriría que el cuadro de diálogo se active y devuelva información en alguna forma de EventArgs. Si la vista es responsable, ¿cómo transfiere información a la VM?
Ray Booysen
Digamos que la VM necesita eliminar algo. La VM llama a un método en la vista Eliminar que devuelve un valor booleano. La Vista puede eliminar el elemento directamente y devolver verdadero, o mostrar un diálogo de confirmación y devolver verdadero / falso dependiendo de la respuesta del usuario.
Cameron MacFarland
La VM no sabe nada sobre el diálogo, pero solo le pidió a la vista que elimine algo, lo que la vista confirmó o denegó.
Cameron MacFarland
Siempre pensé que el punto de MVVM era Modelo: lógica empresarial, ViewModel: lógica GUI y Vista: sin lógica. Lo que de alguna manera se contradice con tu último párrafo. ¡Por favor explique!
David Schmitt
2
Primero, debe determinarse si solicitar confirmación previa a la eliminación es lógica de negocio o lógica de vista. Si es lógica empresarial, el método DeleteFile en el modelo no debe hacerlo, sino que debe devolver el objeto de pregunta de confirmación. Esto incluirá una referencia al delegado que hace la eliminación real. Si no es una lógica de negocios, la VM debe construir una VM de la pregunta en DeleteFileCommand, con dos miembros de ICommand. Uno para sí y otro para no. Probablemente haya argumentos para ambas vistas, y en RL la mayor parte del uso probablemente encontrará ambas.
Guge
3

Una alternativa interesante es usar controladores que son responsables de mostrar las vistas (cuadros de diálogo).

WPF Application Framework (WAF) muestra cómo funciona esto .

jbe
fuente
3

¿Por qué no simplemente generar un evento en la VM y suscribirse al evento en la vista? Esto mantendría la lógica de la aplicación y la vista separadas y aún le permitiría usar una ventana secundaria para los cuadros de diálogo.

Eric Grover
fuente
3

Implementé un comportamiento que escucha un mensaje del ViewModel. Está basado en la solución Laurent Bugnion, pero como no usa código detrás y es más reutilizable, creo que es más elegante.

Cómo hacer que WPF se comporte como si MVVM fuera compatible de inmediato

Elad Katz
fuente
1
Debe incluir el código completo aquí, ya que eso es lo que SO requiere para obtener buenas respuestas. No obstante, el enfoque vinculado es bastante bueno, ¡así que gracias por eso! :)
Yoda
2
@yoda el código completo es bastante largo, y es por eso que prefiero vincularlo. He editado mi respuesta para reflejar los cambios y señalar un enlace que no está roto
Elad Katz
Gracias por la mejora Sin embargo, es mejor proporcionar rollos de página completa de código 3 largos aquí en SO que un enlace que podría estar fuera de línea algún día. Los buenos artículos para temas complejos siempre son bastante largos, y no veo ningún beneficio en abrir una nueva pestaña, cambiar a ella y desplazarme allí para desplazarme en la misma página / pestaña en la que estaba antes. ;)
Yoda
@EladKatz He visto que ha compartido parte de su implementación de WPF en el enlace que proporcionó. ¿Tiene alguna solución para abrir una nueva ventana desde ViewModel? Básicamente tengo dos formas y cada una tiene un ViewModel. Un usuario hace clic en un botón, aparece otro formulario y viewmodel1 envía su objeto a viewmodel2. En el formulario 2, el usuario puede cambiar el objeto y cuando cierra la ventana, el objeto actualizado se enviará de nuevo al primer ViewModel. ¿Tienes alguna solución para esto?
Ehsan
2

Creo que la vista podría tener código para manejar el evento desde el modelo de vista.

Dependiendo del evento / escenario, también podría tener un desencadenante de evento que se suscriba para ver los eventos del modelo y una o más acciones para invocar en respuesta.

Nikhil Kothari
fuente
1

Karl Shifflett ha creado una aplicación de muestra para mostrar cuadros de diálogo utilizando el enfoque de servicio y el enfoque de solicitud de interacción de prisma.

Me gusta el enfoque de servicio: es menos flexible, por lo que es menos probable que los usuarios rompan algo :) También es coherente con la parte WinForms de mi aplicación (MessageBox.Show) Pero si planea mostrar muchos diálogos diferentes, entonces InteractionRequest es un Mejor manera de ir.

http://karlshifflett.wordpress.com/2010/11/07/in-the-box-ndash-mvvm-training/

surfen
fuente
1

Sé que es una pregunta antigua, pero cuando hice esta búsqueda, encontré muchas preguntas relacionadas, pero no encontré una respuesta realmente clara. ¡Entonces hago mi propia implementación de un cuadro de diálogo / cuadro de mensaje / popin, y lo comparto!
Creo que es "a prueba de MVVM", y trato de hacerlo simple y adecuado, pero soy nuevo en WPF, así que siéntase libre de comentar, o incluso hacer una solicitud de extracción.

https://github.com/Plasma-Paris/Plasma.WpfUtils

Puedes usarlo así:

public RelayCommand YesNoMessageBoxCommand { get; private set; }
async void YesNoMessageBox()
{
    var result = await _Service.ShowMessage("This is the content of the message box", "This is the title", System.Windows.MessageBoxButton.YesNo);
    if (result == System.Windows.MessageBoxResult.Yes)
        // [...]
}

O así si quieres popin más sofisticado:

var result = await _Service.ShowCustomMessageBox(new MyMessageBoxViewModel { /* What you want */ });

Y está mostrando cosas como esta:

2

Xav987
fuente
1

El enfoque estándar

Después de pasar años lidiando con este problema en WPF, finalmente descubrí la forma estándar de implementar diálogos en WPF. Estas son las ventajas de este enfoque:

  1. LIMPIAR
  2. No viola el patrón de diseño MVVM
  3. ViewModal nunca hace referencia a ninguna de las bibliotecas de la interfaz de usuario (WindowBase, PresentationFramework, etc.)
  4. Perfecto para pruebas automatizadas.
  5. Los cuadros de diálogo se pueden reemplazar fácilmente.

Entonces, ¿cuál es la clave? Es DI + IoC .

Así es como funciona. Estoy usando MVVM Light, pero este enfoque también puede extenderse a otros marcos:

  1. Agregue un proyecto de aplicación WPF a su solución. Llámalo App .
  2. Agregue una biblioteca de clases ViewModal. Llámalo VM .
  3. La aplicación hace referencia al proyecto VM. El proyecto VM no sabe nada sobre la aplicación.
  4. Agregue referencia NuGet a MVVM Light a ambos proyectos . Estoy usando MVVM Light Standard en estos días, pero también estás de acuerdo con la versión completa de Framework.
  5. Agregue una interfaz IDialogService al proyecto VM:

    public interface IDialogService
    {
      void ShowMessage(string msg, bool isError);
      bool AskBooleanQuestion(string msg);
      string AskStringQuestion(string msg, string default_value);
    
      string ShowOpen(string filter, string initDir = "", string title = "");
      string ShowSave(string filter, string initDir = "", string title = "", string fileName = "");
      string ShowFolder(string initDir = "");
    
      bool ShowSettings();
    }
  6. Exponga una propiedad estática pública de IDialogServicetipo en su ViewModelLocator, pero deje la parte de registro para que realice la capa de Vista. Esta es la clave .

    public static IDialogService DialogService => SimpleIoc.Default.GetInstance<IDialogService>();
  7. Agregue una implementación de esta interfaz en el proyecto de la aplicación.

    public class DialogPresenter : IDialogService
    {
        private static OpenFileDialog dlgOpen = new OpenFileDialog();
        private static SaveFileDialog dlgSave = new SaveFileDialog();
        private static FolderBrowserDialog dlgFolder = new FolderBrowserDialog();
    
        /// <summary>
        /// Displays a simple Information or Error message to the user.
        /// </summary>
        /// <param name="msg">String text that is to be displayed in the MessageBox</param>
        /// <param name="isError">If true, Error icon is displayed. If false, Information icon is displayed.</param>
        public void ShowMessage(string msg, bool isError)
        {
                if(isError)
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Error);
                else
                        System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.OK, MessageBoxImage.Information);
        }
    
        /// <summary>
        /// Displays a Yes/No MessageBox.Returns true if user clicks Yes, otherwise false.
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public bool AskBooleanQuestion(string msg)
        {
                var Result = System.Windows.MessageBox.Show(msg, "Your Project Title", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
                return Result;
        }
    
        /// <summary>
        /// Displays Save dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Save button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public string ShowSave(string filter, string initDir = "", string title = "", string fileName = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgSave.Title = title;
                else
                        dlgSave.Title = "Save";
    
                if (!string.IsNullOrEmpty(fileName))
                        dlgSave.FileName = fileName;
                else
                        dlgSave.FileName = "";
    
                dlgSave.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgSave.InitialDirectory = initDir;
    
                if (dlgSave.ShowDialog() == DialogResult.OK)
                        return dlgSave.FileName;
                else
                        return null;
        }
    
    
        public string ShowFolder(string initDir = "")
        {
                if (!string.IsNullOrEmpty(initDir))
                        dlgFolder.SelectedPath = initDir;
    
                if (dlgFolder.ShowDialog() == DialogResult.OK)
                        return dlgFolder.SelectedPath;
                else
                        return null;
        }
    
    
        /// <summary>
        /// Displays Open dialog. User can specify file filter, initial directory and dialog title. Returns full path of the selected file if
        /// user clicks Open button. Returns null if user clicks Cancel button.
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="initDir"></param>
        /// <param name="title"></param>
        /// <returns></returns>
        public string ShowOpen(string filter, string initDir = "", string title = "")
        {
                if (!string.IsNullOrEmpty(title))
                        dlgOpen.Title = title;
                else
                        dlgOpen.Title = "Open";
    
                dlgOpen.Multiselect = false;
                dlgOpen.Filter = filter;
                if (!string.IsNullOrEmpty(initDir))
                        dlgOpen.InitialDirectory = initDir;
    
                if (dlgOpen.ShowDialog() == DialogResult.OK)
                        return dlgOpen.FileName;
                else
                        return null;
        }
    
        /// <summary>
        /// Shows Settings dialog.
        /// </summary>
        /// <returns>true if User clicks OK button, otherwise false.</returns>
        public bool ShowSettings()
        {
                var w = new SettingsWindow();
                MakeChild(w); //Show this dialog as child of Microsoft Word window.
                var Result = w.ShowDialog().Value;
                return Result;
        }
    
        /// <summary>
        /// Prompts user for a single value input. First parameter specifies the message to be displayed in the dialog 
        /// and the second string specifies the default value to be displayed in the input box.
        /// </summary>
        /// <param name="m"></param>
        public string AskStringQuestion(string msg, string default_value)
        {
                string Result = null;
    
                InputBox w = new InputBox();
                MakeChild(w);
                if (w.ShowDialog(msg, default_value).Value)
                        Result = w.Value;
    
                return Result;
        }
    
        /// <summary>
        /// Sets Word window as parent of the specified window.
        /// </summary>
        /// <param name="w"></param>
        private static void MakeChild(System.Windows.Window w)
        {
                IntPtr HWND = Process.GetCurrentProcess().MainWindowHandle;
                var helper = new WindowInteropHelper(w) { Owner = HWND };
        }
    }
  8. Si bien algunas de estas funciones son genéricas ( ShowMessage, AskBooleanQuestionetc.), otras son específicas de este proyecto y utilizan Windows personalizados . Puede agregar más ventanas personalizadas de la misma manera. La clave es mantener los elementos específicos de la interfaz de usuario en la capa de Vista y simplemente exponer los datos devueltos utilizando POCO en la capa de VM .
  9. Realice el registro de IoC en su interfaz en la capa View utilizando esta clase. Puede hacer esto en el constructor de su vista principal (después de la InitializeComponent()llamada):

    SimpleIoc.Default.Register<IDialogService, DialogPresenter>();
  10. Ahí tienes. Ahora tiene acceso a todas sus funciones de diálogo en las capas VM y View. Su capa de VM puede llamar a estas funciones de esta manera:

    var NoTrump = ViewModelLocator.DialogService.AskBooleanQuestion("Really stop the trade war???", "");
  11. Tan limpio que ves. La capa VM no sabe nada acerca de cómo la capa de interfaz de usuario presentará una pregunta Sí / No al usuario y aún puede funcionar con éxito con el resultado devuelto del diálogo.

Otros beneficios gratis

  1. Para escribir pruebas unitarias, puede proporcionar una implementación personalizada de IDialogServicesu proyecto de prueba y registrar esa clase en IoC en el constructor de su clase de prueba.
  2. Deberá importar algunos espacios de nombres, como Microsoft.Win32acceder a los cuadros de diálogo Abrir y Guardar. Los he dejado fuera porque también hay una versión WinForms de estos cuadros de diálogo disponible, además de que alguien podría querer crear su propia versión. También tenga en cuenta que algunos de los identificadores utilizados DialogPresenterson nombres de mis propias ventanas (por ejemplo SettingsWindow). Deberá eliminarlos tanto de la interfaz como de la implementación o proporcionar sus propias ventanas.
  3. Si su VM realiza subprocesos múltiples, llame a MVVM Light al DispatcherHelper.Initialize()inicio del ciclo de vida de su aplicación.
  4. Excepto por el DialogPresenterque se inyecta en la capa de Vista, se deben registrar otros ViewModals ViewModelLocatory luego se debe exponer una propiedad estática pública de ese tipo para que la capa de Vista la consuma. Algo como esto:

    public static SettingsVM Settings => SimpleIoc.Default.GetInstance<SettingsVM>();
  5. En su mayor parte, sus cuadros de diálogo no deberían tener ningún código subyacente para cosas como el enlace o la configuración de DataContext, etc. Ni siquiera debería pasar cosas como parámetros de constructor. XAML puede hacer todo eso por ti, así:

    <Window x:Class="YourViewNamespace.SettingsWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:local="clr-namespace:YourViewProject"
      xmlns:vm="clr-namespace:YourVMProject;assembly=YourVMProject"
      DataContext="{x:Static vm:ViewModelLocator.Settings}"
      d:DataContext="{d:DesignInstance Type=vm:SettingsVM}" />
  6. Configurar de DataContextesta manera le brinda todo tipo de beneficios en tiempo de diseño, como Intellisense y autocompletado.

Espero que ayude a todos.

punto net
fuente
0

Estaba pensando en un problema similar al preguntar cómo debería ser el modelo de vista para una tarea o diálogo .

Mi solución actual se ve así:

public class SelectionTaskModel<TChoosable> : ViewModel
    where TChoosable : ViewModel
{
    public SelectionTaskModel(ICollection<TChoosable> choices);
    public ReadOnlyCollection<TChoosable> Choices { get; }
    public void Choose(TChoosable choosen);
    public void Abort();
}

Cuando el modelo de vista decide que se requiere la entrada del usuario, muestra una instancia de SelectionTaskModellas posibles opciones para el usuario. La infraestructura se encarga de mostrar la vista correspondiente, que en el momento adecuado llamará a la Choose()función con la elección del usuario.

David Schmitt
fuente
0

Luché con el mismo problema. Se me ocurrió una forma de intercomunicarse entre View y ViewModel. Puede iniciar el envío de un mensaje desde ViewModel a View para indicarle que muestre un cuadro de mensaje e informará de nuevo con el resultado. Entonces ViewModel puede responder al resultado devuelto por la Vista.

Lo demuestro en mi blog :

Dan está jugando con la luz del fuego
fuente
0

Escribí un artículo bastante completo sobre este mismo tema y también desarrollé una biblioteca emergente para cuadros de diálogo MVVM. La estricta adherencia a MVVM no solo es posible, sino que también es muy limpia cuando se implementa correctamente, y puede extenderse fácilmente a bibliotecas de terceros que no se adhieren a ella:

https://www.codeproject.com/Articles/820324/Implementing-Dialog-Boxes-in-MVVM

Mark Feldman
fuente
0

Lo siento, pero tengo que intervenir. He pasado por varias de las soluciones sugeridas, antes de encontrar el espacio de nombres Prism.Wpf.Interactivity en el proyecto Prism. Puede usar las solicitudes de interacción y la acción de la ventana emergente para desplegar una ventana personalizada o para necesidades más simples, hay ventanas emergentes de notificación y confirmación integradas. Estos crean ventanas verdaderas y se administran como tales. puede pasar un objeto de contexto con cualquier dependencia que necesite en el cuadro de diálogo. Utilizamos esta solución en mi trabajo desde que la encontré. Tenemos numerosos desarrolladores senior aquí y a nadie se le ocurrió nada mejor. Nuestra solución anterior era el servicio de diálogo en una superposición y el uso de una clase de presentador para hacerlo posible, pero tenía que tener fábricas para todos los modelos de vista de diálogo, etc.

Esto no es trivial, pero tampoco es súper complicado. Y está integrado en Prism y, por lo tanto, es la mejor (o mejor) práctica en mi humilde opinión.

¡Mis 2 centavos!

jogi
fuente
-1

EDITAR: sí, estoy de acuerdo en que este no es un enfoque MVVM correcto y ahora estoy usando algo similar a lo sugerido por blindmeis.

Una de las formas en que podrías hacer esto es

En su Modelo de vista principal (donde abre el modal):

void OpenModal()
{
    ModalWindowViewModel mwvm = new ModalWindowViewModel();
    Window mw = new Window();
    mw.content = mwvm;
    mw.ShowDialog()
    if(mw.DialogResult == true)
    { 
        // Your Code, you can access property in mwvm if you need.
    }
}

Y en su ventana de vista modal / modelo de vista:

XAML:

<Button Name="okButton" Command="{Binding OkCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">OK</Button>
<Button Margin="2" VerticalAlignment="Center" Name="cancelButton" IsCancel="True">Cancel</Button>

ViewModel:

public ICommand OkCommand
{
    get
    {
        if (_okCommand == null)
        {
            _okCommand = new ActionCommand<Window>(DoOk, CanDoOk);
        }
        return _okCommand ;
    }
}

void DoOk(Window win)
{
    <!--Your Code-->
    win.DialogResult = true;
    win.Close();
}

bool CanDoOk(Window win) { return true; }

o similar a lo que se publica aquí WPF MVVM: Cómo cerrar una ventana

Simone
fuente
2
No era el voto negativo, pero sospecho que es porque el modelo de vista tiene una referencia directa a la vista.
Brian Gideon
@BrianGideon, gracias por tu comentario. Estoy de acuerdo en que esta no es una solución desacoplada. De hecho, no estoy usando algo similar a whar sugerido por blindmeis. Gracias de nuevo.
Simone
Es una mala forma de alcanzar la vista cuando es tan fácil no hacerlo.
Chris Bordeman