.net wpf design-patterns mvvm dialog

.net - Manejo de diálogos en WPF con MVVM



design-patterns dialog (23)

En el patrón MVVM para WPF, el manejo de los 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 un ICommand que 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 ventanas de diálogo 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 fuera necesario 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 estar alejado.


¿Por qué no solo generar un evento en la máquina virtual y suscribirse al evento en la vista? Esto mantendría la lógica de la aplicación y la vista separadas y aún así le permitiría usar una ventana secundaria para los diálogos.


Creo que el manejo de un diálogo debe 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 los diálogos, entonces ViewModel depende de esa implementación. La forma más sencilla de lidiar con este problema es hacer que View 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 de negocios de la GUI, por lo que no debería mezclar la lógica de GUI (para mostrar un diálogo) en la capa de negocios (el Modelo de Vista).


Creo que la vista podría tener un 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 eventos del modelo y una o más acciones para invocar en respuesta.


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

Debes revisar el grupo de Discípulos de WPF de Google y solo buscar el Mediador. Estarás muy contento con las respuestas ...

Sin embargo puedes comenzar con esto:

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

Disfrutar

Edición: puede ver la respuesta a este problema con MVVM Light Toolkit aquí:

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


Después de pasar un tiempo con él, finalmente se me ocurrió la siguiente solución. Algunas ventajas clave de este enfoque son:

  1. Implementa el IDialogService de MVVM Light.
  2. La vista no necesita agregar la referencia de MVVM Light.
  3. VM no necesita hacer ninguna actividad de nivel de presentación. Ni siquiera necesita PresentationFramework referencia.
  4. Utiliza el canal de Messenger de MVVM Light, por lo que la presentación y las capas de VM se desacoplan.
  5. Admite diálogos con un valor de retorno, como preguntas Sí / No o situaciones Aceptar / Cancelar.
  6. Soporta diálogos personalizados.

Aquí está la implementación de IDialogService (entra en el proyecto ViewModel ):

