使用WPF / MVVM Light Toolkit处理窗口closures事件
我想处理窗口的“closures”事件(当用户点击右上angular的“X”button),以便最终显示确认消息或/并取消closures。
我知道如何在代码隐藏中做到这一点:订阅窗口的“closures”事件,然后使用“CancelEventArgs.Cancel”属性。
但是我正在使用MVVM,所以我不确定这是不错的方法。
我认为最好的办法是将Closing事件绑定到我的ViewModel中的Command中。
我试过了:
<i:Interaction.Triggers> <i:EventTrigger EventName="Closing"> <cmd:EventToCommand Command="{Binding CloseCommand}" /> </i:EventTrigger> </i:Interaction.Triggers>
在ViewModel中有一个关联的RelayCommand,但它不起作用(该命令的代码没有执行)。
我只是将视图构造函数中的处理程序关联起来:
MyWindow() { // Set up ViewModel, assign to DataContext etc. Closing += viewModel.OnWindowClosing; }
然后将该处理程序添加到ViewModel
:
public void OnWindowClosing(object sender, CancelEventArgs e) { // Handle closing logic, set e.Cancel as needed }
在这种情况下,除了复杂性之外,通过使用更加间接的更复杂的模式(5个额外的XML
加命令模式行),您只能获得任何东西。
“零代码”的口头禅本身并不是目标,关键是将ViewModel从视图中分离出来 。 即使事件绑定在View的代码隐藏之后, ViewModel
也不依赖于View,而闭合逻辑可以通过unit testing 。
这段代码工作得很好:
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。
这个选项更容易,也许适合你。 在您的视图模型构造函数中,您可以像这样订阅主窗口closures事件:
Application.Current.MainWindow.Closing += new CancelEventHandler(MainWindow_Closing); void MainWindow_Closing(object sender, CancelEventArgs e) { //Your code to handle the event }
祝一切顺利。
Geez,好像很多代码都在这里。 上面的Stas有最小的努力正确的方法。 这里是我的适应(使用MVVMLight,但应该是可识别的)…哦,并且PassEventArgsToCommand =“真”是肯定需要如上所述。
(信贷给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);
在ShutdownService中
/// <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或者它的名称决定是否closures应用程序(无论如何快乐closures窗口):
... ... ... /// <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); } }
提问者应该使用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>
属性在您的viewmodel:
public ICommand WindowClosing { get; private set; }
在您的viewmodel构造函数中附加delegatecommand:
this.WindowClosing = new DelegateCommand<object>(this.OnWindowClosing);
最后,你想要在控制/窗口/closures的地方访问你的代码:
private void OnWindowClosing(object obj) { //put code here }
我会试图在你的App.xaml.cs文件中使用一个事件处理程序来决定是否closures应用程序。
例如,你可以在你的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]
如果您不想了解ViewModel中的窗口(或其任何事件),请按照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; }
在窗口中添加closures事件。 这个代码不会破坏MVVM模式。 视图可以知道视图模型!
void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { IClosing context = DataContext as IClosing; if (context != null) { e.Cancel = !context.OnClosing(); } }
基本上,窗口事件可能不会被分配给MVVM。 一般情况下,closuresbutton显示一个对话框,要求用户“保存:是/否/取消”,这可能无法通过MVVM来实现。
您可以保留OnClosing事件处理程序,您可以在其中调用Model.Close.CanExecute()并在事件属性中设置布尔结果。 所以在CanExecute()调用后,如果为true,则在OnClosed事件中,调用Model.Close.Execute()
我没有做这个testing,但它似乎工作。 以下是我想到的:
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。 您可以将任何事件附加到您的视图模型上的命令,避免任何代码隐藏。
我们在整个解决scheme中使用它,几乎没有代码
http://marlongrech.wordpress.com/2008/12/13/attachedcommandbehavior-v2-aka-acb/
使用MVVM Light Toolkit:
假设在视图模型中有一个Exit命令:
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(); });
另一方面,我使用ViewModel的实例在MainWindow
处理Closing
事件:
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { if (((ViewModel.MainViewModel)DataContext).CancelBeforeClose()) e.Cancel = true; }
CancelBeforeClose
检查视图模型的当前状态,如果closures应该停止,则返回true。
希望它可以帮助别人。
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { MessageBox.Show("closing"); }