如何在 MVVM WPF 应用程序中取消 window 关闭
How to cancel window closing in MVVM WPF application
单击取消按钮(或右上角的 X,或 Esc)后如何取消退出特定表单?
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 也无济于事。它只是禁用按钮。我想让用户点击按钮,但随后通知 him/her,数据将丢失。
也许我应该在按钮上分配一个事件侦听器?
编辑:
我成功地在“取消”按钮上显示了弹出窗口。但我仍然无法管理 Esc 或 X 按钮(右上角)。看来我和Cancel按钮搞混了,因为Execute方法是在我点击X按钮或者Esc的时候执行的。
编辑 2:
我改了问题。它是 'how cancel Cancel button'。但是,这不是我想要的。我需要取消 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 了解单击了哪个按钮,保存或取消。有什么方法可以消除事件的使用吗?
您正在尝试在 ViewModel class 中执行 View 的工作。让您的视图 class 处理关闭请求以及是否应取消它。
要取消关闭 window,您可以订阅视图的 Closing
事件,并在显示 MessageBox
后将 CancelEventArgs.Cancel
设置为 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;
}
}
可以找到以视图模型方式执行此操作的非常好的示例 in the article of Nish Nishant,他在其中使用附加属性将 window 事件与命令挂钩。
附加行为示例代码(代码作者: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");
}
}
我不是 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 标志。
这是另一个直接从 ViewModel 取消关闭的例子window。
查看:
<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 close window event
单击取消按钮(或右上角的 X,或 Esc)后如何取消退出特定表单?
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 也无济于事。它只是禁用按钮。我想让用户点击按钮,但随后通知 him/her,数据将丢失。 也许我应该在按钮上分配一个事件侦听器?
编辑:
我成功地在“取消”按钮上显示了弹出窗口。但我仍然无法管理 Esc 或 X 按钮(右上角)。看来我和Cancel按钮搞混了,因为Execute方法是在我点击X按钮或者Esc的时候执行的。
编辑 2:
我改了问题。它是 'how cancel Cancel button'。但是,这不是我想要的。我需要取消 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 了解单击了哪个按钮,保存或取消。有什么方法可以消除事件的使用吗?
您正在尝试在 ViewModel class 中执行 View 的工作。让您的视图 class 处理关闭请求以及是否应取消它。
要取消关闭 window,您可以订阅视图的 Closing
事件,并在显示 MessageBox
后将 CancelEventArgs.Cancel
设置为 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;
}
}
可以找到以视图模型方式执行此操作的非常好的示例 in the article of Nish Nishant,他在其中使用附加属性将 window 事件与命令挂钩。
附加行为示例代码(代码作者: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");
}
}
我不是 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 标志。
这是另一个直接从 ViewModel 取消关闭的例子window。
查看:
<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 close window event