using System; using System.Linq; using System.Threading.Tasks; namespace VM { public enum MessageBoxButtonVM { OK, OKCancel, YesNo } public enum MessageBoxImageVM { None, Information, Question, Error } public class MessageBoxArgs { public MessageBoxButtonVM Buttons { get; set; } public MessageBoxImageVM Icon { get; set; } public string Title { get; set; } public string Message { get; set; } } //For custom dialogs that return a value public class MessageBoxNotificationWithAction<T> { private readonly Action<T> _callback; public MessageBoxArgs Notification { get; set; } public MessageBoxNotificationWithAction(MessageBoxArgs notification, Action<T> callback) { Notification = notification; CheckCallback(callback); _callback = callback; } public virtual void Execute(T argument) { _callback.Invoke(argument); } private static void CheckCallback(Delegate callback) { if (callback == null) { throw new ArgumentNullException(nameof(callback), "Callback must not be null"); } } } /// <summary> /// Provides an implementation-agnostic way of communicating with the user through dialog boxes. Clients must register for communication messages using /// MVVM Light messaging system. /// </summary> public class DialogService : GalaSoft.MvvmLight.Views.IDialogService { private static GalaSoft.MvvmLight.Messaging.IMessenger Messenger = GalaSoft.MvvmLight.Messaging.Messenger.Default; private string _ProductName = ""; public string ProductName { get { if (_ProductName == "") { //The following statement returns the Title attribute of the current assembly, as defined in project properties (Assembly Information dialog). var TitleAttrib = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributesData().First(x => x.AttributeType.Name == "AssemblyTitleAttribute"); if (TitleAttrib != null) { _ProductName = TitleAttrib.ConstructorArguments[0].Value.ToString(); } else { _ProductName = "Default Application Name"; } } return _ProductName; } } public Task ShowError(Exception error, string title, string buttonText, Action afterHideCallback) { return ShowError(error.Message, title, buttonText, afterHideCallback); } public Task ShowMessage(string message, string title) { return Task.Run(() => MessengerSend(message, title, MessageBoxButtonVM.OK, MessageBoxImageVM.Error)); } public Task ShowError(string message, string title, string buttonText, Action afterHideCallback) { return Task.Run(() => { MessengerSend(message, title, MessageBoxButtonVM.OK, MessageBoxImageVM.Error); afterHideCallback?.Invoke(); }); } public Task ShowMessage(string message, string title, string buttonText, Action afterHideCallback) { return Task.Run(() => { MessengerSend(message, title); afterHideCallback?.Invoke(); }); } public Task<bool> ShowMessage(string message, string title, string buttonConfirmText, string buttonCancelText, Action<bool> afterHideCallback) { if ((buttonConfirmText == "OK" && buttonCancelText == "Cancel") || (buttonConfirmText == "Yes" && buttonCancelText == "No")) { return Task.Run<bool>(() => { MessageBoxButtonVM btn; if (buttonConfirmText == "OK") btn = MessageBoxButtonVM.OKCancel; else btn = MessageBoxButtonVM.YesNo; bool Response = false; Messenger.Send(new MessageBoxNotificationWithAction<bool>( new MessageBoxArgs() { Buttons = btn, Icon = MessageBoxImageVM.Question, Title = (string.IsNullOrEmpty(title) ? _ProductName : title), Message = message }, (result) => Response = result )); afterHideCallback?.Invoke(Response); return Response; }); } else throw new ArgumentException($"{nameof(buttonConfirmText)} and {nameof(buttonCancelText)} must either be OK/Cancel or Yes/No."); } /// <summary> /// For debugging purpose only /// </summary> /// <param name="message"></param> /// <param name="title"></param> /// <returns></returns> public Task ShowMessageBox(string message, string title) => ShowMessage(message, title); private void MessengerSend(string msg, string title = "", MessageBoxButtonVM btn = MessageBoxButtonVM.OK, MessageBoxImageVM icon = MessageBoxImageVM.Information) { Messenger.Send(new MessageBoxArgs() { Buttons = MessageBoxButtonVM.OK, Icon = MessageBoxImageVM.Information, Title = (string.IsNullOrEmpty(title) ? _ProductName : title), Message = msg }); } } }

Aquí está la capa de presentación (entra en Ver proyecto)

using System.Windows; using VM; namespace View { class DialogPresenter { private Window _Parent; public DialogPresenter() { //For simple information boxes GalaSoft.MvvmLight.Messaging.Messenger.Default.Register<MessageBoxArgs>(this, (arg) => ShowDialog(arg)); //For Yes/No or OK/Cancel dialog boxes. GalaSoft.MvvmLight.Messaging.Messenger.Default.Register<MessageBoxNotificationWithAction<bool>>(this, (arg) => arg.Execute(ShowDialog(arg.Notification))); //For notifications that require a string response (such as Manual Timeslot Description) GalaSoft.MvvmLight.Messaging.Messenger.Default.Register<MessageBoxNotificationWithAction<string>>(this, (arg) => arg.Execute(ShowStringInputDialog(arg.Notification.Title, arg.Notification.Message))); } private bool ShowDialog(MessageBoxArgs arg) { MessageBoxButton btn = MessageBoxButton.OK; MessageBoxImage ico = MessageBoxImage.None; switch (arg.Buttons) { case MessageBoxButtonVM.OK: btn = MessageBoxButton.OK; break; case MessageBoxButtonVM.OKCancel: btn = MessageBoxButton.OKCancel; break; case MessageBoxButtonVM.YesNo: btn = MessageBoxButton.YesNo; break; } switch (arg.Icon) { case MessageBoxImageVM.Error: ico = MessageBoxImage.Error; break; case MessageBoxImageVM.Information: ico = MessageBoxImage.Information; break; case MessageBoxImageVM.None: ico = MessageBoxImage.None; break; case MessageBoxImageVM.Question: ico = MessageBoxImage.Question; break; } bool Result = false; _Parent.Dispatcher.Invoke(() => { var Res = MessageBox.Show(arg.Message, arg.Title, btn, ico); Result = (Res == MessageBoxResult.OK || Res == MessageBoxResult.Yes); }); return Result; } private string ShowStringInputDialog(string title, string description, string value = "", int maxLength = 100) { string Result = null; _Parent.Dispatcher.Invoke(() => { //InputBox is a WPF Window I created for taking simple //string values from the user. This also shows that you can //any custom dialog using this approach. InputBox input = new InputBox(); input.Title = title; input.Owner = _Parent; if (input.ShowDialog(description, value, maxLength).Value) Result=input.Value; else Result=null; }); return Result; } //Call this somewhere at application startup so that the dialog boxes //appear as child windows. public void SetParentWindow(Window parent) { _Parent = parent; } } }


Estaba pensando en un problema similar al preguntar cómo debería verse 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, se abre una instancia de SelectionTaskModel con las posibles opciones para el usuario. La infraestructura se ocupa de mostrar la vista correspondiente, que en el momento adecuado llamará a la función Choose() con la elección del usuario.


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

SERVICIO DE DIALOGO

a) una interfaz de servicio de diálogo como a través de un 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 algo de control en la ventana activa), crear una vista correspondiente al nombre del tipo dlgVm dado (usar registro o convención del contenedor o un ContentPresenter con el tipo de DataTemplates asociado). ShowDialogAsync debe crear un TaskCompletionSource y devolver su propiedad .Task. La propia clase DialogViewModel necesita un evento que pueda invocar en la clase derivada cuando desee cerrar, y ver en la vista de diálogo para cerrar / ocultar el cuadro de diálogo y completar el TaskCompletionSource.

b) Para usar, simplemente llame a wait this.DialogService.ShowDialog (myDlgVm) en su instancia de alguna clase derivada de DialogViewModel. Después de esperar devoluciones, observe las propiedades que ha agregado en su cuadro de diálogo VM para determinar qué sucedió; Ni siquiera necesitas una devolución de llamada.

VER AYUDA

Esto tiene su vista escuchando un evento en el modelo de vista. Todo esto podría incluirse en un Comportamiento de mezcla para evitar el uso de código y recursos si lo desea (FMI, subclase de la clase "Comportamiento" para ver una especie de propiedad adjunta de Blendable en esteroides). Por ahora, haremos esto manualmente en cada vista:

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

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

c) Cuando se dispare el evento, haga que la vista abra su vista, que podría estar en un recurso en su página, o puede 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 utilizar. No lo uso mucho. La única ventaja es la capacidad de colocar la vista físicamente dentro de una pestaña, por ejemplo. He utilizado un algoritmo para colocarlo en los límites del control del usuario actual, o si no es lo suficientemente grande, recorrer el árbol visual hasta que se encuentre un contenedor lo suficientemente grande.

