web-dev-qa-db-fra.com

Comment annuler la fermeture d'une fenêtre dans l'application MVVM WPF

Comment puis-je annuler la sortie d'un formulaire particulier après avoir cliqué sur le bouton Annuler (ou X dans le coin supérieur droit ou Esc)?

WPF:

<Window
  ...
  x:Class="MyApp.MyView"
  ...
/>
  <Button Content="Cancel" Command="{Binding CancelCommand}" IsCancel="True"/>
</Window>

ViewModel:

public class MyViewModel : Screen {
  private CancelCommand cancelCommand;
  public CancelCommand CancelCommand {
    get { return cancelCommand; }
  }
  public MyViewModel() {
    cancelCommand = new CancelCommand(this);
  }
}

public class CancelCommand : ICommand {

  public CancelCommand(MyViewModel viewModel) {
    this.viewModel = viewModel;
  }

  public override void Execute(object parameter) {
    if (true) { // here is a real condition
      MessageBoxResult messageBoxResult = System.Windows.MessageBox.Show(
        "Really close?",  "Warning", 
        System.Windows.MessageBoxButton.YesNo);
      if (messageBoxResult == MessageBoxResult.No) { return; }
    }
    viewModel.TryClose(false);
  }

  public override bool CanExecute(object parameter) {
    return true;
  }
}

Le code actuel ne fonctionne pas. Je veux que l'utilisateur reste sur le formulaire actuel s'il choisit "Non" dans la boîte de dialogue contextuelle. En outre, remplacer CanExecute n'aide pas. Il désactive simplement le bouton. Je veux autoriser l'utilisateur à appuyer sur le bouton, mais ensuite l'informer que les données seront perdues. Peut-être que je devrais affecter un écouteur d'événements sur le bouton?

ÉDITER:

J'ai réussi à afficher une fenêtre contextuelle sur le bouton Annuler. Mais je ne peux toujours pas gérer le bouton Esc ou X (en haut à droite). Il semble que j'ai été confondu avec le bouton Annuler, car la méthode Exécuter est exécutée lorsque je clique sur le bouton X ou sur Échap.

EDIT2:

J'ai changé la question. C'était "comment annuler le bouton Annuler". Cependant, ce n'était pas ce que je cherchais. Je dois annuler le bouton Esc ou X. Dans 'MyViewModel' j'ajoute:

        protected override void OnViewAttached(object view, object context) {
            base.OnViewAttached(view, context);
            (view as MyView).Closing += MyViewModel_Closing;
        }

        void MyViewModel_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
            if (true) {
                MessageBoxResult messageBoxResult = System.Windows.MessageBox.Show(
                  "Really close?",  "Warning", 
                  System.Windows.MessageBoxButton.YesNo);
                if (messageBoxResult == MessageBoxResult.No) {
                    e.Cancel = true;
                }
            }
        }

Cela a résolu mon problème. Cependant, j'ai besoin d'ICommand pour comprendre quel bouton a été cliqué, Enregistrer ou Annuler. Existe-t-il un moyen d'éliminer l'utilisation de l'événement?

13
Andrii Muzychuk

Vous essayez de faire le travail de View dans la classe ViewModel. Laissez votre classe View gérer la demande de fermeture et si elle doit être annulée ou non.

Pour annuler la fermeture d'une fenêtre, vous pouvez vous abonner à l'événement de vue Closing et définir CancelEventArgs.Cancel à true après avoir montré un MessageBox.

Voici un exemple:

<Window
    ...
    x:Class="MyApp.MyView"
    Closing="OnClosing"
    ...
/>
</Window>

Code derrière:

private void OnClosing(object sender, CancelEventArgs e)
{
    MessageBoxResult result = MessageBox.Show("Really close?",  "Warning", MessageBoxButton.YesNo);
    if (result != MessageBoxResult.Yes)
    {
        e.Cancel = true;
    }

    bool shouldClose = ((MyViewModel) DataContext).TryClose(false);
    if(!shouldClose)
    {
        e.Cancel = true;
    }
}

Un très bon exemple de faire cela à la manière du modèle de vue peut être trouvé dans l'article de Nish Nishant , où il utilise des propriétés attachées pour connecter des événements de fenêtre avec des commandes.

Exemple de code de comportement attaché (auteur du code: Nish Nishant )

public class WindowClosingBehavior {

    public static ICommand GetClosed(DependencyObject obj) {
        return (ICommand)obj.GetValue(ClosedProperty);
    }

    public static void SetClosed(DependencyObject obj, ICommand value) {
        obj.SetValue(ClosedProperty, value);
    }

    public static readonly DependencyProperty ClosedProperty 
        = DependencyProperty.RegisterAttached(
        "Closed", typeof(ICommand), typeof(WindowClosingBehavior),
        new UIPropertyMetadata(new PropertyChangedCallback(ClosedChanged)));

