Как ViewModel должен закрыть форму?
Я пытаюсь изучить WPF и проблему MVVM, но столкнулся с проблемой. Этот вопрос похож, но не совсем такой, как этот (обработка-диалогов-в-wpf-с-mvvm)...
У меня есть форма "Логин", написанная с использованием шаблона MVVM.
Эта форма имеет ViewModel, которая содержит имя пользователя и пароль, которые связаны с представлением в XAML с использованием обычных привязок данных. Он также имеет команду "Войти", которая связана с кнопкой "Войти" в форме, кроме того, используется обычная привязка данных.
Когда запускается команда "Войти", она вызывает функцию в ViewModel, которая отключается и отправляет данные по сети для входа в систему. Когда эта функция завершается, есть 2 действия:
Логин был неверный - мы просто показываем MessageBox и все в порядке
Логин был действителен, нам нужно закрыть форму входа и вернуть ее в качестве
DialogResult
...
Проблема в том, что ViewModel ничего не знает о реальном представлении, так как же он может закрыть представление и сказать ему вернуть определенный DialogResult? Я мог бы вставить некоторый код в CodeBehind и / или передать View через ViewModel, но похоже, что он полностью победил бы весь смысл MVVM...
Обновить
В конце я просто нарушил "чистоту" шаблона MVVM и заставил View опубликовать Closed
событие, и выставить Close
метод. ViewModel тогда просто вызовет view.Close
, Представление известно только через интерфейс и подключено через контейнер IOC, поэтому тестирование и ремонтопригодность не теряются.
Кажется довольно глупым, что принятый ответ в -5 голосов! В то время как я хорошо осведомлен о хороших чувствах, которые можно получить, решая проблему, будучи "чистым", Конечно, я не единственный, кто думает, что 200 строк событий, команд и поведений просто для того, чтобы избежать однострочного метода в Название "узоры" и "чистота" немного смешно....
26 ответов
Я был вдохновлен ответом Теджуана, чтобы написать более простую прикрепленную собственность. Нет стилей, нет триггеров; вместо этого вы можете просто сделать это:
<Window ...
xmlns:xc="clr-namespace:ExCastle.Wpf"
xc:DialogCloser.DialogResult="{Binding DialogResult}">
Это почти так же чисто, как если бы команда WPF все правильно поняла и сделала DialogResult свойством зависимости в первую очередь. Просто положить bool? DialogResult
свойства на вашей ViewModel и реализовать INotifyPropertyChanged, и вуаля, ваша ViewModel может закрыть окно (и установить его DialogResult), просто установив свойство. MVVM в порядке.
Вот код для DialogCloser:
using System.Windows;
namespace ExCastle.Wpf
{
public static class DialogCloser
{
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached(
"DialogResult",
typeof(bool?),
typeof(DialogCloser),
new PropertyMetadata(DialogResultChanged));
private static void DialogResultChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var window = d as Window;
if (window != null)
window.DialogResult = e.NewValue as bool?;
}
public static void SetDialogResult(Window target, bool? value)
{
target.SetValue(DialogResultProperty, value);
}
}
}
Я также разместил это в своем блоге.
С моей точки зрения, вопрос довольно хороший, так как тот же подход будет использоваться не только для окна "Вход", но и для любого вида окна. Я рассмотрел много предложений, и ни один не подходит для меня. Пожалуйста, ознакомьтесь с моим предложением, которое было взято из статьи шаблона проектирования MVVM.
Каждый класс ViewModel должен наследовать от WorkspaceViewModel
это имеет RequestClose
событие и CloseCommand
собственность ICommand
тип. Реализация по умолчанию CloseCommand
собственность поднимет RequestClose
событие.
Чтобы закрыть окно, OnLoaded
метод вашего окна должен быть переопределен:
void CustomerWindow_Loaded(object sender, RoutedEventArgs e)
{
CustomerViewModel customer = CustomerViewModel.GetYourCustomer();
DataContext = customer;
customer.RequestClose += () => { Close(); };
}
или же OnStartup
метод вашего приложения:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow window = new MainWindow();
var viewModel = new MainWindowViewModel();
viewModel.RequestClose += window.Close;
window.DataContext = viewModel;
window.Show();
}
я думаю что RequestClose
событие и CloseCommand
реализация собственности в WorkspaceViewModel
довольно ясно, но я покажу им, чтобы быть последовательным:
public abstract class WorkspaceViewModel : ViewModelBase
// There's nothing interesting in ViewModelBase as it only implements the INotifyPropertyChanged interface
{
RelayCommand _closeCommand;
public ICommand CloseCommand
{
get
{
if (_closeCommand == null)
{
_closeCommand = new RelayCommand(
param => Close(),
param => CanClose()
);
}
return _closeCommand;
}
}
public event Action RequestClose;
public virtual void Close()
{
if ( RequestClose != null )
{
RequestClose();
}
}
public virtual bool CanClose()
{
return true;
}
}
И исходный код RelayCommand
:
public class RelayCommand : ICommand
{
#region Constructors
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
}
PS Не относитесь ко мне плохо из-за этих источников! Если бы они были у меня вчера, это спасло бы меня несколько часов...
PPS Любые комментарии или предложения приветствуются.
Я использовал прикрепленное поведение, чтобы закрыть окно. Свяжите свойство "signal" в вашей ViewModel с прикрепленным поведением (на самом деле я использую триггер). Когда оно установлено в true, поведение закрывает окно.
http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/
Здесь много комментариев, в которых приводятся аргументы за и против MVVM. Для меня я согласен с Нир; это вопрос правильного использования шаблона, а MVVM не всегда подходит. Кажется, что люди готовы пожертвовать всеми наиболее важными принципами разработки программного обеспечения, просто чтобы привести его в соответствие с MVVM.
Тем не менее,... я думаю, что ваше дело может быть хорошо с небольшим рефакторингом.
В большинстве случаев, с которыми я сталкивался, WPF позволяет вам обходиться без нескольких Window
s. Может быть, вы могли бы попробовать использовать Frame
с и Page
S вместо Windows с DialogResult
s.
В вашем случае мое предложение будет иметь LoginFormViewModel
обращаться с LoginCommand
и если логин неверен, установите свойство на LoginFormViewModel
до соответствующего значения (false
или какое-то значение типа enum, например UserAuthenticationStates.FailedAuthentication
). Вы бы сделали то же самое для успешного входа в систему (true
или какое-либо другое значение перечисления). Вы бы тогда использовали DataTrigger
который реагирует на различные состояния аутентификации пользователя и может использовать простой Setter
изменить Source
собственность Frame
,
Наличие вашего окна входа возвращает DialogResult
я думаю, это то, где ты запутался; тот DialogResult
действительно является собственностью вашей ViewModel. В моем, по общему признанию, ограниченном опыте работы с WPF, когда что-то не так, как обычно, потому что я думаю о том, как бы я сделал то же самое в WinForms.
Надеюсь, это поможет.
Предполагая, что ваш диалог входа в систему является первым окном, которое создается, попробуйте это внутри вашего класса LoginViewModel:
void OnLoginResponse(bool loginSucceded)
{
if (loginSucceded)
{
Window1 window = new Window1() { DataContext = new MainWindowViewModel() };
window.Show();
App.Current.MainWindow.Close();
App.Current.MainWindow = window;
}
else
{
LoginError = true;
}
}
Это простое и чистое решение - вы добавляете событие в ViewModel и приказывает окну закрыться, когда это событие сработало.
Для более подробной информации см. Мой блог, Закрыть окно из ViewModel.
Я бы справился с этим, добавив обработчик событий в свою ViewModel. Когда пользователь успешно вошел в систему, я бы запустил событие. В моем представлении я прикрепляюсь к этому событию, а когда оно срабатывает, я закрываю окно.
public partial class MyWindow: Window
{
public ApplicationSelection()
{
InitializeComponent();
MyViewModel viewModel = new MyViewModel();
DataContext = viewModel;
viewModel.RequestClose += () => { Close(); };
}
}
public class MyViewModel
{
//...Your code...
public event Action RequestClose;
public virtual void Close()
{
if (RequestClose != null)
{
RequestClose();
}
}
public void SomeFunction()
{
//...Do something...
Close();
}
}
Вот то, что я изначально сделал, и это работает, однако это выглядит довольно скучно и некрасиво (глобальная статика ничего не дает)
1: App.xaml.cs
public partial class App : Application
{
// create a new global custom WPF Command
public static readonly RoutedUICommand LoggedIn = new RoutedUICommand();
}
2: LoginForm.xaml
// bind the global command to a local eventhandler
<CommandBinding Command="client:App.LoggedIn" Executed="OnLoggedIn" />
3: LoginForm.xaml.cs
// implement the local eventhandler in codebehind
private void OnLoggedIn( object sender, ExecutedRoutedEventArgs e )
{
DialogResult = true;
Close();
}
4: LoginFormViewModel.cs
// fire the global command from the viewmodel
private void OnRemoteServerReturnedSuccess()
{
App.LoggedIn.Execute(this, null);
}
Позже я удалил весь этот код и просто LoginFormViewModel
вызовите метод Close в его представлении. Это оказалось намного приятнее и легче следовать. ИМХО смысл шаблонов в том, чтобы дать людям более простой способ понять, что делает ваше приложение, и в этом случае MVVM усложняло понимание, чем если бы я не использовала его, а теперь была антипаттерном.
Итак, этому вопросу уже почти 6 лет, и я до сих пор не могу найти здесь, что я думаю, что это правильный ответ, поэтому позвольте мне поделиться своими "2 центами"...
На самом деле у меня есть 2 способа сделать это, первый простой... второй справа, так что если вы ищете правильный, просто пропустите #1 и перейдите к # 2:
1. Быстрый и легкий (но не полный)
Если у меня небольшой проект, я иногда просто создаю CloseWindowAction во ViewModel:
public Action CloseWindow { get; set; } // In MyViewModel.cs
И кто бы ни создал View, или в коде View, я просто установил метод, который будет вызывать действие:
(помните, что MVVM - это разделение View и ViewModel... Код View, представленный здесь, по-прежнему является View, и до тех пор, пока существует правильное разделение, вы не нарушаете шаблон)
Если какая-то ViewModel создает новое окно:
private void CreateNewView()
{
MyView window = new MyView();
window.DataContext = new MyViewModel
{
CloseWindow = window.Close,
};
window.ShowDialog();
}
Или, если вы хотите, чтобы это было в вашем главном окне, просто поместите его под конструктор вашего представления:
public MyView()
{
InitializeComponent();
this.DataContext = new MainViewModel
{
CloseWindow = this.Close
};
}
когда вы хотите закрыть окно, просто вызовите Action на вашей ViewModel.
2. Правильный путь
Теперь правильный способ сделать это - использовать Prism (IMHO), и обо всем этом можно узнать здесь.
Вы можете сделать запрос взаимодействия, заполнить его любыми данными, которые вам понадобятся в новом окне, запустить его, закрыть и даже получить данные обратно. Все это инкапсулировано и одобрено MVVM. Вы даже получаете статус того, как окно было закрыто, например, если пользователь Canceled
или же Accepted
(Кнопка ОК) Окно и данные возвращаются, если вам это нужно. Это немного сложнее и ответ № 1, но это гораздо более полный и рекомендуемый шаблон от Microsoft.
Ссылка, которую я дал, содержит все фрагменты кода и примеры, так что я не буду беспокоиться о том, чтобы разместить здесь какой-либо код, просто прочитайте статью о загрузке Prism Quick Start и запустите ее, ее очень просто понять, просто немного подробнее заставить его работать, но преимущества больше, чем просто закрытие окна.
Это, вероятно, очень поздно, но я столкнулся с той же проблемой, и я нашел решение, которое работает для меня.
Я не могу понять, как создать приложение без диалогов (возможно, это просто блок разума). Так что я был в тупике с MVVM и показывал диалог. Итак, я наткнулся на эту статью CodeProject:
http://www.codeproject.com/KB/WPF/XAMLDialog.aspx
Это UserControl, который в основном позволяет окну находиться внутри визуального дерева другого окна (не разрешено в xaml). Он также предоставляет логическое свойство DependencyProperty под названием IsShowing.
Вы можете установить стиль, как правило, в resourcedictionary, который в основном отображает диалог всякий раз, когда свойство Content элемента управления!= Null через триггеры:
<Style TargetType="{x:Type d:Dialog}">
<Style.Triggers>
<Trigger Property="HasContent" Value="True">
<Setter Property="Showing" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
В представлении, где вы хотите отобразить диалоговое окно, просто имейте это:
<d:Dialog Content="{Binding Path=DialogViewModel}"/>
И в вашей ViewModel все, что вам нужно сделать, это установить для свойства значение (Примечание: класс ViewModel должен поддерживать INotifyPropertyChanged, чтобы представление узнало, что что-то произошло).
вот так:
DialogViewModel = new DisplayViewModel();
Чтобы сопоставить ViewModel с View, у вас должно быть что-то вроде этого в Resourcedictionary:
<DataTemplate DataType="{x:Type vm:DisplayViewModel}">
<vw:DisplayView/>
</DataTemplate>
При этом вы получаете однострочный код для отображения диалога. Проблема, которую вы получаете, заключается в том, что вы не можете действительно закрыть диалог только с помощью приведенного выше кода. Вот почему вы должны поместить событие в базовый класс ViewModel, который наследует DisplayViewModel, и вместо кода выше, напишите это
var vm = new DisplayViewModel();
vm.RequestClose += new RequestCloseHandler(DisplayViewModel_RequestClose);
DialogViewModel = vm;
Затем вы можете обработать результат диалога с помощью обратного вызова.
Это может показаться немного сложным, но как только закладывается фундамент, все довольно просто. Опять же это моя реализация, я уверен, что есть другие:)
Надеюсь, это поможет, это спасло меня.
К вашему сведению, я столкнулся с этой же проблемой, и мне кажется, что я нашел способ обойтись без глобальных или статических вычислений, хотя это может быть не лучшим ответом. Я позволю вам, ребята, решить это для себя.
В моем случае ViewModel, который создает окно для отображения (давайте назовем его ViewModelMain), также знает о LoginFormViewModel (используя приведенную выше ситуацию в качестве примера).
Поэтому я создал свойство LoginFormViewModel, имеющее тип ICommand (назовем его CloseWindowCommand). Затем, прежде чем я вызову.ShowDialog() в Window, я установил свойство CloseWindowCommand в LoginFormViewModel в метод window.Close() окна, который я создал. Затем внутри LoginFormViewModel все, что мне нужно сделать, это вызвать CloseWindowCommand.Execute(), чтобы закрыть окно.
Полагаю, это немного обходной путь / хак, но он работает хорошо, не нарушая паттерн MVVM.
Не стесняйтесь критиковать этот процесс столько, сколько хотите, я могу принять это!:)
Вы можете сделать так, чтобы ViewModel выставлял событие, на которое регистрируется View. Затем, когда ViewModel решает, что пора закрывать представление, оно запускает это событие, которое вызывает закрытие представления. Если вы хотите, чтобы конкретное значение результата было передано обратно, тогда у вас будет свойство в ViewModel для этого.
Просто чтобы добавить к огромному количеству ответов, я хочу добавить следующее. Предполагая, что у вас есть ICommand в вашей ViewModel, и вы хотите, чтобы эта команда закрыла свое окно (или любое другое действие в этом отношении), вы можете использовать что-то вроде следующего.
var windows = Application.Current.Windows;
for (var i=0;i< windows.Count;i++ )
if (windows[i].DataContext == this)
windows[i].Close();
Он не идеален и может быть сложным для тестирования (так как трудно подделать / заглушить статическое электричество), но он чище (ИМХО), чем другие решения.
Erick
Поведение - самый удобный способ здесь.
С одной стороны, он может быть привязан к заданной модели представления (которая может сигнализировать "закрыть форму!")
С другой стороны, он имеет доступ к самой форме, поэтому может подписаться на необходимые события, связанные с формой, или показать диалог подтверждения, или что-то еще.
Написание необходимого поведения может показаться скучным с первого раза. Однако теперь вы можете использовать его в каждой нужной форме, используя точный однострочный фрагмент XAML. И при необходимости вы можете извлечь его как отдельную сборку, чтобы его можно было включить в любой следующий проект, который вы хотите.
В итоге я смешал ответ Джо Уайта и некоторый код из ответа Адама Миллса, поскольку мне нужно было показать пользовательский элемент управления в программно созданном окне. Таким образом, DialogCloser не должен быть в окне, он может быть на самом пользовательском элементе управления
<UserControl ...
xmlns:xw="clr-namespace:Wpf"
xw:DialogCloser.DialogResult="{Binding DialogResult}">
И DialogCloser найдет окно пользовательского элемента управления, если оно не было прикреплено к самому окну.
namespace Wpf
{
public static class DialogCloser
{
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached(
"DialogResult",
typeof(bool?),
typeof(DialogCloser),
new PropertyMetadata(DialogResultChanged));
private static void DialogResultChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var window = d.GetWindow();
if (window != null)
window.DialogResult = e.NewValue as bool?;
}
public static void SetDialogResult(DependencyObject target, bool? value)
{
target.SetValue(DialogResultProperty, value);
}
}
public static class Extensions
{
public static Window GetWindow(this DependencyObject sender_)
{
Window window = sender_ as Window;
return window ?? Window.GetWindow( sender_ );
}
}
}
Я реализовал решение Джо Уайта, но столкнулся с проблемами со случайными ошибками " DialogResult может быть установлен только после того, как окно создано и показано как диалоговое окно ".
Я держал ViewModel после закрытия View и иногда позже открывал новый View, используя ту же виртуальную машину. Похоже, что закрытие нового View до того, как старый View был собран мусором, привело к тому, что DialogResultChanged попытался установить свойство DialogResult в закрытом окне, что спровоцировало ошибку.
Моим решением было изменить DialogResultChanged для проверки свойства окна IsLoaded:
private static void DialogResultChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var window = d as Window;
if (window != null && window.IsLoaded)
window.DialogResult = e.NewValue as bool?;
}
После внесения этого изменения любые вложения в закрытые диалоги игнорируются.
Почему бы просто не передать окно в качестве параметра команды?
C#:
private void Cancel( Window window )
{
window.Close();
}
private ICommand _cancelCommand;
public ICommand CancelCommand
{
get
{
return _cancelCommand ?? ( _cancelCommand = new Command.RelayCommand<Window>(
( window ) => Cancel( window ),
( window ) => ( true ) ) );
}
}
XAML:
<Window x:Class="WPFRunApp.MainWindow"
x:Name="_runWindow"
...
<Button Content="Cancel"
Command="{Binding Path=CancelCommand}"
CommandParameter="{Binding ElementName=_runWindow}" />
Вот простое решение без ошибок (с исходным кодом), оно работает для меня.
Получите вашу ViewModel из
INotifyPropertyChanged
Создайте наблюдаемое свойство CloseDialog во ViewModel
public void Execute() { // Do your task here // if task successful, assign true to CloseDialog CloseDialog = true; } private bool _closeDialog; public bool CloseDialog { get { return _closeDialog; } set { _closeDialog = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged([CallerMemberName]string property = "") { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(property)); } }
}
Прикрепить обработчик в представлении для этого изменения свойства
_loginDialogViewModel = new LoginDialogViewModel(); loginPanel.DataContext = _loginDialogViewModel; _loginDialogViewModel.PropertyChanged += OnPropertyChanged;
Теперь вы почти закончили. В обработчике события сделай
DialogResult = true
protected void OnPropertyChanged(object sender, PropertyChangedEventArgs args) { if (args.PropertyName == "CloseDialog") { DialogResult = true; } }
Другое решение - создать свойство с INotifyPropertyChanged в View Model, например DialogResult, а затем в Code Behind написать:
public class SomeWindow: ChildWindow
{
private SomeViewModel _someViewModel;
public SomeWindow()
{
InitializeComponent();
this.Loaded += SomeWindow_Loaded;
this.Closed += SomeWindow_Closed;
}
void SomeWindow_Loaded(object sender, RoutedEventArgs e)
{
_someViewModel = this.DataContext as SomeViewModel;
_someViewModel.PropertyChanged += _someViewModel_PropertyChanged;
}
void SomeWindow_Closed(object sender, System.EventArgs e)
{
_someViewModel.PropertyChanged -= _someViewModel_PropertyChanged;
this.Loaded -= SomeWindow_Loaded;
this.Closed -= SomeWindow_Closed;
}
void _someViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == SomeViewModel.DialogResultPropertyName)
{
this.DialogResult = _someViewModel.DialogResult;
}
}
}
Самый важный фрагмент _someViewModel_PropertyChanged
,DialogResultPropertyName
может быть какой-то общедоступной константой в SomeViewModel
,
Я использую этот вид трюка, чтобы внести некоторые изменения в View Controls в случае, когда это трудно сделать во ViewModel. OnPropertyChanged во ViewModel вы можете делать все, что вы хотите в View. ViewModel по-прежнему "тестируется модулем", и некоторые небольшие строки кода в коде не имеют значения.
Я бы пошел по этому пути:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
// View
public partial class TestCloseWindow : Window
{
public TestCloseWindow() {
InitializeComponent();
Messenger.Default.Register<CloseWindowMsg>(this, (msg) => Close());
}
}
// View Model
public class MainViewModel: ViewModelBase
{
ICommand _closeChildWindowCommand;
public ICommand CloseChildWindowCommand {
get {
return _closeChildWindowCommand?? (_closeChildWindowCommand = new RelayCommand(() => {
Messenger.Default.Send(new CloseWindowMsg());
}));
}
}
}
public class CloseWindowMsg
{
}
Хотя это не отвечает на вопрос о том, как сделать это с помощью модели представления, это показывает, как это сделать, используя только XAML + blend SDK.
Я решил загрузить и использовать два файла из Blend SDK, оба из которых вы можете использовать в качестве пакета от Microsoft через NuGet. Файлы:
System.Windows.Interactivity.dll и Microsoft.Expression.Interactions.dll
Microsoft.Expression.Interactions.dll дает вам хорошие возможности, такие как возможность устанавливать свойства или вызывать метод для вашей модели представления или другой цели, а также имеет другие виджеты внутри.
Немного XAML:
<Window x:Class="Blah.Blah.MyWindow"
...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
...>
<StackPanel>
<Button x:Name="OKButton" Content="OK">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction
TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
PropertyName="DialogResult"
Value="True"
IsEnabled="{Binding SomeBoolOnTheVM}" />
</i:EventTrigger>
</Button>
<Button x:Name="CancelButton" Content="Cancel">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:ChangePropertyAction
TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
PropertyName="DialogResult"
Value="False" />
</i:EventTrigger>
</Button>
<Button x:Name="CloseButton" Content="Close">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<!-- method being invoked should be void w/ no args -->
<ei:CallMethodAction
TargetObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
MethodName="Close" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<StackPanel>
</Window>
Обратите внимание, что если вы просто используете простое поведение "ОК / Отмена", вы можете уйти без использования свойств IsDefault и IsCancel, если окно показано с помощью Window.ShowDialog().
У меня лично были проблемы с кнопкой, для свойства IsDefault которой было установлено значение true, но она была скрыта при загрузке страницы. Казалось, он не хочет хорошо играть после того, как его показали, поэтому я просто устанавливаю свойство Window.DialogResult, как показано выше, и оно работает для меня.
Я прочитал все ответы, но я должен сказать, что большинство из них просто недостаточно хороши или даже хуже.
Вы могли бы обработать это красиво с классом DialogService, ответственность которого состоит в том, чтобы показать диалоговое окно и вернуть результат диалога. Я создал образец проекта, демонстрирующий его реализацию и использование.
Вот самые важные части:
//we will call this interface in our viewmodels
public interface IDialogService
{
bool? ShowDialog(object dialogViewModel, string caption);
}
//we need to display logindialog from mainwindow
public class MainWindowViewModel : ViewModelBase
{
public string Message {get; set;}
public void ShowLoginCommandExecute()
{
var loginViewModel = new LoginViewModel();
var dialogResult = this.DialogService.ShowDialog(loginViewModel, "Please, log in");
//after dialog is closed, do someting
if (dialogResult == true && loginViewModel.IsLoginSuccessful)
{
this.Message = string.Format("Hello, {0}!", loginViewModel.Username);
}
}
}
public class DialogService : IDialogService
{
public bool? ShowDialog(object dialogViewModel, string caption)
{
var contentView = ViewLocator.GetView(dialogViewModel);
var dlg = new DialogWindow
{
Title = caption
};
dlg.PART_ContentControl.Content = contentView;
return dlg.ShowDialog();
}
}
Разве это не проще? более прямолинейным, более читабельным и последним, но не менее простым в отладке, чем EventAggregator или другие подобные решения?
Как вы можете видеть, в моих моделях представления я использовал первый подход ViewModel, описанный в моем посте здесь: Лучшая практика вызова View из ViewModel в WPF
Конечно, в реальном мире DialogService.ShowDialog
должен иметь больше возможностей для настройки диалога, например, кнопки и команды, которые они должны выполнять. Есть разные способы сделать это, но это выходит за рамки:)
Создать Dependency Property
в вашем View
/любой UserControl
(или же Window
хочешь закрыть). Как ниже:
public bool CloseTrigger
{
get { return (bool)GetValue(CloseTriggerProperty); }
set { SetValue(CloseTriggerProperty, value); }
}
public static readonly DependencyProperty CloseTriggerProperty =
DependencyProperty.Register("CloseTrigger", typeof(bool), typeof(ControlEventBase), new PropertyMetadata(new PropertyChangedCallback(OnCloseTriggerChanged)));
private static void OnCloseTriggerChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
//write Window Exit Code
}
И свяжите это со свойством ViewModel:
<Window x:Class="WpfStackruTempProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="525"
CloseTrigger="{Binding Path=CloseWindow,Mode=TwoWay}"
Недвижимость в VeiwModel
:
private bool closeWindow;
public bool CloseWindow
{
get { return closeWindow; }
set
{
closeWindow = value;
RaiseChane("CloseWindow");
}
}
Теперь запустите операцию закрытия, изменив CloseWindow
значение в ViewModel.:)
Там, где вам нужно закрыть окно, просто поместите это в viewmodel:
та-да
foreach (Window window in Application.Current.Windows)
{
if (window.DataContext == this)
{
window.Close();
return;
}
}