【问题标题】:Bind ViewModel method to View's Alt+F4 and Close button将 ViewModel 方法绑定到 View 的 Alt+F4 和关闭按钮
【发布时间】:2021-05-09 06:40:09
【问题描述】:

我创建了一个自定义方法来关闭视图模型中的表单并将其绑定到按钮。如何获取标题栏中的默认关闭按钮和 Alt+F4 以运行相同的命令?

public void Close(object parameter)
{
    // Check if any field has been edited
    if (IsDirty())
    {
        string message = "You have unsaved changes.\n\nAre you sure you want to close this form?";
        string title = "Close Window";
        MessageBoxResult result = MessageBox.Show(message, title, MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);

        if (result == MessageBoxResult.Cancel)
            return;
    }

    employeeWindow.Close();
}

我还是 WPF 和 MVVM 的新手,我只是不了解现有的任何解决方案,也不了解如何自定义它们以运行上述代码。

【问题讨论】:

标签: c# wpf mvvm


【解决方案1】:

您正在寻找的是Window 上的Closing 事件。它的CancelEventArgs 带有Cancel 类型的bool 属性,可以设置为true,这将取消关闭窗口。使用关闭按钮关闭窗口并按下 Alt+F4 时都会触发关闭事件。

代码隐藏

在代码隐藏场景中,您可以像这样在 XAML 中添加事件处理程序。

<Window Closing="OnClosing" ...>

然后,您将在代码隐藏中创建此事件处理程序并相应地设置 Cancel 属性。

private void OnClosing(object sender, CancelEventArgs e)
{
   // Check if any field has been edited
   if (IsDirty())
   {
      string message = "You have unsaved changes.\n\nAre you sure you want to close this form?";
      string title = "Close Window";
      MessageBoxResult result = MessageBox.Show(message, title, MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);

      if (result == MessageBoxResult.Cancel)
         e.Cancel = true;
   }
}

事件触发器(非 MVVM)

在 MVVM 中,您可以安装 Microsoft.Xaml.Behaviors.Wpf NuGet 包,它可以替代传统的 Blend 行为 (System.Windows.Interactivity)。您可以使用EventTrigger 将事件绑定到视图模型中的命令。在此示例中,您将 CancelEventArgs 直接传递给命令。

<Window ...>
   <b:Interaction.Triggers>
      <b:EventTrigger EventName="Closing">
         <b:InvokeCommandAction Command="{Binding ClosingCommand}"
                                PassEventArgsToCommand="True"/>
      </b:EventTrigger>
   </b:Interaction.Triggers>
   <!-- ...other markup. -->
</Window>

此解决方案允许您在视图模型中定义命令。

public class MyViewModel
{
   public MyViewModel()
   {
      ClosingCommand = new RelayCommand<CancelEventArgs>(ExecuteClosing);
   }

   public ICommand ClosingCommand { get; }

private void ExecuteClosing(CancelEventArgs e)
   {
      string message = "You have unsaved changes.\n\nAre you sure you want to close this form?";
      string title = "Close Window";
      MessageBoxResult result = MessageBox.Show(message, title, MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
   
      if (result == MessageBoxResult.Cancel)
         e.Cancel = true;
   }
}

我在这里不提供ICommand 实现。有关基础知识的更多信息,请参阅:

尽管此解决方案在视图模型上使用命令,但它不兼容 MVVM,因为消息框是一个视图组件,不能驻留在视图模型中。这同样适用于取消事件参数。

MVVM 行为

一种符合 MVVM 的方式可能是创建一个行为来移出确认码。为此,为您的视图模型创建一个包含IsDirty 方法的接口,并在您的视图模型中实现它。

public interface IStatefulViewModel
{
   bool IsDirty();
}
public class MyViewModel : IStatefulViewModel
{
   // ...your code.

   public bool IsDirty()
   {
      // ...your checks.
   }
}

然后,使用 Microsoft.Xaml.Behaviors.Wpf NuGet 包创建一个行为。此行为是可重用的,并且封装了与视图模型分离的关闭逻辑。 CaptionMessage 依赖属性允许绑定消息框内容。

public class WindowClosingBehavior : Behavior<Window>
{
   public static readonly DependencyProperty CaptionProperty = DependencyProperty.Register(
      nameof(Caption), typeof(string), typeof(WindowClosingBehavior), new PropertyMetadata(string.Empty));

   public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
      nameof(Message), typeof(string), typeof(WindowClosingBehavior), new PropertyMetadata(string.Empty));

   public string Caption
   {
      get => (string)GetValue(CaptionProperty);
      set => SetValue(CaptionProperty, value);
   }

   public string Message
   {
      get => (string)GetValue(MessageProperty);
      set => SetValue(MessageProperty, value);
   }

   protected override void OnAttached()
   {
      base.OnAttached();
      AssociatedObject.Closing += OnClosing;
   }

   protected override void OnDetaching()
   {
      base.OnDetaching();
      AssociatedObject.Closing -= OnClosing;
   }

   private void OnClosing(object sender, CancelEventArgs e)
   {
      if (!(AssociatedObject.DataContext is IStatefulViewModel statefulViewModel))
         return;

      if (!statefulViewModel.IsDirty())
         return;

      e.Cancel = ConfirmClosing();
   }

   private bool ConfirmClosing()
   {
      var result = MessageBox.Show(
         Message,
         Caption,
         MessageBoxButton.OKCancel,
         MessageBoxImage.Warning,
         MessageBoxResult.Cancel);

      return result == MessageBoxResult.Cancel;
   }
}

将行为附加到您的窗口。请注意,您可以在任何窗口上执行此操作。

<Window ...>
   <b:Interaction.Behaviors>
      <local:WindowClosingBehavior Caption="Close Window"
                                   Message="You have unsaved changes.&#13;&#10;&#13;&#10;Are you sure you want to close this form?"/>
   </b:Interaction.Behaviors>
   <!-- ...other markup. -->
</Window>

不要被 &amp;#13;&amp;#10; 字符混淆,它们是 XML 中的换行符 (\n)。

【讨论】:

  • 太棒了,这就是我所追求的一切,我遵循了你的 MVVM 行为之一。并感谢您解释它们。
  • “消息框是不能驻留在视图模型中的视图组件”是什么意思。我的视图是由视图模型创建的,因为如果视图模型无法从数据库中加载员工,我不希望创建或显示视图。这不符合 MVVM 吗?
  • @smally MVVM 旨在将您的用户界面与业务逻辑分开。这意味着您的视图模型中不得有任何视图组件引用,无论是WindowUserControl 还是任何其他控件。所以不,这不符合 MVVM。
猜你喜欢
  • 1970-01-01
  • 2012-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多