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


145

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

我知道如何在后面的代码中执行此操作:订阅Closing窗口的事件,然后使用CancelEventArgs.Cancel属性。

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

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

我尝试过:

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

RelayCommand在我的ViewModel中带有一个关联,但是它不起作用(命令的代码未执行)。


3
也对解决这个问题的好答案感兴趣。
Sekhat

3
我从codeplex下载了代码,并对其进行了调试,结果显示:“无法将类型为'System.ComponentModel.CancelEventArgs'的对象转换为类型为'System.Windows.RoutedEventArgs'。” 如果您想要CancelEventArgs,但它不能回答您的问题,那就很好用了
David Hollinshead

我猜您的代码不起作用,因为您将触发器附加到的控件没有Closing事件。您的数据上下文不是窗口...它可能是具有网格或其他内容的数据模板,没有关闭事件。因此,在这种情况下,dbkk的答案是最好的答案。但是,当事件可用时,我更喜欢使用Interaction / EventTrigger方法。
NielW 2014年

例如,您拥有的代码将在Loaded事件上正常工作。
NielW 2014年

Answers:


126

我只是将处理程序关联到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
}

在这种情况下,通过使用具有更多间接性的更精细的模式(XAML加5行额外的Command模式),除了复杂性之外,您什么都不会得到。

“零代码落后”的口号本身并不是目的,关键是要使ViewModel与View脱钩。即使事件绑定在View的代码背后,ViewModel也不依赖于View,并且关闭逻辑可以进行单元测试


4
我喜欢这种解决方案:只需钩入隐藏的按钮即可:)
Benjol 2013年

3
对于不使用MVVMLight并搜索如何将关闭事件通知ViewModel的mvvm初学者,如何正确设置dataContext以及如何在View中获取viewModel对象的链接可能很有趣。如何在视图中获取对ViewModel的引用?以及如何使用datacontext属性在xaml中的窗口上设置ViewModel ...花了我几个小时的时间,如何在ViewModel中处理一个简单的窗口关闭事件。
MarkusEgle 2014年

18
该解决方案与MVVM环境无关。后面的代码不应该了解ViewModel。
2014年

2
@Jacob我认为问题更多在于,您在ViewModel中获得了一个表单事件处理程序,该事件处理程序将ViewModel耦合到特定的UI实现。如果他们要在后面使用代码,则应检查CanExecute,然后改为对ICommand属性调用Execute()。
Evil Pigeon 2014年

14
@Jacob后面的代码可以很好地了解ViewModel成员,就像XAML代码一样。或者,当您创建对ViewModel属性的绑定时,您认为您在做什么?只要您不处理自身背后的代码中的关闭逻辑,该解决方案就非常适合MVVM,但在ViewModel中(尽管像EvilPigeon建议的那样使用ICommand可能是个好主意,因为您也可以绑定)
almulo 2015年

81

这段代码可以正常工作:

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"

1
忘记了:要在命令中获取事件参数,请使用PassEventArgsToCommand =“ True”
Stas 2010年

2
+1简单而传统的方法。前往PRISM会更好。
Tri Q Tran 2010年

16
这是强调WPF和MVVM中漏洞的一种情况。
Damien

1
这将是真正的帮助提的是i<i:Interaction.Triggers>和如何得到它。
Andrii Muzychuk

1
@Chiz,它是一个名称空间,您应该在根元素中这样声明: xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Stas

34

此选项甚至更简单,也许更适合您。在您的View Model构造函数中,您可以订阅Main Window关闭事件,如下所示:

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

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

祝一切顺利。


这是本期提到的最佳解决方案。谢谢 !
2014年

这就是我想要的。谢谢!
Nikki Punjabi

20
...,这在ViewModel和View之间建立了紧密的耦合。-1。
PiotrK 2016年

6
这不是最佳答案。它破坏了MVVM。
萨菲隆

1
@Craig它需要对主窗口或其使用的任何窗口进行硬引用。这要容易得多,但这确实意味着视图模型没有解耦。这不是满足或不满足MVVM书呆子的问题,但是如果必须打破MVVM模式才能使其正常工作,则根本没有必要使用它。
亚历克斯

16

如果您不想了解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();
    }
}

简单,清晰和干净。ViewModel不需要知道视图的细节,因此关注点保持分离。
2015年

上下文始终为null!
Shahid Od

@ShahidOd您的ViewModel需要实现IClosing接口,而不仅仅是实现OnClosing方法。否则,DataContext as IClosing投射将失败并返回null
埃里克·怀特

10

真是的,似乎很多代码都在这里进行。上面的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或它的名称决定是否关闭应用程序(无论如何都会关闭窗口):

...
...
...
    /// <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);
    }
    }

8

询问者应使用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; }

在您的viewmodel构造函数中附加委托命令:

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

最后,您想要在控制/窗口/其他地方关闭的地方到达您的代码:

private void OnWindowClosing(object obj)
        {
            //put code here
        }

3
这不提供取消事件所需的对CancelEventArgs的访问。传递的对象是视图模型,从技术上讲,它是从其执行WindowClosing命令的视图模型。
stephenbayer

4

我很想在您的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]

1
感谢您的详细回答。但是,我认为这不能解决我的问题:当用户单击右上角的“ X”按钮时,我需要处理关闭窗口的问题。在后面的代码中很容易做到这一点(我只需链接Closing事件并将CancelEventArgs.Cancel设置为false即可),但我想以MVVM风格实现。抱歉给您带来混乱
Olivier Payen 2010年

1

基本上,窗口事件可能未分配给MVVM。通常,“关闭”按钮会显示一个对话框,询问用户“保存:是/否/取消”,而MVVM可能无法实现。

您可以保留OnClosing事件处理程序,在其中调用Model.Close.CanExecute()并在event属性中设置布尔结果。因此,如果CanExecute()调用为true,或者在OnClosed事件中调用,请调用Model.Close.Execute()


1

我对此并没有做太多测试,但它似乎有效。这是我想出的:

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);
        }
    }
}

1
在VM希望取消关闭的情况下,这里会发生什么?
Tri Q Tran 2010年


1

使用MVVM Light工具包:

假设视图模型中有一个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的实例处理中的Closing事件MainWindow

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

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

希望它能帮助某人。


-2
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        MessageBox.Show("closing");
    }

嗨,请在代码中添加一些说明,因为它有助于理解您的代码。仅有密码的答案
不满意

这位操作员明确表示,他对使用代码隐藏事件代码不感兴趣。
加西亚
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.