Обработка события закрытия окна с помощью WPF/MVVM Light Toolkit
Я бы хотел обработать событие "Закрытие" (когда пользователь нажимает кнопку "Х" справа) моего окна, чтобы в конечном итоге отобразить сообщение подтверждения или/и отменить закрытие.
Я знаю, как это сделать в кодировке: подпишитесь на событие "Закрытие" окна, затем используйте свойство "CancelEventArgs.Cancel".
Но я использую MVVM, поэтому я не уверен, что это хороший подход.
Я думаю, что хорошим подходом было бы привязать событие закрытия к команде в моей модели ViewModel.
Я пробовал:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<cmd:EventToCommand Command="{Binding CloseCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
С помощью связанного RelayCommand в моей ViewModel, но он не работает (код команды не выполняется).
Ответы
Ответ 1
Я бы просто связал обработчик в конструкторе 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, и логика закрытия может быть проверена модулем.
Ответ 2
Этот код работает просто отлично:
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"
Ответ 3
Эта опция еще проще и, возможно, подходит для вас. В конструкторе View Model вы можете подписаться на событие закрытия главного окна следующим образом:
Application.Current.MainWindow.Closing += new CancelEventHandler(MainWindow_Closing);
void MainWindow_Closing(object sender, CancelEventArgs e)
{
//Your code to handle the event
}
Все самое лучшее.
Ответ 4
Вот ответ в соответствии с шаблоном MVVM, если вы не хотите знать о окне (или любом его событии) в 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. View может знать о viewmodel!
void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
IClosing context = DataContext as IClosing;
if (context != null)
{
e.Cancel = !context.OnClosing();
}
}
Ответ 5
Geez, похоже, здесь много кода для этого. У Стаса выше был правильный подход для минимальных усилий. Вот моя адаптация (с использованием MVVMLight, но должна быть узнаваема)... Oh и 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);
в 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 выглядит примерно так: butRequestShutdown или что бы то ни было, он решает, закрыть ли приложение или нет (что весело закроет окно):
...
...
...
/// <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);
}
}
Ответ 6
Ответчик должен использовать ответ STAS, но для читателей, использующих призму, и не galasoft/mvvmlight, они могут попробовать использовать то, что я использовал:
В определении вверху для окна или usercontrol и т.д. define 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>
Свойство в вашей модели просмотра:
public ICommand WindowClosing { get; private set; }
Прикрепите команду delegate в вашем конструкторе viewmodel:
this.WindowClosing = new DelegateCommand<object>(this.OnWindowClosing);
Наконец, ваш код, который вы хотите достичь при закрытии элемента управления/окна/независимо:
private void OnWindowClosing(object obj)
{
//put code here
}
Ответ 7
Я хотел бы использовать обработчик событий в вашем файле 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]
Ответ 8
В принципе, событие окна не может быть присвоено MVVM. В общем, кнопка "Закрыть" показывает диалоговое окно, чтобы спросить пользователя "сохранить: да/нет/отменить", и это может быть не достигнуто MVVM.
Вы можете сохранить обработчик события OnClosing, где вы вызываете Model.Close.CanExecute() и устанавливаете логический результат в свойстве события.
Поэтому после вызова CanExecute(), если true, ИЛИ в событии OnClosed, вызовите Model.Close.Execute()
Ответ 9
Я не очень много тестировал, но, похоже, он работает. Вот что я придумал:
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);
}
}
}
Ответ 10
Для этого мы используем AttachedCommandBehavior. Вы можете присоединить любое событие к команде в вашей модели просмотра, избегая любого кода.
Мы используем его во всем нашем решении и имеем почти нулевой код за
http://marlongrech.wordpress.com/2008/12/13/attachedcommandbehavior-v2-aka-acb/
Ответ 11
Использование инструмента 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();
});
С другой стороны, я обрабатываю событие Closing
в MainWindow
, используя экземпляр ViewModel:
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (((ViewModel.MainViewModel)DataContext).CancelBeforeClose())
e.Cancel = true;
}
CancelBeforeClose
проверяет текущее состояние модели представления и возвращает true, если закрытие должно быть остановлено.
Надеюсь, что это поможет кому-то.
Ответ 12
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
MessageBox.Show("closing");
}