Украина |
Особенности отображения диалоговых окон в WPF и Silverlight версиях приложения
ChildViewManager
Теперь, когда IChildViewModelManager ведет учет моделей представления дочерних окон, можно перейти к следующему понятию – менеджеру дочерних представлений. Он прослушивает коллекцию ViewModel на предмет изменения по событию CollectionChanged интерфейса INotifyCollectionChanged: если в коллекцию добавлена новая модель представления, то необходимо создать для неё дочернее окно и сохранить соответствие в словаре; затем, при удалении модели представления из коллекции достаточно найти дочернее окно по словарю, закрыть его и удалить запись о соответствии.
Следует заметить, что так как объекты пользовательского интерфейса как правило не позволяют взаимодействовать с собой из потока, отличного от GUI потока, а вызовы слоя моделей представления могут происходить в любом контексте, в том числе в потоке из пула потоков (наиболее часто в нем выполняются обратные методы вызова асинхронных операций Windows Communication Foundation сервисов), то необходимо явно отправлять события изменений коллекции ViewModels на Dispatcher GUI потока посредством DispatcherSynchronizationContext.
1: #if SILVERLIGHT 2: using ChildViewType = System.Windows.Controls.ChildWindow; 3: #else 4: using ChildViewType = System.Windows.Window; 5: #endif 6: 7: public class ChildViewManager 8: { 9: [ImportingConstructor] 10: public ChildViewManager 11: (IEnumerable<IChildViewModel> viewModelCollection) 12: { 13: OnViewModelCollectionChanged(viewModelCollection, 14: new NotifyCollectionChangedEventArgs 15: (NotifyCollectionChangedAction.Reset)); 16: 17: var notifiable = viewModelCollection 18: as INotifyCollectionChanged; 19: 20: if (notifiable != null) 21: { 22: notifiable.CollectionChanged += (sender, e) 23: => DispatcherSynchronizationContext.Post(arg => 24: OnViewModelCollectionChanged(sender, e), null); 25: } 26: } 27: 28: // Private readonly fields 29: protected static readonly DispatcherSynchronizationContext 30: DispatcherSynchronizationContext = 31: new DispatcherSynchronizationContext( 32: #if SILVERLIGHT 33: Deployment.Current.Dispatcher 34: #else 35: Application.Current.Dispatcher 36: #endif 37: ); 38: 39: // Private fields 40: private readonly IDictionary<IChildViewModel, ChildViewType> 41: _childViews = 42: new Dictionary<IChildViewModel, ChildViewType>(); 43: 44: /// <summary> 45: /// Closes all managed <see cref="ChildViewPresenter" /> 46: /// </summary> 47: protected virtual void CloseAllViews() 48: { 49: foreach (KeyValuePair<IChildViewModel, ChildViewType> 50: pair in _childViews) 51: { 52: CloseView(pair.Key); 53: } 54: } 55: 56: /// <summary> 57: /// Closes specified <see cref="ChildViewPresenter" /> 58: /// </summary> 59: protected virtual void CloseView 60: (IChildViewModel childViewModel) 61: { 62: Debug.Assert(_childViews.ContainsKey(childViewModel)); 63: 64: ChildViewType childView = _childViews[childViewModel]; 65: _childViews.Remove(childViewModel); 66: childView.Close(); 67: } 68: 69: /// <summary> 70: /// Shows specified <see cref="ChildViewPresenter" /> 71: /// </summary> 72: protected virtual void ShowView 73: (IChildViewModel childViewModel) 74: { 75: ChildViewType childWindow = new ChildViewPresenter 76: { DataContext = childViewModel }; 77: _childViews.Add(childViewModel, childWindow); 78: childWindow.Show(); 79: } 80: 81: private void OnViewModelCollectionChanged(object sender, 82: NotifyCollectionChangedEventArgs e) 83: { 84: switch (e.Action) 85: { 86: case NotifyCollectionChangedAction.Add: 87: foreach (IChildViewModel viewModel in e.NewItems) 88: { 89: ShowView(viewModel); 90: } 91: break; 92: 93: case NotifyCollectionChangedAction.Remove: 94: foreach (IChildViewModel viewModel in e.OldItems) 95: { 96: CloseView(viewModel); 97: } 98: break; 99: 100: case NotifyCollectionChangedAction.Reset: 101: CloseAllViews(); 102: 103: foreach (IChildViewModel viewModel 104: in (IEnumerable)sender) 105: { 106: ShowView(viewModel); 107: } 108: break; 109: 110: default: 111: throw new ArgumentOutOfRangeException 112: ("e.Action is out of range", (Exception)null); 113: } 114: } 115: }
ChildViewPresenter – это окно Window в случае WPF и ChildWindow в случае Silverlight. Данные элементы управления содержат в себе ViewModelPresenter, который уже в свою очередь определяет, какое представление необходимо отобразить в дочернем окне.
Дополнительной обработки требует случай закрытия дочернего окна через нажатие на иконку в верхнем правом углу. Реализованная логика менеджеров поддерживает корректную инициацию закрытия дочернего окна исключительно со слоя представления, поэтому закрытие одного только ChildViewPresenter не приведет к срабатыванию логики закрытия модели представления.
Для правильной работы приложения при попытке пользователя закрыть дочернее окно системными средствами необходимо отменить это закрытие в событии OnClosing и инициировать корректное закрытие модели представления, которое в свою очередь затем приведет к закрытию окна. С точки зрения пользователя поведение приложения будет корректным, и при этом архитектура приложения не будет загрязнена дополнительной логикой обработки специального случая.
Реализация ChildViewPresenter для WPF:
1: <Window x:Class="TestProject.ChildViewPresenter" 2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4: xmlns:viewModelMapping="clr-namespace:TestProject" 5: SizeToContent="WidthAndHeight" 6: Title="{Binding Title}" 7: WindowStartupLocation="CenterScreen"> 8: 9: <viewModelMapping:ViewModelPresenter 10: x:Name="ViewModelPresenter" ViewModel="{Binding}" /> 11: </Window>
Для Silverlight:
1: <controls:ChildWindow x:Class="TestProject.ChildViewPresenter" 2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4: xmlns:controls= 5: "http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" 6: xmlns:ViewModelMapping="clr-namespace:TestProject" 7: Title="{Binding Title}"> 8: 9: <ViewModelMapping:ViewModelPresenter ViewModel="{Binding}" /> 10: </controls:ChildWindow>
Код данных элементов управления одинаковый как в Silverlight, так и в WPF:
1: public partial class ChildViewPresenter 2: { 3: /// <summary> 4: /// Initializes a new instance 5: /// </summary> 6: public ChildViewPresenter() 7: { 8: InitializeComponent(); 9: } 10: 11: /// <summary> 12: /// Underlying View Model 13: /// </summary> 14: private ICloseableViewModel ViewModel 15: { 16: get 17: { 18: Debug.Assert(DataContext == null 19: || DataContext is ICloseableViewModel); 20: return (ICloseableViewModel)DataContext; 21: } 22: } 23: 24: /// <summary> 25: /// Processes window closing 26: /// </summary> 27: protected override void OnClosing(CancelEventArgs e) 28: { 29: base.OnClosing(e); 30: 31: Debug.Assert(ViewModel != null); 32: 33: if (!ViewModel.IsClosed) 34: { 35: e.Cancel = true; 36: ViewModel.Close(); 37: } 38: } 39: }