    private static void ClosedChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) {

        Window window = target as Window;

        if (window != null) {

            if (e.NewValue != null) {
                window.Closed += Window_Closed;
            }
            else {
                window.Closed -= Window_Closed;
            }
        }
    }

    public static ICommand GetClosing(DependencyObject obj) {
        return (ICommand)obj.GetValue(ClosingProperty);
    }

    public static void SetClosing(DependencyObject obj, ICommand value) {
        obj.SetValue(ClosingProperty, value);
    }

    public static readonly DependencyProperty ClosingProperty 
        = DependencyProperty.RegisterAttached(
        "Closing", typeof(ICommand), typeof(WindowClosingBehavior),
        new UIPropertyMetadata(new PropertyChangedCallback(ClosingChanged)));

    private static void ClosingChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) {

        Window window = target as Window;

        if (window != null) {

            if (e.NewValue != null) {
                window.Closing += Window_Closing;
            }
            else {
                window.Closing -= Window_Closing;
            }
        }
    }

    public static ICommand GetCancelClosing(DependencyObject obj) {
        return (ICommand)obj.GetValue(CancelClosingProperty);
    }

    public static void SetCancelClosing(DependencyObject obj, ICommand value) {
        obj.SetValue(CancelClosingProperty, value);
    }

    public static readonly DependencyProperty CancelClosingProperty 
        = DependencyProperty.RegisterAttached(
        "CancelClosing", typeof(ICommand), typeof(WindowClosingBehavior));

    static void Window_Closed(object sender, EventArgs e) {

        ICommand closed = GetClosed(sender as Window);

        if (closed != null) {
            closed.Execute(null);
        }
    }

    static void Window_Closing(object sender, CancelEventArgs e) {

        ICommand closing = GetClosing(sender as Window);

        if (closing != null) {

            if (closing.CanExecute(null)) {
                closing.Execute(null);
            }
            else {

                ICommand cancelClosing = GetCancelClosing(sender as Window);

                if (cancelClosing != null) {
                    cancelClosing.Execute(null);
                }

                e.Cancel = true;
            }
        }
    }
}   

Exemple comment lier des commandes:

<Window 
    x:Class="WindowClosingDemo.MainWindow"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    xmlns:nsmvvm="clr-namespace:NS.MVVM"
    nsmvvm:WindowClosingBehavior.Closed="{Binding ClosedCommand}"
    nsmvvm:WindowClosingBehavior.Closing="{Binding ClosingCommand}"
    nsmvvm:WindowClosingBehavior.CancelClosing="{Binding CancelClosingCommand}">

Les commandes "ClosedCommand", "ClosingCommand" et "CancelClosingCommand" doivent être définies dans le View-Model séparé.

internal class MainViewModel : ViewModelBase {

    private ObservableCollection<string> log = new ObservableCollection<string>();

    public ObservableCollection<string> Log {
        get { return log; }
    }

    private DelegateCommand exitCommand;

    public ICommand ExitCommand {

        get {

            if (exitCommand == null) {
                exitCommand = new DelegateCommand(Exit);
            }

            return exitCommand;
        }
    }

    private void Exit() {
        Application.Current.Shutdown();
    }

    private DelegateCommand closedCommand;

    public ICommand ClosedCommand {

        get {

            if (closedCommand == null) {
                closedCommand = new DelegateCommand(Closed);
            }

            return closedCommand;
        }
    }

    private void Closed() {
        log.Add("You won't see this of course! Closed command executed");
        MessageBox.Show("Closed");
    }

    private DelegateCommand closingCommand;

    public ICommand ClosingCommand {

        get {

            if (closingCommand == null) {
                closingCommand = new DelegateCommand(ExecuteClosing, CanExecuteClosing);
            }

            return closingCommand;
        }
    }

    private void ExecuteClosing() {
        log.Add("Closing command executed");
        MessageBox.Show("Closing");
    }

    private bool CanExecuteClosing() {

        log.Add("Closing command execution check");

        return MessageBox.Show("OK to close?", "Confirm", MessageBoxButton.YesNo) == MessageBoxResult.Yes;
    }

    private DelegateCommand cancelClosingCommand;

    public ICommand CancelClosingCommand {

        get {

            if (cancelClosingCommand == null) {
                cancelClosingCommand = new DelegateCommand(CancelClosing);
            }

            return cancelClosingCommand;
        }
    }

    private void CancelClosing() {
        log.Add("CancelClosing command executed");
        MessageBox.Show("CancelClosing");
    }
}
0
torpederos

Je ne suis pas un expert MVVM, mais à mon avis la réponse de Yusufs n'est pas tout à fait MVVM. D'un autre côté, la réponse de Torpederos est un peu compliquée pour une annulation rapprochée. Voici mon approche. Dans cet exemple, je me suis abonné à l'événement de clôture, mais il est toujours annulé

private void OnClosing(object sender, CancelEventArgs e)
{
    e.Cancel = true;
    return;
}

Dans le XAML, j'ai ajouté ceci

xmlns:i="http://schemas.Microsoft.com/expression/2010/interactivity"
<i:Interaction.Triggers>
    <i:EventTrigger EventName="Closing">
        <i:InvokeCommandAction Command="{Binding Close}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

Et enfin dans le modèle de vue

public ICommand Close { get; set; }
Close = new RelayCommand(CommandClose);
private void CommandClose(object sender)
{
    if (Dirty)
    {
        // Save your data here
    }
    Environment.Exit(0);
}

Dans cette approche, l'événement de fermeture est déclenché en premier. Cela annule la fermeture. Après cela, le déclencheur d'interaction est appelé et déclenche le code dans le modèle de vue via RelayCommand. Dans le modèle de vue, je peux utiliser l'indicateur Dirty qui n'est pas accessible dans la vue.

0
DaanH