用 WPF/MVVM Light Toolkit 处理窗口关闭事件

我想处理我的窗口的 Closing事件(当用户点击右上角的“ X”按钮时) ,以便最终显示确认消息或者/取消关闭。

我知道如何在代码隐藏中做到这一点: 订阅窗口的 Closing事件,然后使用 CancelEventArgs.Cancel属性。

但是我使用的是 MVVM,所以我不确定这是不是一个好的方法。

我认为最好的方法是在我的 ViewModel 中将 Closing事件绑定到 Command

我试过了:

<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<cmd:EventToCommand Command="{Binding CloseCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>

在我的 ViewModel 中有一个相关的 RelayCommand,但是它不工作(命令的代码没有执行)。

227638 次浏览

我很想在 App.xaml.cs 文件中使用一个事件处理程序,它允许您决定是否关闭应用程序。

例如,您可以在 App.xaml.cs 文件中包含以下代码:

protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// Create the ViewModel to attach the window to
MainWindow window = new MainWindow();
var viewModel = new MainWindowViewModel();


// Create the handler that will allow the window to close when the viewModel asks.
EventHandler handler = null;
handler = delegate
{
//***Code here to decide on closing the application****
//***returns resultClose which is true if we want to close***
if(resultClose == true)
{
viewModel.RequestClose -= handler;
window.Close();
}
}
viewModel.RequestClose += handler;


window.DataContaxt = viewModel;


window.Show();


}

然后在 MainWindowViewModel 代码中,您可以得到以下内容:

#region Fields
RelayCommand closeCommand;
#endregion


#region CloseCommand
/// <summary>
/// Returns the command that, when invoked, attempts
/// to remove this workspace from the user interface.
/// </summary>
public ICommand CloseCommand
{
get
{
if (closeCommand == null)
closeCommand = new RelayCommand(param => this.OnRequestClose());


return closeCommand;
}
}
#endregion // CloseCommand


#region RequestClose [event]


/// <summary>
/// Raised when this workspace should be removed from the UI.
/// </summary>
public event EventHandler RequestClose;


/// <summary>
/// If requested to close and a RequestClose delegate has been set then call it.
/// </summary>
void OnRequestClose()
{
EventHandler handler = this.RequestClose;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}


#endregion // RequestClose [event]

基本上,窗口事件可能不会被分配给 MVVM。通常,Close 按钮会显示一个对话框,要求用户“ save: yes/no/Cancel”,MVVM 可能无法做到这一点。

您可以保留 OnClose 事件处理程序,在其中调用 Model。差不多。CanExecute ()并在 event 属性中设置布尔结果。 因此,在 CanExecute ()调用 if true 之后,或在 OnClose 事件中调用 Model.Close. Execute ()

我还没有对它做过太多的测试,但是它似乎可以工作。下面是我得出的结论:

namespace OrtzIRC.WPF
{
using System;
using System.Windows;
using OrtzIRC.WPF.ViewModels;


/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private MainViewModel viewModel = new MainViewModel();
private MainWindow window = new MainWindow();


protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);


viewModel.RequestClose += ViewModelRequestClose;


window.DataContext = viewModel;
window.Closing += Window_Closing;
window.Show();
}


private void ViewModelRequestClose(object sender, EventArgs e)
{
viewModel.RequestClose -= ViewModelRequestClose;
window.Close();
}


private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
window.Closing -= Window_Closing;
viewModel.RequestClose -= ViewModelRequestClose; //Otherwise Close gets called again
viewModel.CloseCommand.Execute(null);
}
}
}

我们使用 AttachedCommandBehavior 进行此操作。您可以将任何事件附加到视图模型上的命令,从而避免任何代码落后。

我们在整个解决方案中使用它,几乎没有代码落后

Http://marlongrech.wordpress.com/2008/12/13/attachedcommandbehavior-v2-aka-acb/

这个代码工作得很好:

ViewModel.cs:

public ICommand WindowClosing
{
get
{
return new RelayCommand<CancelEventArgs>(
(args) =>{
});
}
}

以及 XAML:

<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<command:EventToCommand Command="{Binding WindowClosing}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>

假设:

  • ViewModel 被分配给主容器的 DataContext
  • xmlns:command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.SL5"
  • xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

天啊,看起来这里有很多代码啊。上面的 Stas 采用了最少工作量的正确方法。这里是我的改编(使用 MVVMLight,但应该可以识别) ... 哦,PassEventArgsToCommand = “ True”当然需要如上所述。

