mvvmlight galasoft c# wpf mvvm mvvm-light

c# - galasoft - nuget mvvmlight



Manejo del evento de cierre de ventana con WPF/MVVM Light Toolkit (12)

Me gustaría manejar el evento "Cierre" (cuando un usuario hace clic en el botón "X" superior derecho) de mi ventana para mostrar eventualmente un mensaje de confirmación o / y cancelar el cierre.

Sé cómo hacer esto en el código subyacente: suscríbase al evento "Cierre" de la ventana y luego use la propiedad "CancelarEventArgs.Cancel".

Pero estoy usando MVVM, así que no estoy seguro de que sea el mejor enfoque.

Creo que el buen enfoque sería vincular el evento de cierre a un comando en mi ViewModel.

Intenté eso:

<i:Interaction.Triggers> <i:EventTrigger EventName="Closing"> <cmd:EventToCommand Command="{Binding CloseCommand}" /> </i:EventTrigger> </i:Interaction.Triggers>

Con un RelayCommand asociado en mi ViewModel pero no funciona (el código del comando no se ejecuta).


Aquí hay una respuesta de acuerdo con el patrón MVVM si no desea conocer la Ventana (o cualquiera de sus eventos) en ViewModel.

public interface IClosing { /// <summary> /// Executes when window is closing /// </summary> /// <returns>Whether the windows should be closed by the caller</returns> bool OnClosing(); }

En ViewModel agregue la interfaz y la implementación

public bool OnClosing() { bool close = true; //Ask whether to save changes och cancel etc //close = false; //If you want to cancel close return close; }

En la Ventana agrego el evento de Cierre. Este código detrás no rompe el patrón MVVM. ¡La vista puede saber sobre el modelo de vista!

void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { IClosing context = DataContext as IClosing; if (context != null) { e.Cancel = !context.OnClosing(); } }


Básicamente, el evento de ventana no se puede asignar a MVVM. En general, el botón Cerrar muestra un cuadro de diálogo para pedir al usuario "guardar: sí / no / cancelar", y esto puede no ser logrado por MVVM.

Puede mantener el controlador de eventos OnClosing, donde llama al Model.Close.CanExecute () y establece el resultado booleano en la propiedad del evento. Entonces, después de la llamada CanExecute () si es verdadera, O en el evento OnClosed, llame al Model.Close.Execute ()


El asker debería usar la respuesta STAS, pero para los lectores que usan prism y no galasoft / mvvmlight, es posible que quieran probar lo que he usado:

En la definición en la parte superior para ventana o usercontrol, etc. define el espacio de nombres:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

Y justo debajo de esa definición:

<i:Interaction.Triggers> <i:EventTrigger EventName="Closing"> <i:InvokeCommandAction Command="{Binding WindowClosing}" CommandParameter="{Binding}" /> </i:EventTrigger> </i:Interaction.Triggers>

Propiedad en su viewmodel:

public ICommand WindowClosing { get; private set; }

Adjunte delegatecommand en su constructor de viewmodel:

this.WindowClosing = new DelegateCommand<object>(this.OnWindowClosing);

Finalmente, su código al que desea llegar cerca del control / ventana / lo que sea:

private void OnWindowClosing(object obj) { //put code here }


Esta opción es aún más fácil, y tal vez es adecuada para usted. En el constructor de su modelo de vista, puede suscribirse al evento de cierre de la ventana principal de esta manera:

Application.Current.MainWindow.Closing += new CancelEventHandler(MainWindow_Closing); void MainWindow_Closing(object sender, CancelEventArgs e) { //Your code to handle the event }

Todo lo mejor.


Este código funciona bien:

ViewModel.cs:

public ICommand WindowClosing { get { return new RelayCommand<CancelEventArgs>( (args) =>{ }); } }

y en XAML:

<i:Interaction.Triggers> <i:EventTrigger EventName="Closing"> <command:EventToCommand Command="{Binding WindowClosing}" PassEventArgsToCommand="True" /> </i:EventTrigger> </i:Interaction.Triggers>

y, por supuesto, ViewModel se asigna a un DataContext del contenedor principal.


Me sentiría tentado de usar un controlador de eventos dentro de su archivo App.xaml.cs que le permitirá decidir si cerrar la aplicación o no.

Por ejemplo, podría tener algo así como el siguiente código en su archivo App.xaml.cs:

protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); // Create the ViewModel to attach the window to MainWindow window = new MainWindow(); var viewModel = new MainWindowViewModel(); // Create the handler that will allow the window to close when the viewModel asks. EventHandler handler = null; handler = delegate { //***Code here to decide on closing the application**** //***returns resultClose which is true if we want to close*** if(resultClose == true) { viewModel.RequestClose -= handler; window.Close(); } } viewModel.RequestClose += handler; window.DataContaxt = viewModel; window.Show(); }

Luego, dentro de su código MainWindowViewModel podría tener lo siguiente:

#region Fields RelayCommand closeCommand; #endregion #region CloseCommand /// <summary> /// Returns the command that, when invoked, attempts /// to remove this workspace from the user interface. /// </summary> public ICommand CloseCommand { get { if (closeCommand == null) closeCommand = new RelayCommand(param => this.OnRequestClose()); return closeCommand; } } #endregion // CloseCommand #region RequestClose [event] /// <summary> /// Raised when this workspace should be removed from the UI. /// </summary> public event EventHandler RequestClose; /// <summary> /// If requested to close and a RequestClose delegate has been set then call it. /// </summary> void OnRequestClose() { EventHandler handler = this.RequestClose; if (handler != null) { handler(this, EventArgs.Empty); } } #endregion // RequestClose [event]


No he hecho muchas pruebas con esto, pero parece que funciona. Esto es lo que se me ocurrió:

namespace OrtzIRC.WPF { using System; using System.Windows; using OrtzIRC.WPF.ViewModels; /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { private MainViewModel viewModel = new MainViewModel(); private MainWindow window = new MainWindow(); protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); viewModel.RequestClose += ViewModelRequestClose; window.DataContext = viewModel; window.Closing += Window_Closing; window.Show(); } private void ViewModelRequestClose(object sender, EventArgs e) { viewModel.RequestClose -= ViewModelRequestClose; window.Close(); } private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { window.Closing -= Window_Closing; viewModel.RequestClose -= ViewModelRequestClose; //Otherwise Close gets called again viewModel.CloseCommand.Execute(null); } } }


Simplemente asociaría el controlador en el constructor de View:

MyWindow() { // Set up ViewModel, assign to DataContext etc. Closing += viewModel.OnWindowClosing; }

A continuación, agregue el controlador al ViewModel :

public void OnWindowClosing(object sender, CancelEventArgs e) { // Handle closing logic, set e.Cancel as needed }

En este caso, no se gana exactamente nada excepto la complejidad mediante el uso de un patrón más elaborado con más direccionamiento indirecto (5 líneas adicionales de patrón XML más comando).

El mantra de "cero código detrás" no es el objetivo en sí mismo, el punto es desacoplar ViewModel de la Vista . Incluso cuando el evento está vinculado en código subyacente de la Vista, ViewModel no depende de la Vista y la lógica de cierre se puede probar en unidades .


Usando MVVM Light Toolkit:

Suponiendo que hay un comando de salida en el modelo de vista:

ICommand _exitCommand; public ICommand ExitCommand { get { if (_exitCommand == null) _exitCommand = new RelayCommand<object>(call => OnExit()); return _exitCommand; } } void OnExit() { var msg = new NotificationMessageAction<object>(this, "ExitApplication", (o) =>{}); Messenger.Default.Send(msg); }

Esto se recibe en la vista:

Messenger.Default.Register<NotificationMessageAction<object>>(this, (m) => if (m.Notification == "ExitApplication") { Application.Current.Shutdown(); });

Por otro lado, manejo el evento Closing en MainWindow , usando la instancia de ViewModel:

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { if (((ViewModel.MainViewModel)DataContext).CancelBeforeClose()) e.Cancel = true; }

CancelBeforeClose comprueba el estado actual del modelo de vista y devuelve verdadero si se debe detener el cierre.

Espero que ayude a alguien.



Vaya, parece que hay muchos códigos aquí para esto. Stas arriba tenía el enfoque correcto para un mínimo esfuerzo. Aquí está mi adaptación (usando MVVMLight pero debería ser reconocible) ... Ah, y PassEventArgsToCommand = "True" definitivamente es necesario como se indicó anteriormente.

(crédito a Laurent Bugnion http://blog.galasoft.ch/archive/2009/10/18/clean-shutdown-in-silverlight-and-wpf-applications.aspx )

... MainWindow Xaml ... WindowStyle="ThreeDBorderWindow" WindowStartupLocation="Manual"> <i:Interaction.Triggers> <i:EventTrigger EventName="Closing"> <cmd:EventToCommand Command="{Binding WindowClosingCommand}" PassEventArgsToCommand="True" /> </i:EventTrigger> </i:Interaction.Triggers>

En el modelo de vista:

///<summary> /// public RelayCommand<CancelEventArgs> WindowClosingCommand ///</summary> public RelayCommand<CancelEventArgs> WindowClosingCommand { get; private set; } ... ... ... // Window Closing WindowClosingCommand = new RelayCommand<CancelEventArgs>((args) => { ShutdownService.MainWindowClosing(args); }, (args) => CanShutdown);

en el ShutdownService

/// <summary> /// ask the application to shutdown /// </summary> public static void MainWindowClosing(CancelEventArgs e) { e.Cancel = true; /// CANCEL THE CLOSE - let the shutdown service decide what to do with the shutdown request RequestShutdown(); }

RequestShutdown se parece a lo siguiente, pero basicRequestShutdown o cualquiera que sea su nombre decide si cerrar la aplicación o no (lo que cerrará felizmente la ventana de todos modos):

... ... ... /// <summary> /// ask the application to shutdown /// </summary> public static void RequestShutdown() { // Unless one of the listeners aborted the shutdown, we proceed. If they abort the shutdown, they are responsible for restarting it too. var shouldAbortShutdown = false; Logger.InfoFormat("Application starting shutdown at {0}...", DateTime.Now); var msg = new NotificationMessageAction<bool>( Notifications.ConfirmShutdown, shouldAbort => shouldAbortShutdown |= shouldAbort); // recipients should answer either true or false with msg.execute(true) etc. Messenger.Default.Send(msg, Notifications.ConfirmShutdown); if (!shouldAbortShutdown) { // This time it is for real Messenger.Default.Send(new NotificationMessage(Notifications.NotifyShutdown), Notifications.NotifyShutdown); Logger.InfoFormat("Application has shutdown at {0}", DateTime.Now); Application.Current.Shutdown(); } else Logger.InfoFormat("Application shutdown aborted at {0}", DateTime.Now); } }


private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { MessageBox.Show("closing"); }