web-dev-qa-db-fra.com

Gestion de l'événement de fermeture de fenêtre avec WPF / MVVM Light Toolkit

Je voudrais gérer l'événement "Clôture" (lorsqu'un utilisateur clique sur le bouton "X" en haut à droite) de ma fenêtre afin d'afficher un message de confirmation ou/et d'annuler la fermeture.

Je sais comment faire cela dans le code-behind: abonnez-vous à l'événement "Fermeture" de la fenêtre puis utilisez la propriété "CancelEventArgs.Cancel".

Mais j'utilise MVVM, donc je ne suis pas sûr que ce soit la bonne approche.

Je pense que la bonne approche serait de lier l'événement Closing à une commande dans mon ViewModel.

J'ai essayé ça:

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

Avec un RelayCommand associé dans mon ViewModel mais cela ne fonctionne pas (le code de la commande n'est pas exécuté).

135
Olivier Payen

J'associerais simplement le gestionnaire dans le constructeur de vue:

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

Ajoutez ensuite le gestionnaire à la ViewModel:

using System.ComponentModel;

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

Dans ce cas, vous n’obtenez absolument rien sauf la complexité en utilisant un modèle plus élaboré avec plus d’indirection (5 lignes supplémentaires de modèle XML plus).

Le mantra "code zéro" n'est pas le but en soi, le but est de découpler ViewModel de la vue. Même lorsque l'événement est lié dans le code-behind de la vue, la ViewModel ne dépend pas de la vue et de la logique de fermeture peut être testé par unité.

116
dbkk

Ce code fonctionne très bien:

ViewModel.cs:

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

et en XAML:

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

en admettant que

  • ViewModel est affecté à un DataContext du conteneur principal.
  • xmlns:command="clr-namespace:GalaSoft.MvvmLight.Command;Assembly=GalaSoft.MvvmLight.Extras.SL5"
  • xmlns:i="clr-namespace:System.Windows.Interactivity;Assembly=System.Windows.Interactivity"
75
Stas

Cette option est encore plus simple et peut-être vous convient-elle. Dans votre constructeur Modèle de vue, vous pouvez souscrire à l'événement de clôture de la fenêtre principale comme suit:

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

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

Bonne chance.

33
PILuaces

Voici une réponse selon le modèle MVVM si vous ne voulez pas connaître la fenêtre (ni aucun de ses événements) dans le ViewModel.

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

Dans le ViewModel, ajoutez l'interface et l'implémentation

public bool OnClosing()
{
    bool close = true;

    //Ask whether to save changes och cancel etc
    //close = false; //If you want to cancel close

    return close;
}

Dans la fenêtre, j'ajoute l'événement de clôture. Ce code derrière ne rompt pas le modèle MVVM. The View peut connaître le modèle de vue!

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

Bon sang, il semble que beaucoup de code soit utilisé ici pour cela. Stas ci-dessus avait la bonne approche pour un effort minimal. Voici mon adaptation (avec MVVMLight mais devrait être reconnaissable) ... Oh et le PassEventArgsToCommand = "True" est définitivement nécessaire comme indiqué ci-dessus.

(crédit à 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> 

Dans le modèle de vue:

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

dans le service d'arrêt

    /// <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 ressemble à ce qui suit, mais fondamentalementRequestShutdown ou le nom qui lui est attribué décide d’arrêter ou non l’application (ce qui fermera joyeusement la fenêtre de toute façon):

...
...
...
    /// <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);
    }
    }
10
AllenM

Le demandeur doit utiliser STAS answer, mais pour les lecteurs qui utilisent prisme et sans galasoft/mvvmlight, ils peuvent vouloir essayer ce que j’ai utilisé:

Dans la définition en haut pour window ou usercontrol, etc., définissez un espace de noms:

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

Et juste en dessous de cette définition:

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

Propriété dans votre viewmodel:

public ICommand WindowClosing { get; private set; }

Attachez delegatecommand dans votre constructeur viewmodel:

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

Enfin, le code que vous souhaitez atteindre à la fermeture du contrôle/de la fenêtre/quel que soit:

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

Je serais tenté d'utiliser un gestionnaire d'événements dans votre fichier App.xaml.cs qui vous permettra de décider de fermer ou non l'application.

Par exemple, vous pourriez alors avoir quelque chose comme le code suivant dans votre fichier 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();

}

Ensuite, dans votre code MainWindowViewModel, vous pouvez avoir les éléments suivants:

#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]
4
ChrisBD

Je n'ai pas fait beaucoup d'essais avec cela, mais cela semble fonctionner. Voici ce que je suis venu avec:

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
Brian Ortiz

Utilisation de MVVM Light Toolkit:

En supposant qu'il existe une commande Exit dans le modèle de vue:

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

Ceci est reçu dans la vue:

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

Par ailleurs, je gère l'événement Closing dans MainWindow, à l'aide de l'instance de ViewModel:

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

CancelBeforeClose vérifie l'état actuel du modèle de vue et renvoie true si la fermeture doit être arrêtée.

J'espère que ça aide quelqu'un.

1
Ron

Nous utilisons AttachedCommandBehavior pour cela. Vous pouvez attacher n'importe quel événement à une commande de votre modèle de vue en évitant tout code en retard.

Nous l'utilisons tout au long de notre solution et avons presque zéro code derrière

http://marlongrech.wordpress.com/2008/12/13/attachedcommandbehavior-v2-aka-acb/

1
Chris Adams

Fondamentalement, l'événement de fenêtre peut ne pas être affecté à MVVM. En général, le bouton Fermer affiche une boîte de dialogue demandant à l'utilisateur "enregistrer: oui/non/annuler", ce qui peut ne pas être réalisé par le MVVM.

Vous pouvez conserver le gestionnaire d'événement OnClosing dans lequel vous appelez Model.Close.CanExecute () et définissez le résultat booléen dans la propriété de l'événement. Ainsi, après l'appel CanExecute () si true, OR dans l'événement OnClosed, appelez Model.Close.Execute ()

1
Echtelion