(贷款给 Laurent Bugnion http://blog.galasoft.ch/archive/2009/10/18/clean-shutdown-in-silverlight-and-wpf-applications.aspx)

   ... MainWindow Xaml
...
WindowStyle="ThreeDBorderWindow"
WindowStartupLocation="Manual">






<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<cmd:EventToCommand Command="{Binding WindowClosingCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>

在视图模型中:

///<summary>
///  public RelayCommand<CancelEventArgs> WindowClosingCommand
///</summary>
public RelayCommand<CancelEventArgs> WindowClosingCommand { get; private set; }
...
...
...
// Window Closing
WindowClosingCommand = new RelayCommand<CancelEventArgs>((args) =>
{
ShutdownService.MainWindowClosing(args);
},
(args) => CanShutdown);

在关闭服务中

    /// <summary>
///   ask the application to shutdown
/// </summary>
public static void MainWindowClosing(CancelEventArgs e)
{
e.Cancel = true;  /// CANCEL THE CLOSE - let the shutdown service decide what to do with the shutdown request
RequestShutdown();
}

RequestShutdown 看起来像下面这样,但是基本上是 RequestShutdown 或其他名称决定是否关闭应用程序(它将愉快地关闭窗口) :

...
...
...
/// <summary>
///   ask the application to shutdown
/// </summary>
public static void RequestShutdown()
{


// Unless one of the listeners aborted the shutdown, we proceed.  If they abort the shutdown, they are responsible for restarting it too.


var shouldAbortShutdown = false;
Logger.InfoFormat("Application starting shutdown at {0}...", DateTime.Now);
var msg = new NotificationMessageAction<bool>(
Notifications.ConfirmShutdown,
shouldAbort => shouldAbortShutdown |= shouldAbort);


// recipients should answer either true or false with msg.execute(true) etc.


Messenger.Default.Send(msg, Notifications.ConfirmShutdown);


if (!shouldAbortShutdown)
{
// This time it is for real
Messenger.Default.Send(new NotificationMessage(Notifications.NotifyShutdown),
Notifications.NotifyShutdown);
Logger.InfoFormat("Application has shutdown at {0}", DateTime.Now);
Application.Current.Shutdown();
}
else
Logger.InfoFormat("Application shutdown aborted at {0}", DateTime.Now);
}
}

这个选择更简单,也许适合你。在视图模型构造函数中,可以订阅主窗口关闭事件,如下所示:

Application.Current.MainWindow.Closing += new CancelEventHandler(MainWindow_Closing);


void MainWindow_Closing(object sender, CancelEventArgs e)
{
//Your code to handle the event
}

一切顺利。

我只需要在 View 构造函数中关联处理程序:

MyWindow()
{
// Set up ViewModel, assign to DataContext etc.
Closing += viewModel.OnWindowClosing;
}

然后将处理程序添加到 ViewModel:

using System.ComponentModel;


public void OnWindowClosing(object sender, CancelEventArgs e)
{
// Handle closing logic, set e.Cancel as needed
}

在这种情况下,通过使用更复杂的间接模式(5行额外的 XAML 加上 Command模式) ,除了复杂性之外什么也得不到。

“零代码隐藏”的咒语本身并不是目标,关键在于 从视图中解耦 ViewModel。即使事件绑定在视图的代码隐藏中,ViewModel也不依赖于视图和关闭逻辑 可以进行单元测试

如果您不想了解 ViewModel 中的 Window (或其中的任何事件) ,以下是根据 MVVM 模式给出的答案。

public interface IClosing
{
/// <summary>
/// Executes when window is closing
/// </summary>
/// <returns>Whether the windows should be closed by the caller</returns>
bool OnClosing();
}

在 ViewModel 中添加接口和实现

public bool OnClosing()
{
bool close = true;


//Ask whether to save changes och cancel etc
//close = false; //If you want to cancel close


return close;
}

在“窗口”中添加“关闭”事件。后面的代码没有打破 MVVM 模式。视图可以知道视图模型!

void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
IClosing context = DataContext as IClosing;
if (context != null)
{
e.Cancel = !context.OnClosing();
}
}

提问者应该使用 STAS 的答案,但是对于使用棱镜而不使用 galasoft/mvvmlight 的读者,他们可能想尝试我使用的答案:

在顶部的定义中为窗口或用户控件等定义名称空间:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

就在这个定义之下:

<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<i:InvokeCommandAction Command="{Binding WindowClosing}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>

视图模型中的属性:

public ICommand WindowClosing { get; private set; }

在你的视图模型构造函数中附加委托命令:

this.WindowClosing = new DelegateCommand<object>(this.OnWindowClosing);

最后,您希望在控件/窗口/随便什么的关闭处到达的代码:

private void OnWindowClosing(object obj)
{
//put code here
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
MessageBox.Show("closing");
}

使用 MVVM Light 工具包:

假设视图模型中有一个 出口命令:

ICommand _exitCommand;
public ICommand ExitCommand
{
get
{
if (_exitCommand == null)
_exitCommand = new RelayCommand<object>(call => OnExit());
return _exitCommand;
}
}


void OnExit()
{
var msg = new NotificationMessageAction<object>(this, "ExitApplication", (o) =>{});
Messenger.Default.Send(msg);
}

