如何在MVVM WPF应用程序中取消窗口关闭

17

在点击“取消”按钮(或右上角的X号,或Esc键)后,我该如何取消退出特定表单?

WPF:

怎样在 WPF 中实现?

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

视图模型:

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;
  }
}
当前代码无法工作。如果用户在弹出对话框中选择 'No',我希望用户留在当前表单上。 此外,覆盖CanExecute并没有帮助。它只是禁用了按钮。我想允许用户点击按钮,但随后通知他/她数据将会丢失。 也许我应该在按钮上分配一个事件监听器?
编辑:
我设法在取消按钮上显示弹出窗口。但我仍然不能管理Esc或X按钮(右上角)。似乎我对取消按钮感到困惑,因为当我点击X按钮或Esc时,执行方法会被执行。
编辑2:
我更改了问题。它是“如何取消取消按钮”,然而,这不是我要找的。我需要取消Esc或X按钮。 在'MyViewModel'中,我添加:
        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;
                }
            }
        }

这解决了我的问题。但是,我需要让ICommand理解是哪个按钮被点击了,保存还是取消。有没有办法不使用事件来实现?


3
您的viewModel.TryClose(false)函数是否会向视图发送事件以关闭对话框?如果是,则可以从xaml代码中删除IsCancel="true"。这部分会导致表单关闭。 - Yusuf Tarık Günaydın
@qqww2 如果我刪除 IsCancel="true",那麼如果我點擊 Esc,它就不會關閉窗口。我希望窗口在按下 Esc 時能夠關閉。 - Andrii Muzychuk
1
在你的命令中注册一个KeyBinding这里有一个例子。 - Yusuf Tarık Günaydın
4个回答

20

您正在尝试在ViewModel类中执行View的工作。让您的View类处理关闭请求以及是否应取消该请求。

要取消窗口的关闭,您可以订阅视图的事件,并在显示后将设置为true。

以下是一个示例:

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

后台代码:

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

    // OR, if triggering dialog via view-model:

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

问题在于View不知道它是否应该关闭。我不知道如何访问ViewModel数据。 - Andrii Muzychuk
1
好的。你能详细解释一下关闭窗口的算法是什么吗? - Yusuf Tarık Günaydın
我在ViewModel中为OnClosing事件分配了处理程序。就像我在EDIT2之后所述的那样。然而,在这种情况下,我无法区分它是如何关闭的。我的意思是,在不添加额外标志的情况下,我将在ICommand中设置它们。 - Andrii Muzychuk
viewModel.TryClose(false) 中的 false 是指它是从 UI 还是代码中调用的?此外,修改后的代码不是 MVVM 中期望的方式。 - Yusuf Tarık Günaydın
我不确定我能否清楚地理解你的意思,但我编辑了答案以调用视图模型类。 - Yusuf Tarık Günaydın
显示剩余2条评论

4

我不是MVVM专家,但在我看来,Yusufs的答案并不完全符合MVVM。另一方面,Torpederos的答案对于仅关闭取消有些复杂。以下是我的方法。 在这个例子中,我订阅了关闭事件,但它总是被取消了。

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

在XAML中,我添加了以下内容。
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>

最后,视图模型中

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

在这种方法中,关闭事件首先被触发。这会取消关闭。之后,交互触发器被调用并通过RelayCommand触发视图模型中的代码。 在视图模型中,我可以使用在视图中不可访问的Dirty标志。

2
这是取消关闭窗口的另一个示例,直接来自ViewModel。
视图:
<Window x:Class="WpfApplicationMvvmLight.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform"
    Title="MainWindow" Height="350" Width="525">
<i:Interaction.Triggers>
    <i:EventTrigger EventName="Closing">
        <cmd:EventToCommand Command="{Binding Path=ClosingCommand}" PassEventArgsToCommand="True" />
    </i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
    <TextBlock>content...</TextBlock>
</Grid>

ViewModel:

using GalaSoft.MvvmLight.CommandWpf;
using System.ComponentModel;
using System.Windows;

namespace WpfApplicationMvvmLight
{
  class SampleViewModel
  {
    public SampleViewModel() {
      _closingCommand = new RelayCommand<CancelEventArgs>(OnClosingCommand);
    }

    private RelayCommand<CancelEventArgs> _closingCommand;
    public RelayCommand<CancelEventArgs> ClosingCommand {
      get {
        return _closingCommand;
      }
    }

    private void OnClosingCommand(CancelEventArgs e) {
      //display your custom message box here..
      var result = MessageBox.Show("Do you want to close?", "", MessageBoxButton.YesNoCancel);
      //set e.Cancel to true to prevent the window from closing
      e.Cancel = result != MessageBoxResult.Yes;
    }
  }
}

后台编程代码:

using System.Windows;

namespace WpfApplicationMvvmLight
{
  public partial class MainWindow : Window
  {
    public MainWindow() {
      InitializeComponent();
      this.DataContext = new SampleViewModel();
    }
  }
}

这是参考文献。MVVM关闭窗口事件

2
非常好的使用视图模型方式做到这一点的例子可以在Nish Nishant的文章中找到(链接),他使用附加属性将窗口事件与命令连接起来。
附加行为的示例代码(代码作者: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;
            }
        }
    }
}   

绑定命令的示例:

<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}">

“ClosedCommand”、“ClosingCommand”和“CancelClosingCommand”命令应在单独的视图模型中定义。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接