Обработка события закрытия окна с помощью WPF / MVVM Light Toolkit - PullRequest
134 голосов
/ 10 сентября 2010

Я бы хотел обработать событие «Закрытие» (когда пользователь нажимает верхнюю правую кнопку «X») моего окна, чтобы в конечном итоге отобразить подтверждающее сообщение или / и отменить закрытие.

Я знаю, как сделать это в коде: подпишитесь на событие «Закрытие» окна, затем используйте свойство «CancelEventArgs.Cancel».

Но я использую MVVM, поэтому не уверен, что это хороший подход.

Я думаю, что хорошим подходом было бы связать событие Closing с Командой в моей ViewModel.

Я пробовал это:

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

Со связанной RelayCommand в моей ViewModel, но она не работает (код команды не выполняется).

Ответы [ 12 ]

115 голосов
/ 24 мая 2012

Я бы просто связал обработчик в конструкторе 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 плюс шаблон команды).

Мантра «с нулевым выделением кода» не является самоцелью, смысл состоит в том, чтобы отделить ViewModel от View .Даже когда событие связано с выделенным кодом View, ViewModel не зависит от View, и логика закрытия может быть проверена модулем .

74 голосов
/ 04 ноября 2010

Этот код прекрасно работает:

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"
33 голосов
/ 06 декабря 2011

Этот вариант еще проще, и, возможно, подходит для вас. В конструкторе вашей модели представления вы можете подписать событие закрытия главного окна следующим образом:

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

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

Всего наилучшего.

11 голосов
/ 25 сентября 2014

Вот ответ в соответствии с MVVM-шаблоном, если вы не хотите знать о Window (или любом его событии) в ViewModel.

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();
    }
}
10 голосов
/ 29 августа 2011

Боже, похоже, здесь много кода для этого. У Стаса выше был правильный подход при минимальных усилиях. Вот моя адаптация (используя MVVMLight, но она должна быть узнаваемой) ... Да, и PassEventArgsToCommand = "True" определенно , как указано выше.

(кредит Лорана Бюньона 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);

в службе выключения

    /// <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 голосов
/ 06 ноября 2014

Запрашивающему следует использовать ответ STAS, но для читателей, которые используют призму и не используют galasoft / mvvmlight, они могут захотеть попробовать то, что я использовал:

В определении вверху окна или управления пользователем и т. Д. Определитеnamespace:

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:

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

Наконец, ваш код, который вы хотите получить при закрытии элемента управления / окна / чего угодно:

private void OnWindowClosing(object obj)
        {
            //put code here
        }
4 голосов
/ 10 сентября 2010

Я хотел бы использовать обработчик событий в вашем файле 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 голос
/ 19 марта 2017

Использование 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();
});

С другой стороны, я обрабатываю событие Closing в MainWindow, используя экземпляр ViewModel:

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

CancelBeforeClose проверяет текущее состояние модели представления и возвращает trueесли закрытие должно быть остановлено.

Надеюсь, это кому-нибудь поможет.

1 голос
/ 07 октября 2010

Мы используем AttachedCommandBehavior для этого.Вы можете прикрепить любое событие к команде в вашей модели представления, избегая какого-либо кода позади.

Мы используем его в нашем решении и имеем почти нулевой код позади

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

1 голос
/ 06 октября 2010

Я не провел много испытаний с этим, но, похоже, работает.Вот что я придумал:

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