Вложенный контекст данных с Unity - PullRequest
1 голос
/ 20 марта 2011

В прошлом году я проходил курс обучения по VB.Net + WPF в университете.Для финального проекта я решил попробовать MVVM (мы вообще не обсуждали его, я просто исследовал его и подумал, что это будет полезное упражнение).Это был хороший опыт, но я уверен, что мог бы сделать несколько неудачных решений, когда дело дошло до дизайна.

С тех пор я закончил и моя работа не имеет ничего общего с WPF или разработкой Windows, однакоЯ разрабатывал небольшое приложение в свое время и подумал, что было бы интересно использовать C # и WPF (C # - это язык, с которым мне очень нравится работать, и мне понравилось работать с WPF, так что это довольно логичный выбор).

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

Я использовал In The Box MVVM Training в качестве руководства и будупри этом используется Unity для внедрения зависимостей.

Теперь в примере приложения, разработанного в руководстве, есть модель единого представления (MainWindowViewModel).MainWindow - это в значительной степени контейнер с 3 или 4 элементами UserControl, которые совместно используют DataContext из MainWindow.

В моем приложении я хотел бы иметь интерфейс на основе вкладок.Как таковое, MainWindow будет в первую очередь связано с отображением списка кнопок для переключения текущего представления (т. Е. Перехода от представления «добавление» к представлению списка).Каждое представление будет автономным UserControl, который будет реализовывать свой собственный DataContext.

Этот код в приложении выглядит следующим образом:

MainWindow window = container.Resolve<MainWindow>();
window.DataContext = container.Resolve<MainWindowViewModel>();
window.Show();

Это прекрасно для установки контекста данных MainWindow.Однако как я буду справляться с назначением каждому пользовательскому контексту его собственного ViewModel в качестве DataContext?

РЕДАКТИРОВАТЬ: Чтобы быть более конкретным, когда я говорю, что интерфейс на основе вкладок, я не имею в виду это в смысле вкладок втекстовый редактор или веб-браузер.Скорее, каждая «вкладка» - это отдельный экран приложения - только один активный экран одновременно.

Кроме того, хотя сообщение Слаумы было несколько полезным, оно не объясняло, какидти о внедрении зависимостей в эти вкладки.Если, например, NewStatementView требуется для вывода своих данных, как бы я внедрил экземпляр класса, который реализует интерфейс IStatementWriter?

РЕДАКТИРОВАТЬ: чтобы упростить мой вопрос, я в основном пытаюсьвыяснить, как внедрить зависимость в класс, не пропуская каждую зависимость через конструктор.В качестве надуманного примера: у класса A есть класс B. Для класса B в качестве параметра-конструктора требуется реализация интерфейса I1.Класс B использует класс C. Класс C принимает, поскольку для конструктора параметров требуется реализация интерфейса I2.

Как бы я справился с этим сценарием, используя DI (и Unity)?Чего я не хочу делать, так это: публичный класс A (I1 i1, I2 i2) {....}

Я могу зарегистрировать все, используя Unity (то есть создать I2, затем C, затем I1 и Bи затем, наконец, вставить их в A), но тогда мне придется создавать все экземпляры, когда я хочу использовать A, даже если мне может даже не понадобиться экземпляр B (и что, если у меня будет целая куча других классов в той же ситуациикак B?).

Ответы [ 3 ]

2 голосов
/ 20 марта 2011

Для интерфейса на основе вкладок эта классическая статья о шаблоне MVVM в WPF может быть очень полезной. (Также предлагается загружаемый образец приложения.)

Основная идея связать каждую вкладку с UserControl заключается в следующем (только грубый набросок, подробности в статье):

В представлении MainWindow есть ContentControl ...

<ContentControl Content="{Binding Path=Workspaces}"
                ContentTemplate="{StaticResource WorkspacesTemplate}" />

... который привязывается к коллекции «рабочих областей» в MainWindowViewModel:

public ObservableCollection<WorkspaceViewModel> Workspaces { get; private set; }

Этот WorkspaceViewModel служит базовым классом для всех моделей представления, которые вы хотите отобразить в виде вкладки.

WorkspacesTemplate - это DataTemplate, который связывает TabControl с коллекцией WorkspaceViewModels:

<DataTemplate x:Key="WorkspacesTemplate">
    <TabControl IsSynchronizedWithCurrentItem="True"
                ItemsSource="{Binding}" />
    </TabControl>
</DataTemplate>

И для каждой конкретной вкладки у вас есть UserControl с ViewModel, который наследуется от WorkspaceViewModel ...

public class MySpecialViewModel : WorkspaceViewModel

... и который связан с UserControl DataTemplate:

<DataTemplate DataType="{x:Type vm:MySpecialViewModel}" >
    <v:MySpecialUserControl />
</DataTemplate>

Теперь, если вы хотите открыть вкладку, в MainWindowViewModel будет команда, которая создаст ViewModel, принадлежащий этой вкладке, и добавит ее в коллекцию Workspaces MainWindowViewModel:

void CreateMySpecialViewModel()
{
    MySpecialViewModel workspace = new MySpecialViewModel();
    Workspaces.Add(workspace);
}

Остальное выполняется механизмом привязки WPF. TabControl автоматически распознает, что этот специальный элемент рабочей области в коллекции имеет тип MySpecialViewModel, и выбирает правильный View / UserControl через шаблон DataTemplate, который мы определили для соединения ViewModel и View, и отображает его в новой вкладке.

