Опубликован: 13.12.2011 | Уровень: для всех | Доступ: платный
Лекция 12:

Особенности отображения диалоговых окон в 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: }
Анисимов Михаил
Анисимов Михаил
Украина
Наталия Шаститко
Наталия Шаститко
Украина, Днепропетровск, Днепропетровский Гуманитарный Университет, 2014