我们认为:

Messenger.Default.Register<NotificationMessageAction<object>>(this, (m) => if (m.Notification == "ExitApplication")
{
Application.Current.Shutdown();
});

另一方面,我在 MainWindow中使用 ViewModel 的实例处理 Closing事件:

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (((ViewModel.MainViewModel)DataContext).CancelBeforeClose())
e.Cancel = true;
}

CancelBeforeClose检查视图模型的当前状态,如果应该停止关闭,则返回 true。

希望能帮到别人。

您可以很容易地在后面编写一些代码; 在 Main.xaml 集中: Closing="Window_Closing"

在美因茨:

 public MainViewModel dataContext { get; set; }
public ICommand CloseApp
{
get { return (ICommand)GetValue(CloseAppProperty); }
set { SetValue(CloseAppProperty, value); }
}
public static readonly DependencyProperty CloseAppProperty =
DependencyProperty.Register("CloseApp", typeof(ICommand), typeof(MainWindow), new PropertyMetadata(null));

返回文章页面美国大陆译者:

dataContext = DataContext as MainViewModel;

窗口关闭:

   if (CloseApp != null)
CloseApp .Execute(this);

返回文章页面主窗口模型:

    public ICommand CloseApp => new CloseApp (this);

最后:

类 CloseApp: ICommand { 公共事件 EventHandler CanExecuteChanged;

    private MainViewModel _viewModel;


public CloseApp (MainViewModel viewModel)
{
_viewModel = viewModel;
}




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


public void Execute(object parameter)
{
Console.WriteLine();
}
}

我从这篇文章中获得了灵感,并将其改编成了一个图书馆,供我自己使用(但将在这里公开: https://github.com/RFBCodeWorks/MvvmControls

虽然我的方法确实通过传递给处理程序的“ sender”和“ eventargs”将视图暴露给 ViewModel,但我使用这种方法只是为了以防需要其他类型的处理。例如,如果处理程序不是 ViewModel,而是在窗口打开/关闭时记录的某个服务,那么该服务可能希望了解发件人。如果 VM 不想了解视图,那么它就不会检查发送方或参数。

下面是我想出的相关代码,它消除了 Code-Behind,并允许在 xaml 中进行绑定:

Behaviors:WindowBehaviors.IWindowClosingHandler="{Binding ElementName=ThisWindow, Path=DataContext}"
    /// <summary>
/// Interface that can be used to send a signal from the View to the ViewModel that the window is closing
/// </summary>
public interface IWindowClosingHandler
{
/// <summary>
/// Executes when window is closing
/// </summary>
void OnWindowClosing(object sender, System.ComponentModel.CancelEventArgs e);


/// <summary>
/// Occurs when the window has closed
/// </summary>
void OnWindowClosed(object sender, EventArgs e);


}


/// <summary>
/// Attached Properties for Windows that allow MVVM to react to a window Loading/Activating/Deactivating/Closing
/// </summary>
public static class WindowBehaviors
{
#region < IWindowClosing >


/// <summary>
/// Assigns an <see cref="IWindowClosingHandler"/> handler to a <see cref="Window"/>
/// </summary>
public static readonly DependencyProperty IWindowClosingHandlerProperty =
DependencyProperty.RegisterAttached(nameof(IWindowClosingHandler),
typeof(IWindowClosingHandler),
typeof(WindowBehaviors),
new PropertyMetadata(null, IWindowClosingHandlerPropertyChanged)
);


/// <summary>
/// Gets the assigned <see cref="IWindowLoadingHandler"/> from a <see cref="Window"/>
/// </summary>
public static IWindowClosingHandler GetIWindowClosingHandler(DependencyObject obj) => (IWindowClosingHandler)obj.GetValue(IWindowClosingHandlerProperty);


/// <summary>
/// Assigns an <see cref="IWindowClosingHandler"/> to a <see cref="Window"/>
/// </summary>
public static void SetIWindowClosingHandler(DependencyObject obj, IWindowClosingHandler value)
{
if (obj is not null and not Window) throw new ArgumentException($"{nameof(IWindowClosingHandler)} property can only be bound to a {nameof(Window)}");
obj.SetValue(IWindowClosingHandlerProperty, value);
}


private static void IWindowClosingHandlerPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Window w = d as Window;
if (w is null) return;
if (e.NewValue != null)
{
w.Closing += W_Closing;
w.Closed += W_Closed;
}
else
{
w.Closing -= W_Closing;
w.Closed -= W_Closed;
}
}


private static void W_Closed(object sender, EventArgs e)
{
GetIWindowClosingHandler(sender as DependencyObject)?.OnWindowClosed(sender, e);
}


private static void W_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
GetIWindowClosingHandler(sender as DependencyObject)?.OnWindowClosing(sender, e);
}


#endregion
}