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

Oli*_*yen 139 c# wpf mvvm mvvm-light

我想处理窗口中的"Closing"事件(当用户点击右上角的"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>
Run Code Online (Sandbox Code Playgroud)

在我的ViewModel中使用关联的RelayCommand但它不起作用(命令的代码未执行).

dbk*_*bkk 120

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

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

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

using System.ComponentModel;

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

在这种情况下,除了复杂性之外,你通过使用更复杂的模式和更多的间接(5个额外的XML加号命令模式行),你几乎没有获得任何东西.

"零代码隐藏"口头禅本身并不是目标,重点是将ViewModel与View分离.即使事件绑定在View的代码隐藏中,ViewModel也不依赖于View,关闭逻辑可以进行单元测试.

  • 此解决方案在MVVM环境中无关紧要.后面的代码不应该知道ViewModel. (17认同)
  • @Jacob代码隐藏可以很好地了解ViewModel成员,只需使用XAML代码即可.或者,当您创建绑定到ViewModel属性时,您认为自己在做什么?这个解决方案对于MVVM来说非常好,只要你不处理代码隐藏本身的结束逻辑,但是在ViewModel中(尽管使用ICommand,就像EvilPigeon所说,这可能是一个好主意,因为你也可以绑定它)) (14认同)
  • 我喜欢[this](http://stephantodd.blogspot.ch/2010/04/how-to-handle-wpf-events-with-mvvm-on.html)解决方案:只需挂钩隐藏按钮:) (4认同)
  • 对于不使用MVVMLight并搜索如何通知ViewModel关于Closing事件的mvvm初学者,如何正确设置dataContext以及如何在View中获取viewModel对象的链接可能很有趣.[如何在视图中获取对ViewModel的引用?](http://stackoverflow.com/a/9789644/3090544)和[如何使用datacontext属性在xaml中的窗口上设置ViewModel](http:/ /stackoverflow.com/a/4590471/3090544)...我花了几个小时,如何在ViewModel中处理一个简单的窗口关闭事件. (3认同)
  • @Jacob我认为问题更多的是你在ViewModel中得到一个表单事件处理程序,它将ViewModel耦合到一个特定的UI实现.如果他们要使用后面的代码,他们应该检查CanExecute,然后在ICommand属性上调用Execute(). (2认同)

Sta*_*tas 79

这段代码工作得很好:

ViewModel.cs:

public ICommand WindowClosing
{
    get
    {
        return new RelayCommand<CancelEventArgs>(
            (args) =>{
                     });
    }
}
Run Code Online (Sandbox Code Playgroud)

在XAML中:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Closing">
        <command:EventToCommand Command="{Binding WindowClosing}" PassEventArgsToCommand="True" />
    </i:EventTrigger>
</i:Interaction.Triggers>
Run Code Online (Sandbox Code Playgroud)

假如说

  • 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"

  • 这是一个突出WPF和MVVM漏洞的场景. (15认同)
  • 什么是"命令:"?这是什么意思? (13认同)
  • +1简单而传统的方法。前往PRISM会更好。 (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
}
Run Code Online (Sandbox Code Playgroud)

祝一切顺利.

  • ...这会在ViewModel和View之间产生紧密耦合.-1. (17认同)
  • 这不是最好的答案.它破坏了MVVM. (5认同)
  • @Craig它需要对主窗口或正在使用的任何窗口进行硬引用。这要容易得多,但这确实意味着视图模型没有解耦。这不是是否满足 MVVM 书呆子的问题,但如果必须打破 MVVM 模式才能使其工作,那么使用它根本没有意义。 (3认同)

Axd*_*der 14

下面是根据MVVM模式,如果你不想知道在视图模型窗口(或者任何事件的)答案.

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

在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;
}
Run Code Online (Sandbox Code Playgroud)

在窗口中,我添加了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();
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 10

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> 
Run Code Online (Sandbox Code Playgroud)

在视图模型中:

///<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);
Run Code Online (Sandbox Code Playgroud)

在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();
    }
Run Code Online (Sandbox Code Playgroud)

RequestShutdown看起来类似于以下内容,但basicRequestShutdown或其命名的任何内容决定是否关闭应用程序(无论如何都会快乐地关闭窗口):

...
...
...
    /// <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);
    }
    }
Run Code Online (Sandbox Code Playgroud)


Chr*_*ris 8

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

在窗口或用户控件顶部的定义中,定义命名空间:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Run Code Online (Sandbox Code Playgroud)

就在这个定义之下:

<i:Interaction.Triggers>
        <i:EventTrigger EventName="Closing">
            <i:InvokeCommandAction Command="{Binding WindowClosing}" CommandParameter="{Binding}" />
        </i:EventTrigger>
</i:Interaction.Triggers>
Run Code Online (Sandbox Code Playgroud)

viewmodel中的属性:

public ICommand WindowClosing { get; private set; }
Run Code Online (Sandbox Code Playgroud)

在viewmodel构造函数中附加delegatecommand:

this.WindowClosing = new DelegateCommand<object>(this.OnWindowClosing);
Run Code Online (Sandbox Code Playgroud)

最后,您希望在控件/窗口/关闭时触及您的代码:

private void OnWindowClosing(object obj)
        {
            //put code here
        }
Run Code Online (Sandbox Code Playgroud)

  • 这不允许访问取消关闭事件所必需的CancelEventArgs.传递的对象是视图模型,在技术上与执行WindowClosing命令的视图模型相同. (3认同)

Chr*_*sBD 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();

}
Run Code Online (Sandbox Code Playgroud)

然后在您的 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]
Run Code Online (Sandbox Code Playgroud)