Не так давно, в ходе изучения MVVM-паттерна, наткнулся на такой вопрос: "А как все-таки открывать новые диалоговые окна (View) из ViewModel?".
Небольшая справка
MVVM - Model- View- View Model - паттерн проектирования приложений. Очень хорошо реализуется в WPF благодаря привязкам (Bindings).
Модель (Model) - Модель данных, Например:
Кликните здесь для просмотра всего текста
| Java | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
| class Student
{
string FirstName;
string SecondName;
int Age;
double mark;
//...
func void SetMark(double mark) {
self.mark = mark;
}
//...
} |
|
.
Представление (View) — это графический интерфейс (окно, кнопки и т.п.). Представление является подписчиком на событие изменения значений свойств или команд, предоставляемых Моделью представления. В случае, если в Модели представления изменилось какое-либо свойство, то она оповещает всех подписчиков об этом, и Представление, в свою очередь, запрашивает обновленное значение свойства из Модели представления. В случае, если пользователь воздействует на какой-либо элемент интерфейса, Представление вызывает соответствующую команду, предоставленную Моделью представления.
Модель представления (ViewModel) является, с одной стороны, абстракцией Представления, а с другой, предоставляет обёртку данных из Модели, которые подлежат связыванию. То есть, она содержит Модель, которая преобразована к Представлению, а также содержит в себе команды, которыми может пользоваться Представление, чтобы влиять на Модель.
(с) Вики
По принципу MVVM ViewModel не знает о существовании View, как и View не знает о ViewModel. Этакая обособленность. По этому делать что-то вроде:| C# | 1
| new MainWindow1().ShowDialog(); |
|
внутри ViewModel будет нарушением использования паттерна.
Через некоторое время путешествия по интернету в поисках хорошего работоспособного и паттерно-чистого решения наткнулся на одного человека, который посоветовал использовать некий сервис, который будет выполнять всю грязную работу. Ранее уже выкладывал сей способ в одной из тем форума в разделе WPF.
C#
| C# | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
| public interface IDialogService
{
bool? ShowDialog(ViewModelBase vm);
MessageBoxResult MessageBox(string messageBoxText, string caption);
MessageBoxResult MessageBox(string messageBoxText, string caption, MessageBoxButton button);
MessageBoxResult MessageBox(string messageBoxText, string caption, MessageBoxButton button,
MessageBoxImage iconImage);
}
public class DialogService : IDialogService
{
private static DialogService _default;
private readonly Stack<Window> _parents = new Stack<Window>();
private readonly IDictionary<Type, Type> _windows = new Dictionary<Type, Type>();
public DialogService(Window mainWindow)
{
_parents.Push(mainWindow);
}
public DialogService()
{
}
public static DialogService Default => _default ?? (_default = new DialogService());
public bool? ShowDialog(ViewModelBase vm)
{
var vmType = vm.GetType();
if (!_windows.ContainsKey(vmType))
throw new ArgumentException("no registerd window for type " + vmType.Name);
var viewType = _windows[vmType];
var view = (Window) Activator.CreateInstance(viewType);
view.DataContext = vm;
view.Owner = _parents.Peek();
view.Closed += WindowClosed;
_parents.Push(view);
return view.ShowDialog();
}
public MessageBoxResult MessageBox(string messageBoxText, string caption)
{
return WPF.MessageBox.Show(_parents.Peek(), messageBoxText, caption);
}
public MessageBoxResult MessageBox(string messageBoxText, string caption, MessageBoxButton button)
{
return WPF.MessageBox.Show(_parents.Peek(), messageBoxText, caption, button);
}
public MessageBoxResult MessageBox(string messageBoxText, string caption, MessageBoxButton button,
MessageBoxImage iconImage)
{
return WPF.MessageBox.Show(_parents.Peek(), messageBoxText, caption, button, iconImage);
}
public void SetMainParent(Window window)
{
_parents.Clear();
_parents.Push(window);
}
public void Register<T, TK>()
where T : ViewModelBase
where TK : Window
{
_windows.Add(typeof (T), typeof (TK));
}
private void WindowClosed(object sender, EventArgs e)
{
var view = (Window) sender;
view.Closed -= WindowClosed;
_parents.Pop();
var vm = view.DataContext as IDisposable;
vm?.Dispose();
}
} |
|
Насколько можно видеть - данный сервис позволяет произвести связывание ViewModel и View, который будет отображать данные из первого и отображение View по заданному ViewModel.
То есть - у нас имеется место в приложении, где нам необходимо отобразить следующее окно с какой-либо информацией. Для этого мы создаем в нужном месте ViewModel, в котором будут содержаться требуемые данные и "просим сервис отобразить данный ViewModel".
Таким образом, определив в App.xaml обработчик события Startup и в App.xaml.cs в обработчике этого пропишем что-то вроде:| C# | 1
2
3
4
5
6
7
8
9
10
11
12
13
| var _dialogService = DialogService.Default;
_dialogService.Register<FirstViewModel, FirstView>();
_dialogService.Register<SecondViewModel, SecondView>();
_dialogService.Register<ThirdViewModel, ThirdView>();
var v = new SecondView();
var vm = new SecondViewModel(_dialogService);
v.DataContext = vm;
_dialogService.SetMainParent(v);
v.ShowDialog(); |
|
И, допустим, в SecondViewModel у нас имеется метод, "вызывающий другое окно":| C# | 1
2
| var fvm = new FirstViewModel(editedperson);
_dialogService.ShowDialog(fvm)); |
|
Таким образом у нас имеется более-менее чистый способ решения вопроса об открытии других окон в рамках паттерна MVVM.
PS. Как можно было заметить - DialogService реализует паттерн синглтон. Как к нему относится - дело ваше. Человек, посоветовавший данный способ не слишком позитивно отзывался о данном паттерне, посему рекомендовал "протаскивать" ранее созданный _dialogService по всем ViewModel (с помощью передачи его в качестве аргумента в конструктор ViewModel)
|