Реализация паттерна MVVM с использованием IoC-контейнера, как метод избавления от зависимости между компонентами системы
Сопоставление модели представления и представления
Теперь, когда 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 будет отображено представление, найденное по описанному выше алгоритму.