首页 文章

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

提问于
浏览
126

我想处理窗口中的“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>

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

12 回答

  • 69

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

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

  • 32

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

    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 加上命令模式行),你几乎没有获得任何东西 .

    "zero code-behind"咒语本身不是目标,重点是 decouple ViewModel from the View . 即使事件绑定在View的代码隐藏中, ViewModel 也不依赖于View和结束逻辑 can be unit-tested .

  • 1

    这段代码工作得很好:

    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

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

    Application.Current.MainWindow.Closing += new CancelEventHandler(MainWindow_Closing);
    
    void MainWindow_Closing(object sender, CancelEventArgs e)
    {
                //Your code to handle the event
    }
    

    祝一切顺利 .

  • 102

    Geez,看起来像这里有很多代码 . 上面的Stas有最小努力的正确方法 . 这是我的改编(使用MVVMLight,但应该是可识别的)...哦,如上所述, PassEventArgsToCommand="True" 需要 definitely .

    (感谢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看起来类似于以下内容,但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);
        }
        }
    
  • 8

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

    提问者应该使用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
            }
    
  • -3

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

    我没有做太多的测试,但它似乎工作 . 这是我想出的:

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

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

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

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

  • 10

    使用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 检查当前的视图模型状态,如果应该停止关闭,则返回true .

    希望能帮助到你有人 .

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

相关问题