155 votes

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

J'aimerais m'occuper de la Closing (lorsqu'un utilisateur clique sur le bouton supérieur droit 'X') de ma fenêtre afin d'afficher éventuellement un message de confirmation et/ou d'annuler la fermeture.

Je sais comment faire dans le code-behind : s'abonner à la liste de diffusion de l Closing de la fenêtre, puis utilisez la fonction CancelEventArgs.Cancel propriété.

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

Je pense que la bonne approche serait de lier la Closing à un Command dans mon ViewModel.

J'ai essayé :

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

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

3 votes

Je suis également intéressé par une bonne réponse à cette question.

3 votes

J'ai téléchargé le code de codeplex et le déboguer a révélé : "Unable to cast object of type 'System.ComponentModel.CancelEventArgs' to type 'System.Windows.RoutedEventArgs'." Cela fonctionne bien si vous Ne le fais pas. veulent les CancelEventArgs mais cela ne répond pas à votre question...

0 votes

Je suppose que votre code ne fonctionne pas parce que le contrôle auquel vous avez attaché votre déclencheur n'a pas d'événement de fermeture. Votre contexte de données n'est pas une fenêtre... C'est probablement un modèle de données avec une grille ou autre, qui n'a pas d'événement de fermeture. La réponse de dbkk est donc la meilleure dans ce cas. Cependant, je préfère l'approche Interaction/EventTrigger lorsque l'événement est disponible.

140voto

dbkk Points 5305

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

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

Ensuite, ajoutez le gestionnaire à l ViewModel :

using System.ComponentModel;

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

Dans ce cas, vous ne gagnez rien d'autre que de la complexité en utilisant un modèle plus élaboré avec plus d'indirection (5 lignes supplémentaires de XAML plus Command modèle).

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

4 votes

J'aime ce solution : il suffit d'accrocher un bouton caché :)

3 votes

Pour les débutants en mvvm n'utilisant pas MVVMLight et cherchant comment informer le ViewModel de l'événement Closing, les liens comment configurer correctement le dataContext et comment obtenir l'objet viewModel dans la vue peuvent être intéressants. Comment obtenir une référence au ViewModel dans la vue ? y Comment définir un ViewModel sur une fenêtre en xaml en utilisant la propriété datacontext ? ...Cela m'a pris plusieurs heures, comment un simple événement de fermeture de fenêtre pouvait être géré dans le ViewModel.

0 votes

J'adore cette solution élégante. Elle a très bien fonctionné pour moi. Merci !

84voto

Stas Points 282

Ce code fonctionne très bien :

ViewModel.cs :

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

et dans XAML :

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

en supposant 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"

1 votes

Oublié : pour obtenir les arguments des événements dans la commande, utilisez PassEventArgsToCommand="True".

2 votes

+1 approche simple et conventionnelle. Ce serait encore mieux de passer à PRISM.

16 votes

C'est un scénario qui met en évidence les lacunes de WPF et MVVM.

38voto

PILuaces Points 81

Cette option est encore plus facile, et vous convient peut-être. Dans le constructeur de votre modèle de vue, vous pouvez souscrire à l'événement de fermeture de la fenêtre principale comme ceci :

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

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

Tous les meilleurs.

0 votes

C'est la meilleure solution parmi celles mentionnées dans ce numéro. Merci !

0 votes

C'est ce que je cherchais. Merci.

0 votes

Pourquoi alors les gens utilisent-ils des méthodes plus lourdes alors qu'il existe une approche aussi simple ? Merci.

10voto

AllenM Points 96

Bon sang, on dirait qu'il y a beaucoup de code ici pour ça. Stas ci-dessus avait la bonne approche pour un effort minimal. Voici mon adaptation (en utilisant MVVMLight mais devrait être reconnaissable)... Oh et le PassEventArgsToCommand="Vrai" es 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 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 ressemble à ce qui suit, mais en fait, c'est RequestShutdown ou son nom qui décide d'arrêter ou non l'application (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);
    }
    }

4voto

ChrisBD Points 5795

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 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 pourriez avoir ce qui suit :

#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 votes

Merci pour cette réponse détaillée. Cependant, je ne pense pas que cela résolve mon problème : je dois gérer la fermeture de la fenêtre lorsque l'utilisateur clique sur le bouton "X" en haut à droite. Il serait facile de le faire dans le code-behind (il suffirait de lier l'événement Closing et de définir le paramètre CancelEventArgs.Cancel à true ou false), mais j'aimerais le faire dans le style MVVM. Désolé pour cette confusion

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X