Опубликован: 13.12.2011 | Доступ: свободный | Студентов: 1021 / 34 | Оценка: 4.29 / 4.57 | Длительность: 13:56:00
Лекция 11:

Реализация паттерна MVVM с использованием IoC-контейнера, как метод избавления от зависимости между компонентами системы

< Лекция 10 || Лекция 11: 1234 || Лекция 12 >

Сопоставление модели представления и представления

Теперь, когда MEF имеет информацию как о представлении, так и о модели представления, следует реализовать логику сопоставления, которая на основании соглашения об именовании определяет представление, соответствующее переданной модели представления.

Обычной практикой является добавление суффикса ViewModel моделям представления и суффикса View представлениям. Таким образом, имея имя типа модели представления, достаточно заменить в нем ViewModel на View, чтобы получить имя представления.

  1: var view = ServiceLocator.Current.GetInstance<IView>(
  2: 		viewModel.GetType().Name.Replace("ViewModel", "View"));

На практике часто возникает ситуация, в которой целое семейство моделей представления, объединенное в одну иерархию, соответствует единственному представлению. Например, модели представления по редактированию доменных объектов UserEditViewModel, RoleEditViewModel будут иметь общего предка EditViewModel, и обеим моделям представления должно быть сопоставлено одно представление EditView, содержащее элемент управления DataForm, предоставляющий обобщенный интерфейс редактирования объекта на основе отражения типов.

Простым решением будет создание 2 наследников для представления EditView, UserEditView и RoleEditView, не содержащих никакой логики и служащих для соблюдения соглашения о наименовании. Однако более разумно включить в логику поиска соответствия обход по всему дереву наследования, до тех пор, пока не будет найдено соответствие:

  1: IView view;
  2: Type viewModelType = viewModel.GetType();
  3:
  4: do
  5: {
  6:     view = ServiceLocator.Current.GetInstance<IView>(
  7:               viewModelType.Name.Replace("ViewModel", "View"));
  8:
  9:     if (view != null)
 10:     {
 11:         break;
 12:     }
 13:
 14:     viewModelType = viewModelType.BaseType;
 15: } while (viewModelType != null);

Для возможности повторного использования данная логика добавляется в наследника класса ContentControl, который по переданной через зависимое свойство ViewModel модели представления находит соответствующее представление и устанавливает её своим содержимым:

  1: /// <summary>
  2: /// Presents a View corresponding to the View Model
  3: /// </summary>
  4: public class ViewModelPresenter : ContentControl
  5: {
  6:     /// <summary>
  7:     /// Initializes a new instance
  8:     /// </summary>
  9:     public ViewModelPresenter()
 10:     {
 11:         HorizontalContentAlignment = HorizontalAlignment.Stretch;
 12:         VerticalContentAlignment = VerticalAlignment.Stretch;
 13:     }
 14:
 15:     // Dependency properties
 16:     public static readonly DependencyProperty ViewModelProperty
 17:       = DependencyProperty.Register(
 18:         "ViewModel", typeof(object), typeof(ViewModelPresenter),
 19:         new PropertyMetadata(null, OnViewModelPropertyChanged));
 20:
 21:     // Private fields
 22:     private Type _oldViewType;
 23:
 24:     /// <summary>
 25:     /// View Model to be presented
 26:     /// </summary>
 27:     public object ViewModel
 28:     {
 29:         get { return GetValue(ViewModelProperty); }
 30:         set { SetValue(ViewModelProperty, value); }
 31:     }
 32:
 33:     private static void OnViewModelPropertyChanged(
 34:        DependencyObject changedObject,
 35:        DependencyPropertyChangedEventArgs args)
 36:     {
 37:         var contentControl = (ViewModelPresenter)changedObject;
 38:         contentControl.RefreshContentPresenter();
 39:     }
 40:
 41:     private void RefreshContentPresenter()
 42:     {
 43:         if (ViewModel == null)
 44:         {
 45:             Content = null;
 46:             _oldViewType = null;
 47:
 48:             return;
 49:         }
 50:
 51:         IView view;
 52:         Type viewModelType = ViewModel.GetType();
 53:
 54:         do
 55:         {
 56:             view = ServiceLocator.Current.GetInstance<IView>(
 57:                 viewModelType.Name.Replace("ViewModel", "View"));
 58:
 59:             if (view != null)
 60:             {
 61:                 break;
 62:             }
 63:
 64:             viewModelType = viewModelType.BaseType;
 65:         } while (viewModelType != null);
 66:
 67:         if (view != null)
 68:         {
 69:             Type viewType = view.GetType();
 70:
 71:             if (viewType == _oldViewType && Content is IView)
 72:             {
 73:                 ((IView)Content).DataContext = ViewModel;
 73:             }
 74:             else
 75:             {
 76:                 view.DataContext = ViewModel;
 77:                 Content = view;
 78:                 _oldViewType = viewType;
 79:             }
 80:         }
 81:         else
 82:         {
 83:             Content = "View hasn't been found";
 84:             _oldViewType = null;
 85:         }
 86:     }
 87: }

В данном элементе управления также реализовано простейшее кеширование: если представление не изменилось, а изменилась только модель представления, то создания нового объекта не произойдет, и новая модель представления будет назначена контекстом представления взамен старой.

Далее, в если в XAML разметке необходимо отобразить представление, соответствующее определенной модели представления, достаточно вставить элемент управления ViewModelPresenter и передать ему в зависимое свойство данную модель представления. Внутри ViewModelPresenter будет отображено представление, найденное по описанному выше алгоритму.

< Лекция 10 || Лекция 11: 1234 || Лекция 12 >