在 ViewModel 中处理 Window 的 Closed 和 Closing 事件






4.94/5 (21投票s)
本文讨论了一个附加行为,
引言
本文的灵感来自 Reed Copsey, Jr. 的 Blend 行为,该行为位于 Expression Code Gallery 上。 Reed 的行为使用了一种巧妙的技术,允许 View-Model 以 MVVM 友好的方式处理 View 的 Closing
/Closed
事件。 由于他的代码与 Expression DLL 绑定,我认为编写一个纯 WPF 版本会更好。 虽然在概念上与 Blend 行为相似,但我稍微偏离了概念的实现方式以及它的使用方式。 因此,这不是一个直接的 1 对 1 替换,但您应该能够以大致相同的方式工作,而无需付出太多努力。
代码的想法是允许 Window 的 Closed
和 Closing
事件通过 View-Model 中的命令来处理,为了实现这一点,我为 Window
对象编写了一个附加行为。
用法
该行为暴露了三个命令
Closed
- 当Window.Closed
事件触发时,将执行此命令。闭运算
Execute
- 如果Window.Closing
事件未被取消,则执行此命令。CanExecute
- 当Window.Closing
事件触发时,将调用此命令,让您有机会决定是否要取消。
CancelClosing
- 当Window.Closing
事件被取消时,将执行此命令。
以下是如何在 View-Model 中实现这些命令的示例。 请注意,该示例中的 MessageBox.Show
调用仅用于演示它是如何工作的。 您可能希望使用某种依赖注入来避免直接的 UI 代码,而是使用某种 MessageBox
服务(假设您确实想用一个 *Yes*/ *No* 消息框提示用户)。
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");
}
}
以下是如何在 XAML 中附加命令。
<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}"
Title="MainWindow" Height="350" Width="525">
实现
这是附加行为的代码清单。 此列表已重新格式化以适应 600 像素的宽度。
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;
}
}
}
}
我已突出显示上面代码中的相关部分。 对于 Closed
事件,我们只需执行任何可用命令,因为现在担心取消为时已晚。 对于 Closing
事件,Closing
命令的 CanExecute
用于确定我们是否正在取消。 如果我们没有取消,我们执行 Closing
命令,否则我们执行 CancelClosing
命令(如果可用)。
就这样。 谢谢。
参考
- http://gallery.expression.microsoft.com/en-us/WindowCloseBehavior - 这是 Reed 的原始文章,它引导我编写了一个没有 Blend 依赖项的文章。
历史
- 2010 年 4 月 15 日 - 首次发表文章