Esto permite que los diálogos estén cerca del lugar en el que realmente se usan, 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 presionar manualmente los diálogos, incluso tener varios cuasi. Los diálogos modales se abren en diferentes pestañas o sub-vistas.


He escrito un artículo bastante completo sobre este mismo tema y también he desarrollado una biblioteca emergente para diálogos de MVVM. La adherencia estricta a MVVM no solo es posible sino también muy limpia cuando se implementa correctamente, y se puede extender fácilmente a las bibliotecas de terceros que no se adhieren a ellas mismas:

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



Karl Shifflett ha creado una aplicación de ejemplo para mostrar cuadros de diálogo utilizando el enfoque de servicio y el enfoque Prism InteractionRequest.

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 de WinForms de mi aplicación (MessageBox.Show). Pero si planea mostrar muchos diálogos diferentes, InteractionRequest es un mejor manera de ir

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


Lo siento, pero tengo que intervenir. He revisado varias de las soluciones sugeridas, antes de encontrar el espacio de nombres de 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 gestionan como tales. puede pasar un objeto de contexto con cualquier dependencia que necesite en el diálogo. Usamos esta solución en mi trabajo desde que la encontré. Tenemos numerosos desarrolladores senior aquí y nadie ha encontrado nada mejor. Nuestra solución anterior fue el servicio de diálogo en una superposición y el uso de una clase de presentador para hacerlo realidad, pero tenía que tener fábricas para todos los modelos de diálogo, etc.

Esto no es trivial pero tampoco es super complicado. Y está integrado en Prism y, por lo tanto, es la mejor (o mejor) práctica IMHO.

Mis 2 centavos!


Luché con el mismo problema. He encontrado una manera de intercomunicar entre la vista y el ViewModel. Puede iniciar el envío de un mensaje desde el ViewModel a la Vista para indicarle que muestre un buzón de mensajes y se informará con el resultado. Luego, el ViewModel puede responder al resultado devuelto por la Vista.

Lo demuestro en mi blog :


Mi solución actual resuelve la mayoría de los problemas que mencionó, pero está completamente abstraída de elementos específicos de la plataforma y se puede reutilizar. Además, no utilicé ningún enlace de código detrás solo con DelegateCommands que implementa ICommand. El diálogo es básicamente una vista: un control independiente 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


Realmente luché con este concepto durante 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 lo siguiente:

Mi idea original fue que no se debería permitir que un ViewModel llame a un cuadro de diálogo directamente, ya que no tiene por qué decidir cómo debe aparecer un diálogo. Debido a esto, comencé a pensar en cómo podría pasar mensajes como lo haría en MVP (por ejemplo, View.ShowSaveFileDialog ()). Sin embargo, creo que este es el enfoque equivocado.

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

Por lo tanto, lo que debe suceder es que durante la prueba se use 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 un simulacro de prueba que tendrá un comportamiento predeterminado.

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

Usando este enfoque, su ViewModel aún se puede probar y, dependiendo de cómo se burle de sus diálogos, puede controlar el comportamiento.

Espero que esto ayude.



Sé que es una pregunta antigua, pero cuando hice esta búsqueda, encontré muchas preguntas relacionadas, pero no encontré una respuesta muy clara. ¡Así que hago mi propia implementación de un cuadro de diálogo / mensaje / popin, y lo comparto!
Creo que es "a prueba de MVVM", y trato de hacerlo simple y apropiado, 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:


Sugiero dejar de lado los diálogos modales de la década de 1990 y, en su lugar, implementar un control como superposición (lienzo + posicionamiento absoluto) con visibilidad vinculada a un booleano en la máquina virtual. 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 de usercontrol detrás. (Ya que tengo mis vistas en un archivo .exe y ViewModels en una DLL, no me siento mal por el código que manipula la interfaz de usuario).



Un buen diálogo de MVVM debería:

  1. Ser declarado con sólo XAML.
  2. Obtener todo su comportamiento de enlace de datos.

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

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

Es realmente simple de usar y no requiere cambios extraños 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 quieras agregar un estilo que establezca Mostrar. Lo explico en mi artículo. Espero que esto te ayude.


Una alternativa interesante es usar controladores que sean responsables de mostrar las vistas (diálogos).

Cómo funciona esto se muestra en WPF Application Framework (WAF) .


Utilice un comando de congelación

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


Yo uso this 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);


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

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

En tu Modelo de Vista Principal (donde abres 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 tu ventana de Modal View / ViewModel:

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