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

时间:2010-09-10 09:18:04

标签: c# wpf mvvm mvvm-light

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

我知道如何在代码隐藏中执行此操作:订阅窗口的“Closing”事件,然后使用“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但它不起作用(命令的代码未执行)。

12 个答案:

答案 0 :(得分:109)

我只想在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 XML加上命令模式的额外行来获得除了复杂性之外的任何东西。

&#34;零代码&#34; mantra本身不是目标,重点是将ViewModel与View 分离。即使事件绑定在View的代码隐藏中,ViewModel也不依赖于View,而关闭逻辑可以进行单元测试

答案 1 :(得分:72)

这段代码很好用:

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"

答案 2 :(得分:33)

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

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

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

一切顺利。

答案 3 :(得分:11)

如果您不想在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;
}

在窗口中我添加了Closing事件。这个代码背后并没有打破MVVM模式。 View可以知道viewmodel!

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

答案 4 :(得分:10)

Geez,看起来像这里有很多代码。上面的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);
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);
    }
    }

答案 5 :(得分: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>

viewmodel中的属性:

public ICommand WindowClosing { get; private set; }

在viewmodel构造函数中附加delegatecommand:

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

最后,您想要在控件/窗口/其他任何地方找到您的代码:

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

答案 6 :(得分: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]

答案 7 :(得分:1)

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

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

答案 8 :(得分: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);
        }
    }
}

答案 9 :(得分:1)

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

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

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

答案 10 :(得分:1)

使用MVVM Light Toolkit:

假设视图模型中有退出命令:

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。

希望它有所帮助。

答案 11 :(得分:-2)

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