Особенности отображения диалоговых окон в WPF и Silverlight версиях приложения
Цель лекции: показать читателям на примере фрагментов кода механизм отображения диалоговых окон в многослойном кроссплатформенном Silverlight/WPF MVVM приложении.
Понятие ICloseableViewModel и IChildViewModel
В предыдущей лекции было введено понятие базовой модели представления, и определяющего её интерфейса, IViewModel. Получившееся простейшее приложение состояло из главной модели представления, MainViewModel, и соответствующего ей представления, MainView.
Сложно представить себе программу, обходящуюся одним главным окном. В любом MVVM приложении необходимо не только взаимодействовать с сервисами на уровне главной модели представления, но и отображать дополнительную информацию в диалоговых окнах. В соответствии с шаблоном MVVM вся логика приложения должна находиться на уровне модели представления, а значит должна быть возможность из произвольной модели представления инициировать открытие дочерней модели представления.
Логично ввести понятие закрываемой модели представления, которая может быть отображена вызовом метода Show() и закрыта вызовом метода Close():
1: /// <summary>
2: /// Interface of a View Model that can be closed
3: /// </summary>
4: public interface ICloseableViewModel : IViewModel
5: {
6: /// <summary>
7: /// Shows whether View Model has been closed
8: /// </summary>
9: bool IsClosed { get; }
10:
11: /// <summary>
12: /// Event raised when the View Model is closed
13: /// </summary>
14: event EventHandler Closed;
15:
16: /// <summary>
17: /// Closes View Model
18: /// </summary>
19: void Close();
20:
21: /// <summary>
22: /// Registers View Model to be shown
23: /// </summary>
24: void Show();
25: }Флаг IsClosed сообщает о том, была ли модель представления уже закрыта, а событие Close инициируется как раз в момент закрытия.
Для дальнейшего расширения иерархии интерфейсов моделей представления необходимо ввести интерфейс модели представления с заголовком:
1: /// <summary>
2: /// Interface of a View Model that has a title
3: /// </summary>
4: public interface IEntitledViewModel : IViewModel
5: {
6: /// <summary>
7: /// View Model's title
8: /// </summary>
9: string Title { get; }
10: }Спецификацией понятия закрываемой модели представления будет модель представления дочернего окна, имеющая заголовок:
1: /// <summary>
2: /// Interface of Child View's View Model
3: /// </summary>
4: public interface IChildViewModel
5: : ICloseableViewModel, IEntitledViewModel
6: {
7: //
8: }Понятие IChildViewModelManager
Очевидно, что модели представления дочерних окон не могут работать сами по себе, и необходим механизм, отображающий их в настоящие дочерние окна целевой платформы (Silverlight или WPF).
Для этих целей необходимо ввести менеджер дочерних моделей представления: он ведет учет видимых в настоящий момент моделей представления и сообщает о них всем внешним слушателям, подписанным на изменения коллекции ViewModels:
1: /// <summary>
2: /// Interface of class managing closeable View Models of
3: /// the specified type
4: /// </summary>
5: public interface IChildViewModelManager
6: : ICloseableViewModelPresenter<IChildViewModel>
7: {
8: /// <summary>
9: /// Collection of managed View Models
10: /// </summary>
11: ReadOnlyObservableCollection<IChildViewModel>
12: ViewModels { get; }
13: }Здесь используется понятие презентера закрываемой модели представления, который принимает к регистрации видимые модели представления:
1: /// <summary>
2: /// Interface of closeable View Model presenter
3: /// </summary>
4: [InheritedExport]
5: public interface ICloseableViewModelPresenter<in TViewModelBase>
6: where TViewModelBase : ICloseableViewModel
7: {
8: /// <summary>
9: /// Shows <paramref name="viewModel" />
10: /// </summary>
11: void ShowViewModel(TViewModelBase viewModel);
12: }Реализация менеджера дочерних моделей представления принимает к регистрации видимые модели представления дочерних окон и добавляет их во внутреннюю коллекцию. Одновременно с регистрацией происходит привязка к событию Closed модели представления, по которому она снимается с регистрации в менеджере и удаляется из коллекции:
1: /// <summary>
2: /// Manages closeable View Models of the specified type
3: /// </summary>
4: internal class ChildViewModelManager
5: : IChildViewModelManager
6: {
7: // Private fields
8: private readonly ObservableCollection<IChildViewModel>
9: _viewModelsInternal =
10: new DispatchObservableCollection<IChildViewModel>();
11: private ReadOnlyObservableCollection<IChildViewModel>
12: _viewModels;
13:
14: /// <summary>
15: /// Collection of managed View Models
16: /// </summary>
17: public ReadOnlyObservableCollection<IChildViewModel>
18: ViewModels
19: {
20: get
21: {
22: return _viewModels ?? (_viewModels =
23: new ReadOnlyObservableCollection<IChildViewModel>(
24: _viewModelsInternal));
25: }
26: }
27:
28: #region ICloseableViewModelPresenter<TViewModelBase> Members
29:
30: void ICloseableViewModelPresenter<IChildViewModel>
31: .ShowViewModel(IChildViewModel viewModel)
32: {
33: ShowViewModelCore(viewModel);
34: }
35:
36: #endregion
37:
38: /// <summary>
39: /// Closes <paramref name="viewModel" />
40: /// </summary>
41: protected virtual void CloseViewModelCore
42: (IChildViewModel viewModel)
43: {
44: viewModel.Closed -= OnViewModelClosed;
45:
46: Debug.Assert(_viewModelsInternal.Contains(viewModel));
47: _viewModelsInternal.Remove(viewModel);
48: }
49:
50: /// <summary>
51: /// Shows <paramref name="viewModel" />,
52: /// adding it to collection
53: /// </summary>
54: protected virtual void ShowViewModelCore
55: (IChildViewModel viewModel)
56: {
57: Debug.Assert(!viewModel.IsClosed);
58: viewModel.Closed += OnViewModelClosed;
59:
60: Debug.Assert(!_viewModelsInternal.Contains(viewModel));
61: _viewModelsInternal.Add(viewModel);
62: }
63:
64: private void OnViewModelClosed(object sender, EventArgs e)
65: {
66: Debug.Assert(sender is IChildViewModel);
67: CloseViewModelCore((IChildViewModel)sender);
68: }
69: }