2010-09-10 19 views
117

Me gustaría gestionar 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.Manejo del evento de cierre de ventana con WPF/MVVM Light Toolkit

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.

he intentado:

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

Con una RelayCommand asociado en mi modelo de vista pero no funciona (el código del comando no se ejecuta).

+3

También está interesado en una buena respuesta para responder a esto. – Sekhat

+3

Descargué el código de Codeplex y lo depuré revelé: "No se pudo convertir el objeto del tipo 'System.ComponentModel.CancelEventArgs' para escribir 'System.Windows.RoutedEventArgs'." Funciona bien si ** no ** quiere el CancelEventArgs pero eso no responde a su pregunta ... –

+0

Supongo que su código no funciona porque el control al que conectó su desencadenador no funciona tener un evento de cierre. Su contexto de datos no es una ventana ... Probablemente es una plantilla de datos con una cuadrícula o algo así, que no tiene un evento de Cierre. Entonces la respuesta de dbkk es la mejor respuesta en este caso. Sin embargo, prefiero el enfoque Interaction/EventTrigger cuando el evento está disponible. – NielW

Respuesta

3

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

Por ejemplo, usted podría entonces tener algo como el siguiente código en el archivo de 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 que podría tener el 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] 
+1

Gracias por la respuesta detallada. Sin embargo, no creo que eso resuelva mi problema: necesito manejar el cierre de la ventana cuando el usuario hace clic en el botón superior derecho 'X'. Sería fácil hacer esto en el código subyacente (me limitaría a vincular el evento de cierre y establecer CancelEventArgs.Cancel en verdadero o falso), pero me gustaría hacer esto en estilo MVVM. Perdón por la confusión –

1

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 conservar 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()

1

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); 
     } 
    } 
} 
+1

¿Qué pasará aquí en el escenario donde el V-M desea cancelar el cierre? –

61

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.

+1

He olvidado: para obtener los argumentos del evento en el comando use PassEventArgsToCommand = "True" – Stas

+1

+1 enfoque simple y convencional.Sería aún mejor dirigirse a PRISM. –

+11

Este es un escenario que resalta los enormes agujeros en WPF y MVVM. – Damien

9

Vaya, parece que hay un montón de código 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) ...Oh y el PassEventArgsToCommand = "Verdadero" es definitivamente 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 ve algo como lo siguiente, pero basicallyRequestShutdown o lo que sea se nombra decide si apagar el a plicación o no (que se cerrará la ventana alegremente 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); 
    } 
    } 
28

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

Application.Current.MainWindow.Closing += new CancelEventHandler(MainWindow_Closing); 

void MainWindow_Closing(object sender, CancelEventArgs e) 
{ 
      //Your code to handle the event 
} 

Todo lo mejor.

+0

Esta es la mejor solución entre las otro mencionado en esta edición. Gracias ! – Jacob

+0

Esto es lo que estaba buscando. ¡Gracias! –

+0

¿Por qué entonces las personas usan métodos más engorrosos mientras existe un enfoque tan directo? Gracias. – Saeid

95

yo simplemente asociar el controlador en la vista de constructor:

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

Luego agregar el controlador a la ViewModel:

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

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

El mantra "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, el ViewModel no depende de la Vista y la lógica de cierre se puede probar en unidades.

+2

Me gusta [esto] (http://stephantodd.blogspot.ch/2010/04/how-to-handle-wpf-events-with-mvvm-on.html) solución: simplemente enganchar en un botón oculto :) – Benjol

+3

Para los principiantes de mvvm que no usan MVVMLight y que buscan cómo informar al modelo de vista sobre el evento de cierre, los enlaces sobre cómo configurar el dataContext correctamente y cómo obtener el objeto viewModel en la vista pueden ser interesantes. [¿Cómo obtener una referencia al ViewModel en la vista?] (Http://stackoverflow.com/a/9789644/3090544) y [Cómo configuro un ViewModel en una ventana en xaml usando la propiedad datacontext] (http://stackoverflow.com/a/4590471/3090544) ... Me tomó varias horas, cómo se podría manejar un simple evento de cierre de ventana en ViewModel. – MarkusEgle

+0

Me encanta esta elegante solución. Funcionó fabulosamente para mí. ¡Gracias! –

5

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 el modelo de vista añadir 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 acto de clausura. 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(); 
    } 
} 
+0

Simple, claro y limpio. El ViewModel no necesita conocer detalles específicos de la vista, por lo que las preocupaciones se mantienen separadas. –

+0

acaba de utilizar esto, gracias! – PmanAce

5

El autor de la pregunta debe utilizar respuesta STAS, pero para los lectores que utilizan prisma y sin galasoft/mvvmlight, es posible que desee probar lo que he utilizado:

En la definición en la parte superior de la ventana o control de usuario, etc definir 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 modelo de vista:

public ICommand WindowClosing { get; private set; } 

Acople DelegateCommand en su constructor modelo de vista:

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

Finalmente, el código que desea alcanzar en el cierre del control/ventana/lo que sea:

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

Esto no da acceso a CancelEventArgs que es necesario para cancelar el evento de cierre. El objeto pasado es el modelo de vista, que técnicamente es el mismo modelo de vista desde el que se ejecuta el comando WindowClosing. – stephenbayer

+0

¡Gracias por mostrar dónde conseguir 'i'! –

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

Hola, agregue un poco de explicación junto con el código, ya que ayuda a comprender su código. [Las respuestas del código solo están mal vistas] (http://meta.stackexchange.com/questions/148272/is-there-any-benefit-to-allowing-code-only-answers-while-blocking-code-only-ques) –

+0

La op declaró explícitamente que no estaba interesado en usar código de evento de código subyacente para esto. –

1

Uso de MVVM Light Toolkit:

Suponiendo que hay una salida mando en vista del modelo:

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

Esta es recibido en la vista:

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

Por otro lado, manejar Closing evento en MainWindow, utilizando el instancia de ViewModel:

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

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

Espero que ayude a alguien.

Cuestiones relacionadas