2013-03-28 2 views
0

Если какое-либо промежуточное приложение MVVM, которое имеет более 5 видов и модов просмотра, есть ли какие-либо рекомендации по разработке шаблонов, как делать строительные леса такого приложения?Вождение приложения MVVM

Сейчас я обычно есть контроллер, который создается в App.OnStartup которых:

  • устанавливает основной вид
  • впрыскивает подвидов (обычно у меня есть MainWindow со строкой состояния и навигации, которая имеет "внутренние окна")
  • обрабатывает брак взглядов и viewmodels.
  • ручка навигации (идущая от просмотра для просмотра B)
  • поддерживает навигацию (строку навигации и вещи, как типичный NavigationService.GoBack())

Я считаю, что уже есть хорошие образцы дизайна, но не из о которых я слышал или читал.

Вопрос в следующем: Есть ли общепринятый образец того, как обращаться с муфтой viewmodel и view (установка datacontext) и навигации между видами?

На мой взгляд, оба вида представления (установка DataContext в XAML) и ViewModel-First (пусть viewmodel получают представление, введенное через DI/IOC), не так хороши, потому что они имеют зависимости между представлением и viewmodel.

Plain MVVM не делает никаких предположений о том, как настроить всю машину MVVM. Мне просто интересно, что эта довольно распространенная проблема не имеет «готового» решения. Контроллеры широко используются, я считаю. Как другие решают это?

+0

Я не вижу, что вы здесь просите. –

+0

он просит лучший способ реализовать MVVM-приложение в своем приложении. –

+0

отредактированный вопрос для большей ясности –

ответ

1

Некоторые шаблоны проектирования для рассмотрения - Inversion of Control (IoC) и Event Aggregator.

Для C#/MVVM Caliburn Micro Framework (является одной из пары) делает IoC и агрегацию событий намного проще.

Вы правильно определили основную проблему MVVM в том, что нет готового решения, чтобы действительно отделить ViewModel от представления. Это основная концепция, что ViewModels предназначены для использования с Views. Проблема сводится к тому, как управлять экземплярами пар ViewModel/View.

Просмотреть первый подход предполагает, что View знает и может создавать экземпляры ViewModels по мере необходимости - это проблема для SoC, потому что у любого класса View теперь есть несколько обязанностей; разворачивание ViewModel и управление пользовательским интерфейсом.

Просмотреть модель сначала сложно, потому что она часто приводит к нарушению одного из главных теней MVVM - что виртуальная машина должна быть тестируемой без каких-либо связанных представлений.

Здесь обычно приходит IoC. IoC обычно находится в слое «Вид» (это значит, что он должен иметь доступ ко всем классам View и ViewModel по мере необходимости), он не должен быть самим представлением. Часто лучше думать о вашем менеджере IoC как о контроллере, который приводит к псевдо-шаблону MVCVM. Единственной целью этого «контролера» становится предоставление спама экземпляров View и ViewModel тем, кто в нем нуждается.

Шаблон Aggregator событий действительно помогает в этом, потому что классам в ViewModel и View больше не нужно беспокоиться о том, с кем они сопряжены, и они могут быть связаны только с другими классами на своем собственном уровне. Специфической View Model не нужно заботиться о том, кто отправил событие «Update Load Progress», все, что ему нужно, чтобы отвечать за результаты обработки события, установив его свойство прогресса.

+0

Да, Caliburn представляет совершенно новый способ справиться с такими сценариями и в некоторой степени поддерживает другие проблемы, такие как EventToCommand (не знаю точное имя в Caliburn). IoC всегда является проблемой даже в простых приложениях. –

+0

Prism Eventaggregator выглядит очень интересно, возможно, это то, что должен иметь любой «контроллер»/«ведущий»/«messanger». –

+0

@MareInfinitus Я никогда не использовал PRISM, но я слышал, что он также имеет хорошие рамки для Eggreg Aggregator и IoC. Как только я начал использовать шаблон агрегатора событий, я никогда не оглядывался назад ... – EtherDragon

1

Что касается «ссылки» между View и ViewModel, я нашел концепцию DataTemplateManager в this post действительно интересной. В основном, это позволяет делать такие вещи, как

DataTemplateManager.Register<TViewModel1,TView1>(); 
DataTemplateManager.Register<TViewModel2,TView2>(); 
DataTemplateManager.Register<TViewModel3,TView3>(); 

это может быть не лучшим решением, по общему признанию, но это очень удобно. Я уже включил это в свою собственную домашнюю MVVM-структуру.

+0