2 голосов
/ 23 марта 2011

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

Существует два основных способа сделать это:

1:

Подключить модели видов к видам.

В этом сценарии XAML для MainWindow содержит дочерние элементы управления.В вашем случае некоторые из этих представлений, вероятно, будут скрыты (поскольку вы одновременно показываете только один экран).

Модели представлений подключаются к представлениям, обычно одним из двух способов:

В приведенном ниже коде после вызова InitializeComponents() или в обработчике события this.Loaded укажите this.DataContext = container.Resolve<MyViewModelType>();

. Обратите внимание, что в этом случае контейнер должен быть доступен глобально.Это типично для приложений, которые используют Unity.Вы спросили, как дети будут разрешать такие интерфейсы, как IStatementWriter.Если контейнер является глобальным, дочерние модели представлений могут просто вызвать container.Resolve<IStatementWriter>();

Другой способ связать модели представлений с представлениями - создать экземпляр модели представления в XAML, например:

<UserControl ...>
  <UserControl.DataContext>
    <local:MyViewModelType/>
  </UserControl.DataContext>
  ...
</UserControl>

Этот метод не совместим с Unity.Существует несколько сред MVVM, которые позволяют разрешать типы в XAML (я полагаю, что Caliburn делает).Эти платформы реализуют это с помощью расширений разметки .

2:

Подключите представление к модели представления.

Обычно это мой предпочтительный метод, хотя он усложняет дерево XAML.Этот метод работает очень хорошо, когда вам нужно выполнить навигацию в модели основного вида.

Создайте объекты модели дочернего вида в модели основного вида.

public class MainViewModel
{
   public MyViewModelType Model1 { get; private set; }
   public ViewModelType2 Model2 { get; private set; }
   public ViewModelType3 Model3 { get; private set; }

   public MainViewModel()
   {
      // This allows us to use Unity to resolve the view models!
      // We can use a global container or pass it into the constructor of the main view model
      // The dependencies for the child view models could then be resolved in their 
      //    constructors if you don't want to make the container global.
      Model1 = container.Resolve<MyViewModelType>();
      Model2 = container.Resolve<ViewModelType2>();
      Model3 = container.Resolve<ViewModelType3>();

      CurrentViewModel = Model1;
   }

   // You will need to fire property changed notifications here!
   public object CurrentViewModel { get; set; }
}

В главном представлении создайте один или несколько элементов управления содержимым и задайте для содержимого (-ей) модели представления, которые вы хотите отобразить.

<Window ...>
   ...
      <ContentControl Content="{Binding CurrentViewModel}">
         <ContentControl.Resources>
            <DataTemplate DataType="{x:Type local:MyViewModelType}">
               <local:MyViewType/>
            </DataTemplate>
            <DataTemplate DataType="{x:Type local:ViewModelType2}">
               <local:ViewType2/>
            </DataTemplate>
            <DataTemplate DataType="{x:Type local:ViewModelType3}">
               <local:ViewType3/>
            </DataTemplate>
         </ContentControl.Resources>
      </ContentControl>
   ...
</Window>

Обратите внимание, что мы связываемребенок просматривает модели с помощью шаблонов данных на ContentControl.Эти шаблоны данных могли быть определены на уровне Window или даже на уровне Application, но мне нравится помещать их в контекст, чтобы было легче увидеть, как представления привязываются к моделям представления.Если бы у нас был только один тип модели представления для каждого ContentControl, мы могли бы использовать свойство ContentTemplate вместо использования ресурсов.

РЕДАКТИРОВАТЬ: В этом методе модели представлений могут быть разрешены с помощью внедрения зависимостей, но представления разрешаются с помощью механизма разрешения ресурсов WPF.Вот как это работает:

Когда для содержимого ContentPresenter (базового компонента в ContentControl) задан объект, который НЕ является визуальным (не производным от класса Visual), WPF ищет шаблон данных для отображения объекта.Сначала он использует любые явные шаблоны данных, установленные на элементе управления хоста (например, свойство ContentTemplate в ContentControl).Затем он ищет логическое дерево, исследуя ресурсы каждого элемента в дереве для DataTemplate с ключом ресурса {x:Type local:OBJECT_TYPE}, где OBJECT_TYPE - тип данных контента.Обратите внимание, что в этом случае он находит шаблоны данных, которые мы определили локально.Когда стиль, шаблон элемента управления или шаблон данных определены с целевым типом, но не с именованным ключом, тип становится ключом.Window и Application находятся в логическом дереве, поэтому ресурсы / шаблоны, определенные здесь, также будут найдены и разрешены, если они не будут расположены в ресурсах элемента управления хоста.

Один последний комментарий.Если шаблон данных не найден, WPF вызывает ToString() для объекта содержимого и использует результат в качестве визуального содержимого.Если ToString() не переопределено каким-либо значимым образом, результатом будет TextBlock, содержащий тип содержимого.<- </p>

Когда вы обновляете свойство CurrentViewModel на MainViewModel, содержимое и представление в главном представлении будут автоматически изменяться, пока вы запускаете уведомление об изменении свойства в модели основного представления.

Дайте мне знать, если я что-то пропустил или вам нужна дополнительная информация.

0 голосов
/ 20 марта 2011

В точке, где вы разрешаете ваши представления, полученные из UserControl, используйте внедрение свойства , чтобы разрешить новый ViewModel для каждого и установить свойство DataContext представления.

...