Особенности отображения диалоговых окон в 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: }