Интересная техника. Попробуй это в моем контроллере;) –

1

У меня есть небольшой проект, который содержит одноэлементный класс под названием ViewFinder, который имеет несколько статических методов, называемых MakeWindowFor(vm) и MakeDialogFor(vm), что и взять ViewModel в качестве параметра. ViewFinder имеет словарь, который я заполняю, что ссылки viewmodels с окнами, которые я установил, чтобы соответствовать им. Дополнительная информация может быть добавлена, потому что, возможно, образ живет внутри другого, а не просто является окном.

Это может быть не лучший способ выполнить задачу, но работает для моих нужд в этом проекте и не позволяет объектам представления не знать о фактической реализации представления. Предки всех моих моделей просмотра содержат события для таких вещей, как отображение ящиков сообщений, и все мои окна происходят из базового класса, который знает, как подписаться и реагировать на эти общие события.

public class ViewFinder { 
    private static ViewFinder m_Instance; 
    public static ViewFinder Instance { 
     get { 
      if (m_Instance == null) 
       m_Instance = new ViewFinder(); 
      return (m_Instance); 
     } 
    } 

    /// Maps viewmodels to windows/dialogs. The key is the type of the viewmodel, the value is the type of the window. 
    private Dictionary<Type, Type> ViewDictionary = new Dictionary<Type, Type>(); 

    /// Private constructor because this is a singleton class. 
    /// 
    /// Registers the viewmodels/views. 
    private ViewFinder() { 
     Register(typeof(SomeViewModel), typeof(SomeWindowsForViewModel)); 
     Register(typeof(SomeViewModel2), typeof(SomeWindowsForViewModel2)); 
    } 

    /// Registers a window with a viewmodel for later lookup. 
    /// <param name="viewModelType">The Type of the viewmodel. Must descend from ViewModelBase.</param> 
    /// <param name="windowType">The Type of the view. Must descend from WindowBase.</param> 
    public void Register(Type viewModelType, Type windowType) { 
     if (viewModelType == null) 
      throw new ArgumentNullException("viewModelType"); 
     if (windowType == null) 
      throw new ArgumentNullException("windowType"); 
     if (!viewModelType.IsSubclassOf(typeof(ViewModelBase))) 
      throw new ArgumentException("viewModelType must derive from ViewModelBase."); 
     if (!windowType.IsSubclassOf(typeof(WindowBase))) 
      throw new ArgumentException("windowType must derive from WindowBase."); 
     ViewDictionary.Add(viewModelType, windowType); 
    } 

    /// Finds the window registered for the viewmodel and shows it in a non-modal way. 
    public void MakeWindowFor(ViewModelBase viewModel) { 
     Window win = CreateWindow(viewModel); 
     win.Show(); 
    } 

    /// Finds a window for a viewmodel and shows it with ShowDialog(). 
    public bool? MakeDialogFor(ViewModelBase viewModel) { 
     Window win = CreateWindow(viewModel); 
     return (win.ShowDialog()); 
    } 

    /// Helper function that searches through the ViewDictionary and finds a window. The window is not shown here, 
    /// because it might be a regular non-modal window or a dialog. 
    private Window CreateWindow(ViewModelBase viewModel) { 
     Type viewType = ViewDictionary[viewModel.GetType()] as Type; 
     if (viewType == null) 
      throw new Exception(String.Format("ViewFinder can't find a view for type '{0}'.", viewModel.GetType().Name)); 
     Window win = Activator.CreateInstance(viewType) as Window; 
     if (win == null) 
      throw new Exception(String.Format("Activator returned null while trying to create instance of '{0}'.", viewType.Name)); 
     win.DataContext = viewModel; 
     return win; 
    } 
} 
+0

, чтобы вы реагировали в своем коде на такие события, как «ShowMessageBox», и ваши режимы просмотра не отображали на нем сообщениями? –

+0

Да. Мой класс предков 'ViewModelBase' реализует такие функции, как' RaiseMessageBoxEvent() ', который использует настраиваемые EventArgs. Затем все мои окна/диалоги сходят с класса 'WindowBase', который автоматически подписывается на эти общие события и реагирует. Представления используют заявления для приведения viewmodels в область видимости, но не наоборот. Представления подписываются на свои собственные события DataContextChanged, чтобы гарантировать, что они не отписываются от предыдущего. Класс ViewFinder, о котором я упомянул, похож на программный файл конфигурации, который соединяет их вместе. – Steve

+0

Ваш ViewFinder замечательный! На данный момент я играю с калберном, который, кажется, приносит много вещей, которые я действительно пропустил. –