MVVM - Краткое руководство

Хорошо упорядоченный и, возможно, наиболее часто используемый способ организации кода - это использовать шаблон «MVVM». ВModel, View, ViewModel (MVVM pattern) все о том, как организовать и структурировать код для написания поддерживаемых, тестируемых и расширяемых приложений.

Model - Он просто содержит данные и не имеет ничего общего с бизнес-логикой.

ViewModel - Он действует как связь / соединение между моделью и представлением и делает вещи красивыми.

View - Он просто хранит отформатированные данные и по существу делегирует все модели.

Отдельная презентация

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

  • С разделенным представлением класс пользовательского интерфейса намного проще. Конечно, у него есть XAML, но код, стоящий за ним, делает настолько мало, насколько это практически возможно.

  • Логика приложения принадлежит к отдельному классу, который часто называют моделью.

  • Однако это еще не все. Если вы остановитесь на этом, вы, вероятно, повторите очень распространенную ошибку, которая приведет вас к безумию привязки данных.

  • Многие разработчики пытаются использовать привязку данных для подключения элементов XAML непосредственно к свойствам модели.

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

  • Способ представления данных часто несколько отличается от того, как они структурированы внутри.

  • Более того, у большинства пользовательских интерфейсов есть состояние, которое не принадлежит модели приложения.

  • Например, если в вашем пользовательском интерфейсе используется перетаскивание, что-то должно отслеживать такие вещи, как, где сейчас находится перетаскиваемый элемент, как его внешний вид должен меняться, когда он перемещается над возможными целями перетаскивания, и как эти цели перетаскивания могут также изменяются при перетаскивании элемента над ними.

  • Такое состояние может быть на удивление сложным и требует тщательного тестирования.

  • На практике вам обычно нужен какой-то другой класс, расположенный между пользовательским интерфейсом и моделью. У этого есть две важные роли.

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

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

Шаблон MVVM - это, в конечном счете, современная структура шаблона MVC, поэтому основная цель остается неизменной - обеспечить четкое разделение между логикой предметной области и уровнем представления. Вот некоторые из преимуществ и недостатков шаблона MVVM.

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

Есть три важных ключевых момента, которые вытекают из применения MVVM, а именно:

Ремонтопригодность

  • Четкое разделение различных типов кода должно упростить переход к одной или нескольким из этих более детализированных и целенаправленных частей и внесение изменений без беспокойства.

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

Тестируемость

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

  • Это значительно упрощает написание модульных тестов на основе базовой логики.

  • Убедитесь, что он работает правильно, когда написано, и продолжает работать, даже когда что-то меняется при обслуживании.

Расширяемость

  • Иногда это перекрывается с ремонтопригодностью из-за четких границ разделения и более детализированных фрагментов кода.

  • У вас больше шансов сделать любую из этих частей более пригодной для повторного использования.

  • Он также имеет возможность заменять или добавлять новые фрагменты кода, которые делают аналогичные вещи в нужных местах в архитектуре.

Очевидная цель шаблона MVVM - абстракция представления, которая уменьшает объем бизнес-логики в коде программной части. Однако ниже приведены некоторые другие весомые преимущества:

  • ViewModel легче тестировать, чем код программной части или код, управляемый событиями.
  • Вы можете протестировать его без неудобной автоматизации пользовательского интерфейса и взаимодействия.
  • Уровень представления и логика слабо связаны.

Недостатки

  • Некоторые думают, что для простых пользовательских интерфейсов MVVM может быть излишним.
  • Точно так же в более крупных случаях может быть сложно разработать ViewModel.
  • Отладка будет немного сложной, когда у нас есть сложные привязки данных.

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

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

  • Уровень представления состоит из представлений.

  • Логический слой - это модели представления.

  • Уровень представления - это комбинация объектов модели.

  • Клиентские службы, которые создают и сохраняют их, либо направляют доступ в двухуровневом приложении, либо через вызовы службы, а затем в ваше приложение.

  • Клиентские службы официально не являются частью шаблона MVVM, но часто используются с MVVM для достижения дальнейшего разделения и предотвращения дублирования кода.

Типовые обязанности

В общем, модель - самая простая для понимания. Это модель данных на стороне клиента, которая поддерживает представления в приложении.

  • Он состоит из объектов со свойствами и некоторых переменных, содержащих данные в памяти.

  • Некоторые из этих свойств могут ссылаться на другие объекты модели и создавать граф объектов, который в целом является объектами модели.

  • Объекты модели должны вызывать уведомления об изменении свойств, что в WPF означает привязку данных.

  • Последней обязанностью является проверка, которая является необязательной, но вы можете встроить информацию о проверке в объекты модели, используя функции проверки привязки данных WPF через интерфейсы, такие как INotifyDataErrorInfo / IDataErrorInfo.

Посмотреть обязанности

Основное назначение и обязанности представлений - определить структуру того, что пользователь видит на экране. Конструкция может содержать статические и динамические части.

  • Статические части - это иерархия XAML, которая определяет элементы управления и макет элементов управления, из которых состоит представление.

  • Динамическая часть похожа на анимацию или изменения состояния, которые определены как часть представления.

  • Основная цель MVVM состоит в том, чтобы в представлении не было кода.

  • Невозможно, чтобы не было никакого кода. По крайней мере, вам нужен конструктор и вызов для инициализации компонента.

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

  • Существуют также другие типы кода, которые должны быть включены в код, стоящий за любым кодом, который требуется для ссылки на элемент пользовательского интерфейса, по сути, это код просмотра.

Обязанности ViewModel

  • ViewModel - это основная суть приложения MVVM. Основная ответственность ViewModel - предоставить данные для представления, чтобы оно могло выводить эти данные на экран.

  • Это также позволяет пользователю взаимодействовать с данными и изменять данные.

  • Другой ключевой задачей ViewModel является инкапсуляция логики взаимодействия для представления, но это не означает, что вся логика приложения должна входить в ViewModel.

  • Он должен уметь обрабатывать соответствующую последовательность вызовов, чтобы все происходило правильно, в зависимости от пользователя или любых изменений в представлении.

  • ViewModel также должен управлять любой логикой навигации, например, решать, когда пора перейти к другому представлению.

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

Давайте посмотрим на простой пример, в котором мы будем использовать подход MVVM.

Step 1 - Создайте новый проект приложения WPF MVVMDemo.

Step 2 - Добавьте три папки (Model, ViewModel и Views) в свой проект.

Step 3 - Добавьте класс StudentModel в папку Model и вставьте приведенный ниже код в этот класс.

using System.ComponentModel;

namespace MVVMDemo.Model {
 
   public class StudentModel {}
	
   public class Student : INotifyPropertyChanged {
      private string firstName; 
      private string lastName;
		
      public string FirstName { 
         get { 
            return firstName; 
         }
			
         set { 
            if (firstName != value) { 
               firstName = value; 
               RaisePropertyChanged("FirstName"); 
               RaisePropertyChanged("FullName"); 
            } 
         } 
      }
		
      public string LastName { 
         get {return lastName; } 
			
         set {
            if (lastName != value) { 
               lastName = value;
               RaisePropertyChanged("LastName");
               RaisePropertyChanged("FullName"); 
            } 
         } 
      }
		
      public string FullName { 
         get { 
            return firstName + " " + lastName; 
         } 
      }
		
      public event PropertyChangedEventHandler PropertyChanged;
		
      private void RaisePropertyChanged(string property) {
         if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
         } 
      } 
   } 
}

Step 4 - Добавьте еще один класс StudentViewModel в папку ViewModel и вставьте следующий код.

using MVVMDemo.Model; 
using System.Collections.ObjectModel;

namespace MVVMDemo.ViewModel { 

   public class StudentViewModel { 
	
      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }
		
      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();
				
         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" }); 
			
         Students = students; 
      } 
   } 
}

Step 5 - Добавьте новый пользовательский элемент управления (WPF), щелкнув правой кнопкой мыши папку «Представления» и выбрав «Добавить»> «Новый элемент…».

Step 6- Нажмите кнопку «Добавить». Теперь вы увидите файл XAML. Добавьте следующий код в файл StudentView.xaml, который содержит различные элементы пользовательского интерфейса.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
		
         <ItemsControl ItemsSource = "{Binding Path = Students}">
			
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
                  <StackPanel Orientation = "Horizontal">
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/>
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/>
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/>
								
                  </StackPanel> 
               </DataTemplate> 
            </ItemsControl.ItemTemplate> 
				
         </ItemsControl> 
			
      </StackPanel> 
   </Grid> 
	
</UserControl>

Step 7 - Теперь добавьте StudentView в файл MainPage.xaml, используя следующий код.

<Window x:Class = "MVVMDemo.MainWindow"
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
   xmlns:local = "clr-namespace:MVVMDemo"
   xmlns:views = "clr-namespace:MVVMDemo.Views"
   mc:Ignorable = "d"
   Title = "MainWindow" Height = "350" Width = "525">
	
   <Grid>
      <views:StudentView x:Name = "StudentViewControl" Loaded = "StudentViewControl_Loaded"/>
   </Grid>
	
</Window>

Step 8 - Вот реализация события Loaded в файле MainPage.xaml.cs, которое обновит представление из ViewModel.

using System.Windows;

namespace MVVMDemo {

   /// <summary>
      /// Interaction logic for MainWindow.xaml
   /// </summary>
	
   public partial class MainWindow : Window {
	
      public MainWindow() {
         InitializeComponent();
      }
		
      private void StudentViewControl_Loaded(object sender, RoutedEventArgs e) {
         MVVMDemo.ViewModel.StudentViewModel studentViewModelObject = 
            new MVVMDemo.ViewModel.StudentViewModel();
         studentViewModelObject.LoadStudents();
			
         StudentViewControl.DataContext = studentViewModelObject;
      }
   }
}

Step 9 - Когда приведенный выше код скомпилирован и выполнен, вы получите следующий вывод в главном окне.

Мы рекомендуем вам выполнить приведенный выше пример пошагово для лучшего понимания.

В этой главе мы рассмотрим различные способы, которыми вы можете подключить свои представления к ViewModel. Во-первых, давайте посмотрим на конструкцию View first, где мы можем объявить ее в XAML. Как мы видели в примере в предыдущей главе, где мы подключили представление из главного окна. Теперь мы увидим другие способы подключения просмотров.

Мы будем использовать тот же пример и в этой главе. Ниже приведена та же реализация класса Model.

using System.ComponentModel;

namespace MVVMDemo.Model {
 
   public class StudentModel {} 
	
   public class Student : INotifyPropertyChanged { 
      private string firstName; 
      private string lastName;
		
      public string FirstName { 
         get { return firstName; }
			
         set { 
            if (firstName != value) { 
               firstName = value; 
               RaisePropertyChanged("FirstName");
               RaisePropertyChanged("FullName"); 
            } 
         } 
      }
	
      public string LastName {
         get { return lastName; } 
			
         set { 
            if (lastName != value) { 
               lastName = value; 
               RaisePropertyChanged("LastName");
               RaisePropertyChanged("FullName"); 
            } 
         } 
      }
	
      public string FullName { 
         get { 
            return firstName + " " + lastName; 
         } 
      }
	
      public event PropertyChangedEventHandler PropertyChanged;
	
      private void RaisePropertyChanged(string property) { 
         if (PropertyChanged != null) { 
            PropertyChanged(this, new PropertyChangedEventArgs(property)); 
         } 
      } 
   }  
}

Вот реализация класса ViewModel. На этот раз метод LoadStudents вызывается в конструкторе по умолчанию.

using MVVMDemo.Model; 
using System.Collections.ObjectModel;

namespace MVVMDemo.ViewModel{ 

   public class StudentViewModel { 
	
      public StudentViewModel() { 
         LoadStudents(); 
      } 
		
      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }
		
      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();
			
         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" }); 
			
         Students = students; 
      } 
   } 
}

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

  • Просмотр первой конструкции в XAML
  • Посмотреть первую конструкцию в коде программной части

Просмотр первой конструкции в XAML

Один из способов - просто добавить ViewModel в качестве вложенного элемента в установщик для свойства DataContext, как показано в следующем коде.

<UserControl.DataContext> 
   <viewModel:StudentViewModel/> 
</UserControl.DataContext>

Вот полный файл View XAML.

<UserControl x:Class="MVVMDemo.Views.StudentView"
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
   xmlns:local = "clr-namespace:MVVMDemo.Views"
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
   mc:Ignorable = "d"
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <UserControl.DataContext>
      <viewModel:StudentViewModel/>
   </UserControl.DataContext>
	
   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}">
			
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal"> 
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/> 
								
                      <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/>
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate>
				
         </ItemsControl> 
      </StackPanel> 
   </Grid> 
	
</UserControl>

Посмотреть первую конструкцию в коде программной части

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

Обычно свойство DataContext устанавливается в методе конструктора представления, но вы также можете отложить построение до тех пор, пока не сработает событие Load представления.

using System.Windows.Controls;

namespace MVVMDemo.Views {
 
   /// <summary> 
      /// Interaction logic for StudentView.xaml 
   /// </summary> 
	
   public partial class StudentView : UserControl { 
      public StudentView() { 
         InitializeComponent(); 
         this.DataContext = new MVVMDemo.ViewModel.StudentViewModel(); 
      } 
   } 
}

Одна из причин построения модели представления в Code-behind вместо XAML заключается в том, что конструктор модели представления принимает параметры, но синтаксический анализ XAML может создавать элементы, только если они определены в конструкторе по умолчанию.

Теперь в этом случае XAML-файл представления будет выглядеть, как показано в следующем коде.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" 
   d:DesignWidth = "300">
	
   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}"> 
			
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal"<
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/> 
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/> 
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate>
				
         </ItemsControl> 
      </StackPanel> 
   </Grid>
	
</UserControl>

Вы можете объявить это представление в MainWindow, как показано в файле MainWindow.XAML.

<Window x:Class = "MVVMDemo.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:local = "clr-namespace:MVVMDemo" 
   xmlns:views = "clr-namespace:MVVMDemo.Views" 
   mc:Ignorable = "d" 
   Title = "MainWindow" Height = "350" Width = "525"> 
	
   <Grid> 
      <views:StudentView x:Name = "StudentViewControl"/> 
   </Grid> 
	
</Window>

Когда приведенный выше код скомпилирован и выполнен, вы увидите следующий вывод в главном окне.

Мы рекомендуем вам выполнить приведенный выше пример пошагово для лучшего понимания.

В этой главе мы расскажем, как подключить ViewModel. Это продолжение предыдущей главы, в которой мы обсуждали конструкцию View first. Теперь следующая форма первой конструкции - этоmeta-pattern который известен как ViewModelLocator. Это псевдошаблон, который накладывается поверх шаблона MVVM.

  • В MVVM каждый View должен быть подключен к его ViewModel.

  • ViewModelLocator - это простой подход для централизации кода и большего разделения представления.

  • Это означает, что ему не нужно явно знать о типе ViewModel и о том, как его построить.

  • Существует несколько различных подходов к использованию ViewModelLocator, но здесь мы используем наиболее похожий на тот, который является частью структуры PRISM.

ViewModelLocator предоставляет стандартный, согласованный, декларативный и слабо связанный способ построения представления сначала, который автоматизирует процесс подключения ViewModel к представлению. На следующем рисунке представлен высокоуровневый процесс ViewModelLocator.

Step 1 - Выясните, какой тип представления создается.

Step 2 - Определите ViewModel для этого конкретного типа View.

Step 3 - Постройте ту ViewModel.

Step 4 - Установите для представления DataContext значение ViewModel.

Чтобы понять основную концепцию, давайте посмотрим на простой пример ViewModelLocator, продолжив тот же пример из предыдущей главы. Если вы посмотрите на файл StudentView.xaml, вы увидите, что мы статически подключили ViewModel.

Теперь, как показано в следующей программе, закомментируйте этот код XAML, а также удалите код из кода программной части.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
	
   <!--<UserControl.DataContext> 
      <viewModel:StudentViewModel/> 
   </UserControl.DataContext>-->
	
   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}">
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal">
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/> 
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/> 
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate> 
         </ItemsControl> 
      </StackPanel> 
   </Grid>
	
</UserControl>

Теперь давайте создадим новую папку VML и добавим новый общедоступный класс ViewModelLocator, который будет содержать одно присоединенное свойство (свойство зависимости) AutoHookedUpViewModel, как показано в следующем коде.

public static bool GetAutoHookedUpViewModel(DependencyObject obj) { 
   return (bool)obj.GetValue(AutoHookedUpViewModelProperty); 
}

public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) { 
   obj.SetValue(AutoHookedUpViewModelProperty, value); 
}

// Using a DependencyProperty as the backing store for AutoHookedUpViewModel. 
//This enables animation, styling, binding, etc...
 
public static readonly DependencyProperty AutoHookedUpViewModelProperty =
   DependencyProperty.RegisterAttached("AutoHookedUpViewModel",
   typeof(bool), typeof(ViewModelLocator), new PropertyMetadata(false,
   AutoHookedUpViewModelChanged));

А теперь вы можете увидеть базовое определение свойства присоединения. Чтобы добавить поведение к свойству, нам нужно добавить измененный обработчик событий для этого свойства, который содержит автоматический процесс подключения ViewModel для View. Код для этого следующий -

private static void AutoHookedUpViewModelChanged(DependencyObject d, 
   DependencyPropertyChangedEventArgs e) { 
   if (DesignerProperties.GetIsInDesignMode(d)) return; 
   var viewType = d.GetType(); 
   string str = viewType.FullName; 
   str = str.Replace(".Views.", ".ViewModel."); 
	
   var viewTypeName = str; 
   var viewModelTypeName = viewTypeName + "Model"; 
   var viewModelType = Type.GetType(viewModelTypeName); 
   var viewModel = Activator.CreateInstance(viewModelType);
   ((FrameworkElement)d).DataContext = viewModel; 
}

Ниже приводится полная реализация класса ViewModelLocator.

using System; 
using System.ComponentModel; 
using System.Windows;

namespace MVVMDemo.VML { 

   public static class ViewModelLocator { 
	
      public static bool GetAutoHookedUpViewModel(DependencyObject obj) {
         return (bool)obj.GetValue(AutoHookedUpViewModelProperty); 
      }
		
      public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) { 
         obj.SetValue(AutoHookedUpViewModelProperty, value); 
      }
		
      // Using a DependencyProperty as the backing store for AutoHookedUpViewModel. 
		
      //This enables animation, styling, binding, etc...
      public static readonly DependencyProperty AutoHookedUpViewModelProperty =
         DependencyProperty.RegisterAttached("AutoHookedUpViewModel", 
         typeof(bool), typeof(ViewModelLocator), new
         PropertyMetadata(false, AutoHookedUpViewModelChanged));
		
      private static void AutoHookedUpViewModelChanged(DependencyObject d,
         DependencyPropertyChangedEventArgs e) { 
         if (DesignerProperties.GetIsInDesignMode(d)) return; 
         var viewType = d.GetType(); 
			
         string str = viewType.FullName; 
         str = str.Replace(".Views.", ".ViewModel."); 
			
         var viewTypeName = str; 
         var viewModelTypeName = viewTypeName + "Model";
         var viewModelType = Type.GetType(viewModelTypeName); 
         var viewModel = Activator.CreateInstance(viewModelType);
			
        ((FrameworkElement)d).DataContext = viewModel; 
      } 
   } 
}

Первое, что нужно сделать, - это добавить пространство имен, чтобы мы могли получить доступ к этому типу ViewModelLocator в корне нашего проекта. Затем в элементе маршрута, который является типом представления, добавьте свойство AutoHookedUpViewModel и установите для него значение true.

xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"

Вот полная реализация файла StudentView.xaml.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
   
   <!--<UserControl.DataContext> 
      <viewModel:StudentViewModel/> 
   </UserControl.DataContext>-->

   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}"> 
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal"> 
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/> 
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/>
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate> 
         </ItemsControl> 
      </StackPanel>
   </Grid> 
	
</UserControl>

Когда приведенный выше код скомпилирован и выполнен, вы увидите, что ViewModelLocator подключает ViewModel для этого конкретного View.

Ключевой момент, на который следует обратить внимание, это то, что представление больше не связано каким-либо образом с типом его ViewModel или тем, как оно создается. Все это перенесено в центральное место внутри ViewModelLocator.

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

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

  • Элементы пользовательского интерфейса в представлении привязаны к свойствам, которые предоставляет ViewModel.

  • Порядок построения View и ViewModel зависит от ситуации, так как мы сначала рассмотрели View.

  • Создается представление и ViewModel, и DataContext представления устанавливается в ViewModel.

  • Привязки могут быть привязками данных OneWay или TwoWay для обмена данными между View и ViewModel.

Давайте посмотрим на привязки данных в том же примере. Ниже представлен код XAML для StudentView.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">

   <!--<UserControl.DataContext> 
      <viewModel:StudentViewModel/> 
   </UserControl.DataContext>--> 

   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}"> 
            <ItemsControl.ItemTemplate>
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal"> 
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/>
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/> 
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate> 
         </ItemsControl> 
      </StackPanel> 
   </Grid> 

</UserControl>
  • Если вы посмотрите на приведенный выше код XAML, вы увидите, что ItemsControl привязан к коллекции студентов, предоставляемой ViewModel.

  • Вы также можете видеть, что свойство модели Student также имеет свои собственные индивидуальные привязки, и они привязаны к Textboxes и TextBlock.

  • ItemSource элемента ItemsControl может быть привязан к свойству Student, поскольку для общего контекста данных для представления установлено значение ViewModel.

  • Отдельные привязки свойств здесь также являются привязками DataContext, но они не привязаны к самой ViewModel из-за того, как работает ItemSource.

  • Когда источник элемента связывается со своей коллекцией, он визуализирует контейнер для каждого элемента при визуализации и устанавливает DataContext этого контейнера для элемента. Таким образом, общий DataContext для каждого текстового поля и текстового блока в строке будет отдельным Student в коллекции. И вы также можете видеть, что эти привязки для TextBoxes являются привязкой данных TwoWay, а для TextBlock - привязкой данных OneWay, поскольку вы не можете редактировать TextBlock.

Когда вы снова запустите это приложение, вы увидите следующий результат.

Давайте теперь изменим текст во втором текстовом поле первой строки с Allain на Upston и нажмем вкладку, чтобы потерять фокус. Вы увидите, что текст TextBlock также обновлен.

Это связано с тем, что привязки TextBoxes установлены на TwoWay, и он также обновляет модель, а из модели снова обновляется TextBlock.

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

В MVVM есть еще одна основная форма, известная как первая конструкция ViewModel.

  • Первый подход к построению ViewModel использует возможности неявных шаблонов данных в WPF.

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

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

using MVVMDemo.Model; 
using System.Collections.ObjectModel;

namespace MVVMDemo.ViewModel { 

   public class StudentViewModel {
	
      public StudentViewModel() { 
         LoadStudents(); 
      } 
		
      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }
		
      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();
			
         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" }); 
			
         Students = students; 
      } 
   } 
}

Вы можете видеть, что приведенная выше ViewModel не изменилась. Мы продолжим с того же примера из предыдущей главы. Этот класс ViewModel просто предоставляет свойство коллекции Student и заполняет его при создании. Перейдем к файлу StudentView.xaml, удалим существующую реализацию и определим шаблон данных в разделе Ресурсы.

<UserControl.Resources> 
   <DataTemplate x:Key = "studentsTemplate">
	
      <StackPanel Orientation = "Horizontal"> 
         <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
            Width = "100" Margin = "3 5 3 5"/> 
				
         <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
            Width = "100" Margin = "0 5 3 5"/> 
				
         <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
            Margin = "0 5 3 5"/> 
      </StackPanel> 
		
   </DataTemplate> 
</UserControl.Resources>

Теперь добавьте поле со списком и привяжите данные к этому списку со свойством «Студенты», как показано в следующем коде.

<ListBox ItemsSource = "{Binding Students}" ItemTemplate = "{StaticResource studentsTemplate}"/>

В разделе «Ресурсы» DataTemplate имеет ключ studentTemplate, а затем, чтобы фактически использовать этот шаблон, нам нужно использовать свойство ItemTemplate ListBox. Итак, теперь вы можете видеть, что мы проинструктировали список использовать этот конкретный шаблон для рендеринга этих студентов. Ниже приводится полная реализация файла StudentView.xaml.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
	
   <UserControl.Resources> 
      <DataTemplate x:Key = "studentsTemplate"> 
		
         <StackPanel Orientation = "Horizontal"> 
            <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
               Width = "100" Margin = "3 5 3 5"/> 
					
            <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
               Width = "100" Margin = "0 5 3 5"/> 
					
            <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
               Margin = "0 5 3 5"/> 
					
         </StackPanel> 
			
      </DataTemplate> 
   </UserControl.Resources>
	
   <Grid> 
      <ListBox 
         ItemsSource = "{Binding Students}" 
         ItemTemplate = "{StaticResource studentsTemplate}"/> 
   </Grid>
	
</UserControl>

Когда приведенный выше код скомпилирован и выполнен, вы увидите следующее окно, которое содержит один ListBox. Каждый ListBoxItem содержит данные объекта класса Student, которые отображаются в текстовых блоках и текстовых полях.

Чтобы сделать этот шаблон неявным, нам нужно удалить свойство ItemTemplate из списка и добавить свойство DataType в определение нашего шаблона, как показано в следующем коде.

<UserControl.Resources> 
   <DataTemplate DataType = "{x:Type data:Student}">
	
      <StackPanel Orientation = "Horizontal"> 
         <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
            Width = "100" Margin = "3 5 3 5"/> 
				
         <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
            Width = "100" Margin = "0 5 3 5"/> 
				
         <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
            Margin = "0 5 3 5"/> 
				
      </StackPanel> 
		
   </DataTemplate> 
</UserControl.Resources>
 
<Grid> 
   <ListBox ItemsSource = "{Binding Students}"/> 
</Grid>

В DataTemplate очень важно расширение разметки x: Type, которое похоже на тип оператора в XAML. Итак, в основном нам нужно указать на тип данных Student, который находится в пространстве имен MVVMDemo.Model. Ниже приведен обновленный полный файл XAML.

<UserControl x:Class="MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:data = "clr-namespace:MVVMDemo.Model" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
	
   <UserControl.Resources> 
      <DataTemplate DataType = "{x:Type data:Student}"> 
		
         <StackPanel Orientation = "Horizontal"> 
            <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
               Width = "100" Margin = "3 5 3 5"/> 
					
            <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
               Width = "100" Margin = "0 5 3 5"/> 
					
            <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
               Margin = "0 5 3 5"/> 
					
         </StackPanel> 
			
      </DataTemplate> 
   </UserControl.Resources>
	
   <Grid>
      <ListBox ItemsSource = "{Binding Students}"/> 
   </Grid> 
	
</UserControl>

Когда вы снова запустите это приложение, вы все равно получите тот же рендеринг учеников с шаблоном данных, потому что он автоматически сопоставляет тип визуализируемого объекта путем поиска соответствующего DataTemplate.

Мы рекомендуем вам выполнить приведенный выше пример пошаговым методом для лучшего понимания.

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

Связь View / ViewModel с помощью команд

Командный шаблон хорошо документирован и в течение нескольких десятилетий часто использует шаблон проектирования. В этом шаблоне есть два основных участника: вызывающий и получатель.

Invoker

  • Вызывающий объект - это фрагмент кода, который может выполнять некоторую императивную логику.

  • Обычно это элемент пользовательского интерфейса, с которым взаимодействует пользователь в контексте инфраструктуры пользовательского интерфейса.

  • Это может быть просто еще один кусок логического кода где-то еще в приложении.

Приемник

  • Получатель - это логика, которая предназначена для выполнения при срабатывании вызывающей стороны.

  • В контексте MVVM получателем обычно является метод в вашей модели представления, который необходимо вызвать.

Между этими двумя у вас есть уровень препятствий, который подразумевает, что вызывающий и получатель не должны явно знать друг о друге. Обычно это представляется как абстракция интерфейса, доступная вызывающей стороне, и конкретная реализация этого интерфейса способна вызывать получатель.

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

В файле StudentView.xaml у нас есть ListBox, который подключает данные учащихся из ViewModel. Теперь добавим кнопку для удаления ученика из ListBox.

Важно то, что работать с командами на кнопке очень просто, потому что у них есть свойство command для подключения к ICommand.

Итак, мы можем предоставить свойство в нашей ViewModel, которое имеет ICommand и привязывается к нему из свойства команды кнопки, как показано в следующем коде.

<Button Content = "Delete" 
   Command = "{Binding DeleteCommand}" 
   HorizontalAlignment = "Left" 
   VerticalAlignment = "Top" 
   Width = "75" />

Давайте добавим в ваш проект новый класс, который будет реализовывать интерфейс ICommand. Ниже приведена реализация интерфейса ICommand.

using System; 
using System.Windows.Input;

namespace MVVMDemo { 

   public class MyICommand : ICommand { 
      Action _TargetExecuteMethod; 
      Func<bool> _TargetCanExecuteMethod;
		
      public MyICommand(Action executeMethod) {
         _TargetExecuteMethod = executeMethod; 
      }
		
      public MyICommand(Action executeMethod, Func<bool> canExecuteMethod){ 
         _TargetExecuteMethod = executeMethod;
         _TargetCanExecuteMethod = canExecuteMethod; 
      }
		
      public void RaiseCanExecuteChanged() { 
         CanExecuteChanged(this, EventArgs.Empty); 
      }
		
      bool ICommand.CanExecute(object parameter) { 
		
         if (_TargetCanExecuteMethod != null) { 
            return _TargetCanExecuteMethod(); 
         } 
			
         if (_TargetExecuteMethod != null) { 
            return true; 
         } 
			
         return false; 
      }
		
      // Beware - should use weak references if command instance lifetime 
         is longer than lifetime of UI objects that get hooked up to command 
			
      // Prism commands solve this in their implementation 
      public event EventHandler CanExecuteChanged = delegate { };
		
      void ICommand.Execute(object parameter) { 
         if (_TargetExecuteMethod != null) {
            _TargetExecuteMethod(); 
         } 
      } 
   } 
}

Как видите, это простая реализация делегирования ICommand, в которой у нас есть два делегата: один для executeMethod, а второй - для canExecuteMethod, которые можно передать при построении.

В приведенной выше реализации есть два перегруженных конструктора: один только для executeMethod, а второй - для executeMethod, а я canExecuteMethod.

Добавим свойство типа MyICommand в класс StudentView Model. Теперь нам нужно создать экземпляр в StudentViewModel. Мы будем использовать перегруженный конструктор MyICommand, который принимает два параметра.

public MyICommand DeleteCommand { get; set;} 

public StudentViewModel() { 
   LoadStudents(); 
   DeleteCommand = new MyICommand(OnDelete, CanDelete); 
}

Теперь добавим реализацию методов OnDelete и CanDelete.

private void OnDelete() { 
   Students.Remove(SelectedStudent); 
}

private bool CanDelete() { 
   return SelectedStudent != null; 
}

Нам также необходимо добавить новый SelectedStudent, чтобы пользователь мог удалить выбранный элемент из ListBox.

private Student _selectedStudent;
 
public Student SelectedStudent { 
   get { 
      return _selectedStudent; 
   } 
	
   set { 
      _selectedStudent = value;
      DeleteCommand.RaiseCanExecuteChanged(); 
   } 
}

Ниже приводится полная реализация класса ViewModel.

using MVVMDemo.Model; 

using System.Collections.ObjectModel; 
using System.Windows.Input; 
using System;

namespace MVVMDemo.ViewModel { 

   public class StudentViewModel { 
	
      public MyICommand DeleteCommand { get; set;} 
		
      public StudentViewModel() { 
         LoadStudents(); 
         DeleteCommand = new MyICommand(OnDelete, CanDelete); 
      }
		
      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }
		
      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();
			
         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" }); 
			
         Students = students; 
      }
		
      private Student _selectedStudent; 
		
      public Student SelectedStudent { 
         get {
            return _selectedStudent; 
         } 
			
         set { 
            _selectedStudent = value;
            DeleteCommand.RaiseCanExecuteChanged(); 
         } 
      }
		
      private void OnDelete() { 
         Students.Remove(SelectedStudent); 
      }
		
      private bool CanDelete() { 
         return SelectedStudent != null; 
      }
   } 
}

В StudentView.xaml нам нужно добавить свойство SelectedItem в ListBox, которое будет привязано к свойству SelectStudent.

<ListBox ItemsSource = "{Binding Students}" SelectedItem = "{Binding SelectedStudent}"/>

Ниже приведен полный файл xaml.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:data = "clr-namespace:MVVMDemo.Model" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d"
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <UserControl.Resources> 
      <DataTemplate DataType = "{x:Type data:Student}"> 
		
         <StackPanel Orientation = "Horizontal"> 
			
            <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
               Width = "100" Margin = "3 5 3 5"/> 
					
            <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
               Width = "100" Margin = "0 5 3 5"/> 
					
            <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
               Margin = "0 5 3 5"/> 
					
         </StackPanel> 
			
      </DataTemplate> 
   </UserControl.Resources>
	
   <Grid> 
      <StackPanel Orientation = "Horizontal"> 
         <ListBox ItemsSource = "{Binding Students}" 
            SelectedItem = "{Binding SelectedStudent}"/> 
				
         <Button Content = "Delete" 
            Command = "{Binding DeleteCommand}"
            HorizontalAlignment = "Left" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
      </StackPanel> 
   </Grid>
	
</UserControl>

Когда приведенный выше код скомпилирован и запущен, вы увидите следующее окно.

Вы можете видеть, что кнопка удаления отключена. Он будет включен при выборе любого пункта.

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

Мы рекомендуем вам выполнить приведенный выше пример пошагово для лучшего понимания.

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

  • После декомпозиции сложных представлений это не означает, что каждый фрагмент дочернего содержимого, который вы разделяете в свой собственный файл XAML, обязательно должен быть представлением MVVM.

  • Фрагмент контента просто предоставляет структуру для отображения чего-либо на экране и не поддерживает какой-либо ввод или манипуляции со стороны пользователя для этого контента.

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

  • Наконец, если у вас есть иерархия представлений и ViewModel, родительская ViewModel может стать центром взаимодействия, так что каждая дочерняя ViewModel может оставаться отделенной от других дочерних ViewModel и от их родительских моделей в максимально возможной степени.

Давайте посмотрим на пример, в котором мы определим простую иерархию между различными представлениями. Создайте новый проект приложения WPFMVVMHierarchiesDemo

Step 1 - Добавьте три папки (Model, ViewModel и Views) в свой проект.

Step 2 - Добавьте классы Customer и Order в папку Model, CustomerListView и OrderView в папку Views, а также CustomerListViewModel и OrderViewModel в папку ViewModel, как показано на следующем изображении.

Step 3- Добавьте текстовые блоки как в CustomerListView, так и в OrderView. Вот файл CustomerListView.xaml.

<UserControl x:Class="MVVMHierarchiesDemo.Views.CustomerListView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <Grid> 
      <TextBlock Text = "Customer List View"/> 
   </Grid> 
	
</UserControl>

Ниже приведен файл OrderView.xaml.

<UserControl x:Class = "MVVMHierarchiesDemo.Views.OrderView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc ="http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d ="http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views" mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <Grid> 
      <TextBlock Text = "Order View"/> 
   </Grid> 
	
</UserControl>

Теперь нам нужно что-то для размещения этих представлений, и хорошее место для этого в нашем MainWindow, потому что это простое приложение. Нам нужен контейнерный элемент управления, в котором мы можем размещать наши представления и переключать их в режиме навигации. Для этого нам нужно добавить ContentControl в наш файл MainWindow.xaml, и мы будем использовать его свойство content и привязать его к ссылке ViewModel.

Теперь определите шаблоны данных для каждого представления в словаре ресурсов. Ниже приведен файл MainWindow.xaml. Обратите внимание, как каждый шаблон данных сопоставляет тип данных (тип ViewModel) с соответствующим представлением.

<Window x:Class = "MVVMHierarchiesDemo.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo" 
   xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views" 
   xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel" 
   mc:Ignorable = "d" 
   Title = "MainWindow" Height = "350" Width = "525"> 
   
   <Window.DataContext> 
      <local:MainWindowViewModel/> 
   </Window.DataContext>
	
   <Window.Resources> 
      <DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
         <views:CustomerListView/> 
      </DataTemplate>
		
      <DataTemplate DataType = "{x:Type viewModels:OrderViewModel}"> 
         <views:OrderView/> 
      </DataTemplate> 
   </Window.Resources>
	
   <Grid> 
      <ContentControl Content = "{Binding CurrentView}"/> 
   </Grid> 
	
</Window>

Каждый раз, когда текущая модель представления установлена ​​на экземпляр CustomerListViewModel, она будет отображать CustomerListView с подключенной ViewModel. Это модель ViewModel порядка, она будет отображать OrderView и так далее.

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

Давайте создадим ViewModel для этого MainWindow под названием MainWindowViewModel. Мы можем просто создать экземпляр нашей ViewModel из XAML и использовать его для установки свойства DataContext окна. Для этого нам нужно создать базовый класс, чтобы инкапсулировать реализацию INotifyPropertyChanged для наших ViewModels.

Основная идея этого класса - инкапсулировать реализацию INotifyPropertyChanged и предоставить вспомогательные методы производному классу, чтобы они могли легко запускать соответствующие уведомления. Ниже приведена реализация класса BindableBase.

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Linq; 
using System.Runtime.CompilerServices; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo { 

   class BindableBase : INotifyPropertyChanged { 
	
      protected virtual void SetProperty<T>(ref T member, T val,
         [CallerMemberName] string propertyName = null) { 
            if (object.Equals(member, val)) return;
				
            member = val;
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
      }
			
      protected virtual void OnPropertyChanged(string propertyName) { 
         PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
      } 
		
      public event PropertyChangedEventHandler PropertyChanged = delegate { }; 
   } 
}

Теперь пора начать переключаться между видами с помощью нашего свойства CurrentViewModel. Нам просто нужен способ управлять настройкой этого свойства. И мы собираемся сделать так, чтобы конечный пользователь мог по команде перейти к списку клиентов или к просмотру заказов. Сначала добавьте в свой проект новый класс, который будет реализовывать интерфейс ICommand. Ниже приведена реализация интерфейса ICommand.

using System; 
using System.Windows.Input;

namespace MVVMHierarchiesDemo { 

   public class MyICommand<T> : ICommand { 
	
      Action<T> _TargetExecuteMethod; 
      Func<T, bool> _TargetCanExecuteMethod;
		
      public MyICommand(Action<T> executeMethod) {
         _TargetExecuteMethod = executeMethod; 
      }
		
      public MyICommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod) {
         _TargetExecuteMethod = executeMethod;
         _TargetCanExecuteMethod = canExecuteMethod; 
      }

      public void RaiseCanExecuteChanged() {
         CanExecuteChanged(this, EventArgs.Empty); 
      } 
		
      #region ICommand Members

      bool ICommand.CanExecute(object parameter) { 
		
         if (_TargetCanExecuteMethod != null) { 
            T tparm = (T)parameter; 
            return _TargetCanExecuteMethod(tparm); 
         } 
			
         if (_TargetExecuteMethod != null) { 
            return true; 
         } 
			
         return false; 
      }
		
      // Beware - should use weak references if command instance lifetime is
         longer than lifetime of UI objects that get hooked up to command 
			
      // Prism commands solve this in their implementation 

      public event EventHandler CanExecuteChanged = delegate { };
	
      void ICommand.Execute(object parameter) { 
         if (_TargetExecuteMethod != null) {
            _TargetExecuteMethod((T)parameter); 
         } 
      } 
		
      #endregion 
   } 
}

Теперь нам нужно настроить навигацию верхнего уровня для этих моделей ViewModel, и логика для этого переключения должна принадлежать MainWindowViewModel. Для этого мы собираемся использовать метод, называемый navigate, который принимает строковый адресат и возвращает свойство CurrentViewModel.

private void OnNav(string destination) {
 
   switch (destination) { 
      case "orders": 
         CurrentViewModel = orderViewModelModel; 
      break; 
      case "customers": 
      default: 
         CurrentViewModel = custListViewModel; 
      break; 
   } 
}

Для навигации по этим различным представлениям нам нужно добавить две кнопки в наш файл MainWindow.xaml. Ниже приводится полная реализация файла XAML.

<Window x:Class = "MVVMHierarchiesDemo.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo" 
   xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views" 
   xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel" 
   mc:Ignorable = "d" 
   Title = "MainWindow" Height = "350" Width = "525">

   <Window.DataContext> 
      <local:MainWindowViewModel/> 
   </Window.DataContext>
	
   <Window.Resources> 
      <DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
         <views:CustomerListView/> 
      </DataTemplate> 
		
      <DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
         <views:OrderView/> 
      </DataTemplate> 
   </Window.Resources>
	
   <Grid>
      <Grid.RowDefinitions> 
         <RowDefinition Height = "Auto" /> 
         <RowDefinition Height = "*" /> 
      </Grid.RowDefinitions> 
	
      <Grid x:Name = "NavBar"> 
         <Grid.ColumnDefinitions> 
            <ColumnDefinition Width = "*" /> 
            <ColumnDefinition Width = "*" /> 
            <ColumnDefinition Width = "*" /> 
         </Grid.ColumnDefinitions> 
	
         <Button Content = "Customers" 
            Command = "{Binding NavCommand}"
            CommandParameter = "customers" 
            Grid.Column = "0" />
				
         <Button Content = "Order" 
            Command = "{Binding NavCommand}" 
            CommandParameter = "orders" 
            Grid.Column = "2" />
      </Grid> 
	
      <Grid x:Name = "MainContent" Grid.Row = "1"> 
         <ContentControl Content = "{Binding CurrentViewModel}" /> 
      </Grid> 
		
   </Grid> 
	
</Window>

Ниже приводится полная реализация MainWindowViewModel.

using MVVMHierarchiesDemo.ViewModel; 
using MVVMHierarchiesDemo.Views; 

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo {
 
   class MainWindowViewModel : BindableBase {
	
      public MainWindowViewModel() { 
         NavCommand = new MyICommand<string>(OnNav); 
      } 
		
      private CustomerListViewModel custListViewModel = new CustomerListViewModel(); 
		
      private OrderViewModel orderViewModelModel = new OrderViewModel();
		
      private BindableBase _CurrentViewModel; 
		
      public BindableBase CurrentViewModel { 
         get {return _CurrentViewModel;} 
         set {SetProperty(ref _CurrentViewModel, value);} 
      }
		
      public MyICommand<string> NavCommand { get; private set; }

      private void OnNav(string destination) {
		
         switch (destination) { 
            case "orders": 
               CurrentViewModel = orderViewModelModel; 
               break; 
            case "customers": 
            default: 
               CurrentViewModel = custListViewModel; 
               break; 
         } 
      } 
   } 
}

Получите все свои модели представления из класса BindableBase. Когда приведенный выше код скомпилирован и выполнен, вы увидите следующий результат.

Как видите, мы добавили только две кнопки и CurrentViewModel в наш MainWindow. Если вы нажмете любую кнопку, он перейдет к этому конкретному представлению. Нажмите кнопку «Клиенты», и вы увидите, что отображается CustomerListView.

Мы рекомендуем вам выполнить приведенный выше пример пошагово для лучшего понимания.

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

Проверка в MVVM

  • Когда ваше приложение начинает принимать ввод данных от конечных пользователей, вам необходимо рассмотреть возможность проверки этих вводимых данных.

  • Убедитесь, что он соответствует вашим общим требованиям.

  • WPF имеет несколько отличных сборок и функций в системе привязки для проверки ввода, и вы по-прежнему можете использовать все эти функции при выполнении MVVM.

  • Имейте в виду, что логика, которая поддерживает вашу проверку и определяет, какие правила существуют для того, какие свойства должны быть частью Модели или ViewModel, а не самого View.

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

  • Установлено создание исключений для свойства.
  • Реализация интерфейса IDataErrorInfo.
  • Реализация INotifyDataErrorInfo.
  • Используйте правила проверки WPF.

В общем, рекомендуется использовать INotifyDataErrorInfo, который был представлен в WPF .net 4.5, и он поддерживает запрос объекта на наличие ошибок, связанных со свойствами, а также устраняет несколько недостатков со всеми другими параметрами. В частности, он позволяет выполнять асинхронную проверку. Это позволяет свойствам иметь более одной связанной с ними ошибки.

Добавление проверки

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

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

Базовый класс будет поддерживать INotifyDataErrorInfo, чтобы эта проверка запускалась при изменении свойств.

Создайте и добавьте новый класс с именем ValidatableBindableBase. Поскольку у нас уже есть базовый класс для обработки изменения свойства, давайте выведем из него базовый класс, а также реализуем интерфейс INotifyDataErrorInfo.

Ниже приведена реализация класса ValidatableBindableBase.

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 

//using System.ComponentModel.DataAnnotations; 
using System.Linq; 
using System.Runtime.CompilerServices; 
using System.Text;
using System.Threading.Tasks; 
using System.Windows.Controls;

namespace MVVMHierarchiesDemo { 

   public class ValidatableBindableBase : BindableBase, INotifyDataErrorInfo { 
      private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();

      public event EventHandler<DataErrorsChangedEventArgs> 
         ErrorsChanged = delegate { };

      public System.Collections.IEnumerable GetErrors(string propertyName) {
		
         if (_errors.ContainsKey(propertyName)) 
            return _errors[propertyName]; 
         else 
            return null; 
      }
      
      public bool HasErrors { 
         get { return _errors.Count > 0; } 
      }
		
      protected override void SetProperty<T>(ref T member, T val, 
         [CallerMemberName] string propertyName = null) {
		
         base.SetProperty<T>(ref member, val, propertyName);
         ValidateProperty(propertyName, val);
      }
		
      private void ValidateProperty<T>(string propertyName, T value) {
         var results = new List<ValidationResult>();
			
         //ValidationContext context = new ValidationContext(this); 
         //context.MemberName = propertyName;
         //Validator.TryValidateProperty(value, context, results);

         if (results.Any()) {
            //_errors[propertyName] = results.Select(c => c.ErrorMessage).ToList(); 
         } else { 
            _errors.Remove(propertyName); 
         }
			
         ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); 
      } 
   } 
}

Теперь добавьте AddEditCustomerView и AddEditCustomerViewModel в соответствующие папки. Ниже приведен код AddEditCustomerView.xaml.

<UserControl x:Class = "MVVMHierarchiesDemo.Views.AddEditCustomerView"
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <Grid> 
      <Grid.RowDefinitions> 
         <RowDefinition Height = "Auto" /> 
         <RowDefinition Height = "Auto" />
      </Grid.RowDefinitions>
		
      <Grid x:Name = "grid1" 
         HorizontalAlignment = "Left" 
         DataContext = "{Binding Customer}" 
         Margin = "10,10,0,0" 
         VerticalAlignment = "Top">
			
         <Grid.ColumnDefinitions> 
            <ColumnDefinition Width = "Auto" /> 
            <ColumnDefinition Width = "Auto" /> 
         </Grid.ColumnDefinitions>
		
         <Grid.RowDefinitions> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
         </Grid.RowDefinitions>
		
         <Label Content = "First Name:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "0" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "firstNameTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "0" 
            Text = "{Binding FirstName, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Last Name:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "1" 
            VerticalAlignment = "Center" /> 
			
         <TextBox x:Name = "lastNameTextBox"
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "1" 
            Text = "{Binding LastName, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Email:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "2" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "emailTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "2" 
            Text = "{Binding Email, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Phone:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "3" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "phoneTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "3" 
            Text = "{Binding Phone, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
      </Grid> 

      <Grid Grid.Row = "1"> 
         <Button Content = "Save" 
            Command = "{Binding SaveCommand}" 
            HorizontalAlignment = "Left" 
            Margin = "25,5,0,0" 
            VerticalAlignment = "Top" 
            Width = "75" />
		
         <Button Content = "Add" 
            Command = "{Binding SaveCommand}" 
            HorizontalAlignment = "Left" 
            Margin = "25,5,0,0" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
		
         <Button Content = "Cancel" 
            Command = "{Binding CancelCommand}" 
            HorizontalAlignment = "Left" 
            Margin = "150,5,0,0" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
      </Grid>
		
   </Grid> 
	
</UserControl>

Ниже приводится реализация AddEditCustomerViewModel.

using MVVMHierarchiesDemo.Model;

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.ViewModel { 

   class AddEditCustomerViewModel : BindableBase { 
	
      public AddEditCustomerViewModel() {
         CancelCommand = new MyIcommand(OnCancel); 
         SaveCommand = new MyIcommand(OnSave, CanSave); 
      } 
		
      private bool _EditMode; 
		
      public bool EditMode { 
         get { return _EditMode; } 
         set { SetProperty(ref _EditMode, value);} 
      }
		
      private SimpleEditableCustomer _Customer;
		
      public SimpleEditableCustomer Customer { 
         get { return _Customer; } 
         set { SetProperty(ref _Customer, value);} 
      }

      private Customer _editingCustomer = null;
		
      public void SetCustomer(Customer cust) {
         _editingCustomer = cust; 
			
         if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged; 
         Customer = new SimpleEditableCustomer();
         Customer.ErrorsChanged += RaiseCanExecuteChanged;
         CopyCustomer(cust, Customer); 
      }
		
      private void RaiseCanExecuteChanged(object sender, EventArgs e) { 
         SaveCommand.RaiseCanExecuteChanged(); 
      }

      public MyIcommand CancelCommand { get; private set; }
      public MyIcommand SaveCommand { get; private set; }

      public event Action Done = delegate { };
		
      private void OnCancel() { 
         Done(); 
      }

      private async void OnSave() { 
         Done(); 
      }
		
      private bool CanSave() { 
         return !Customer.HasErrors; 
      }  
   } 
}

Ниже представлена ​​реализация класса SimpleEditableCustomer.

using System;
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.Model { 

   public class SimpleEditableCustomer : ValidatableBindableBase { 
      private Guid _id; 
		
      public Guid Id { 
         get { return _id; } 
         set { SetProperty(ref _id, value); } 
      }
		
      private string _firstName; 
      [Required]
		
      public string FirstName { 
         get { return _firstName; } 
         set { SetProperty(ref _firstName, value); } 
      }
		
      private string _lastName; 
      [Required] 
		
      public string LastName {  
         get { return _lastName; } 
         set { SetProperty(ref _lastName, value); } 
      }
		
      private string _email; 
      [EmailAddress] 
		
      public string Email {
         get { return _email; } 
         set { SetProperty(ref _email, value); } 
      }
		
      private string _phone; 
      [Phone] 
		
      public string Phone { 
         get { return _phone; } 
         set { SetProperty(ref _phone, value); } 
      } 
   } 
}

Когда приведенный выше код скомпилирован и запущен, вы увидите следующее окно.

Когда вы нажмете кнопку «Добавить клиента», вы увидите следующее представление. Когда пользователь оставляет какое-либо поле пустым, оно выделяется, а кнопка сохранения становится недоступной.

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

Теперь нам нужно нечто подобное, чтобы отделить нашу ViewModel от клиентских сервисов.

На заре объектно-ориентированного программирования разработчики столкнулись с проблемой создания и извлечения экземпляров классов в приложениях. Были предложены различные решения этой проблемы.

За последние несколько лет внедрение зависимостей и инверсия управления (IoC) приобрели популярность среди разработчиков и получили приоритет над некоторыми более старыми решениями, такими как шаблон Singleton.

Внедрение зависимостей / Контейнеры IoC

IoC и внедрение зависимостей - это два тесно связанных шаблона проектирования, а контейнер - это, по сути, фрагмент кода инфраструктуры, который выполняет оба этих шаблона за вас.

  • Шаблон IoC заключается в делегировании ответственности за построение, а шаблон внедрения зависимостей - в предоставлении зависимостей для уже созданного объекта.

  • Их можно рассматривать как двухэтапный подход к строительству. Когда вы используете контейнер, он берет на себя несколько обязанностей, а именно:

    • По запросу он создает объект.
    • Контейнер будет определять, от чего зависит этот объект.
    • Построение этих зависимостей.
    • Внедрение их в строящийся объект.
    • Рекурсивно выполняющийся процесс.

Давайте посмотрим, как мы можем использовать внедрение зависимостей, чтобы нарушить разделение между ViewModels и клиентскими службами. Мы подключим форму AddEditCustomerViewModel для обработки сохранения, используя связанную с этим инъекцию зависимостей.

Сначала нам нужно создать новый интерфейс в нашем проекте в папке Services. Если у вас нет папки служб в вашем проекте, сначала создайте ее и добавьте следующий интерфейс в папку служб.

using MVVMHierarchiesDemo.Model; 

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.Services { 

   public interface ICustomersRepository { 
      Task<List<Customer>> GetCustomersAsync(); 
      Task<Customer> GetCustomerAsync(Guid id); 
      Task<Customer> AddCustomerAsync(Customer customer); 
      Task<Customer> UpdateCustomerAsync(Customer customer); 
      Task DeleteCustomerAsync(Guid customerId); 
   } 
}

Ниже представлена ​​реализация ICustomersRepository.

using MVVMHierarchiesDemo.Model; 

using System; 
using System.Collections.Generic; 
using System.Linq; using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.Services { 

   public class CustomersRepository : ICustomersRepository {
      ZzaDbContext _context = new ZzaDbContext();

      public Task<List<Customer>> GetCustomersAsync() { 
         return _context.Customers.ToListAsync(); 
      }

      public Task<Customer> GetCustomerAsync(Guid id) { 
         return _context.Customers.FirstOrDefaultAsync(c => c.Id == id); 
      }
		
      public async Task<Customer> AddCustomerAsync(Customer customer){ 
         _context.Customers.Add(customer); 
         await _context.SaveChangesAsync(); 
         return customer;
      }

      public async Task<Customer> UpdateCustomerAsync(Customer customer) {
		
         if (!_context.Customers.Local.Any(c => c.Id == customer.Id)) { 
            _context.Customers.Attach(customer); 
         } 
			
         _context.Entry(customer).State = EntityState.Modified;
         await _context.SaveChangesAsync(); 
         return customer;
			
      }

      public async Task DeleteCustomerAsync(Guid customerId) {
         var customer = _context.Customers.FirstOrDefault(c => c.Id == customerId); 
			
         if (customer != null) {
            _context.Customers.Remove(customer); 
         }
			
         await _context.SaveChangesAsync(); 
      } 
   } 
}

Самый простой способ выполнить обработку сохранения - добавить новый экземпляр ICustomersRepository в AddEditCustomerViewModel и перегрузить конструкторы AddEditCustomerViewModel и CustomerListViewModel.

private ICustomersRepository _repo; 

public AddEditCustomerViewModel(ICustomersRepository repo) { 
   _repo = repo; 
   CancelCommand = new MyIcommand(OnCancel);
   SaveCommand = new MyIcommand(OnSave, CanSave); 
}

Обновите метод OnSave, как показано в следующем коде.

private async void OnSave() { 
   UpdateCustomer(Customer, _editingCustomer); 
	
   if (EditMode) 
      await _repo.UpdateCustomerAsync(_editingCustomer); 
   else 
      await _repo.AddCustomerAsync(_editingCustomer); 
   Done(); 
} 

private void UpdateCustomer(SimpleEditableCustomer source, Customer target) { 
   target.FirstName = source.FirstName; 
   target.LastName = source.LastName; 
   target.Phone = source.Phone; 
   target.Email = source.Email; 
}

Ниже приводится полная модель AddEditCustomerViewModel.

using MVVMHierarchiesDemo.Model; 
using MVVMHierarchiesDemo.Services; 

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text;
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo.ViewModel { 

   class AddEditCustomerViewModel : BindableBase { 
      private ICustomersRepository _repo; 
		
      public AddEditCustomerViewModel(ICustomersRepository repo) { 
         _repo = repo;
         CancelCommand = new MyIcommand(OnCancel); 
         SaveCommand = new MyIcommand(OnSave, CanSave); 
      } 
		
      private bool _EditMode; 
		
      public bool EditMode { 
         get { return _EditMode; } 
         set { SetProperty(ref _EditMode, value); } 
      }

      private SimpleEditableCustomer _Customer; 
		
      public SimpleEditableCustomer Customer { 
         get { return _Customer; } 
         set { SetProperty(ref _Customer, value); } 
      }
		
      private Customer _editingCustomer = null;

      public void SetCustomer(Customer cust) { 
         _editingCustomer = cust; 
			
         if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged; 
         Customer = new SimpleEditableCustomer();
         Customer.ErrorsChanged += RaiseCanExecuteChanged;
         CopyCustomer(cust, Customer); 
      }

      private void RaiseCanExecuteChanged(object sender, EventArgs e) { 
         SaveCommand.RaiseCanExecuteChanged(); 
      }

      public MyIcommand CancelCommand { get; private set; } 
      public MyIcommand SaveCommand { get; private set; }

      public event Action Done = delegate { };
		
      private void OnCancel() { 
         Done(); 
      }

      private async void OnSave() { 
         UpdateCustomer(Customer, _editingCustomer); 
			
         if (EditMode) 
            await _repo.UpdateCustomerAsync(_editingCustomer); 
         else 
            await _repo.AddCustomerAsync(_editingCustomer); 
         Done(); 
      }

      private void UpdateCustomer(SimpleEditableCustomer source, Customer target) { 
         target.FirstName = source.FirstName; 
         target.LastName = source.LastName; 
         target.Phone = source.Phone; 
         target.Email = source.Email; 
      }

      private bool CanSave() { 
         return !Customer.HasErrors; 
      }
		
      private void CopyCustomer(Customer source, SimpleEditableCustomer target) { 
         target.Id = source.Id; 
			
         if (EditMode) { 
            target.FirstName = source.FirstName; 
            target.LastName = source.LastName; 
            target.Phone = source.Phone; 
            target.Email = source.Email; 
         }
      } 
   } 
}

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

Когда вы нажмете кнопку «Добавить клиента», вы увидите следующее представление. Когда пользователь оставляет какое-либо поле пустым, оно выделяется, а кнопка сохранения становится недоступной.

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

  • В приложении WPF, которое использует шаблон проектирования MVVM (Model-View-ViewModel), модель представления - это компонент, который отвечает за обработку логики и состояния представления приложения.

  • Файл кода программной части представления не должен содержать кода для обработки событий, возникающих из любого элемента пользовательского интерфейса (UI), такого как Button или ComboBox, а также не должен содержать никакой логики, специфичной для домена.

  • В идеале код программной части представления содержит только конструктор, который вызывает метод InitializeComponent и, возможно, некоторый дополнительный код для управления или взаимодействия со слоем представления, который трудно или неэффективно выразить в XAML, например сложные анимации.

Давайте посмотрим на простой пример событий нажатия кнопки в нашем приложении. Ниже приведен код XAML файла MainWindow.xaml, в котором вы увидите две кнопки.

<Window x:Class = "MVVMHierarchiesDemo.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo" 
   xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views" 
   xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel" 
   mc:Ignorable = "d" 
   Title = "MainWindow" Height = "350" Width = "525">
	
   <Window.DataContext> 
      <local:MainWindowViewModel/> 
   </Window.DataContext>
	
   <Window.Resources> 
      <DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
         <views:CustomerListView/> 
      </DataTemplate>
		 
      <DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
         <views:OrderView/>
      </DataTemplate> 
   </Window.Resources> 

   <Grid> 
      <Grid.RowDefinitions> 
         <RowDefinition Height = "Auto" /> 
         <RowDefinition Height = "*" /> 
      </Grid.RowDefinitions> 
		
      <Grid x:Name = "NavBar"> 
         <Grid.ColumnDefinitions> 
            <ColumnDefinition Width = "*" />
            <ColumnDefinition Width = "*" /> 
            <ColumnDefinition Width = "*" /> 
         </Grid.ColumnDefinitions>
			
         <Button Content = "Customers" 
            Command = "{Binding NavCommand}" 
            CommandParameter = "customers" 
            Grid.Column = "0" />
				
         <Button Content = "Order" 
            Command = "{Binding NavCommand}" 
            CommandParameter = "orders" 
            Grid.Column = "2" />
      </Grid>
		
      <Grid x:Name = "MainContent" Grid.Row = "1"> 
         <ContentControl Content = "{Binding CurrentViewModel}" />
      </Grid> 
		
   </Grid> 

</Window>

Вы можете видеть, что свойство кнопки Click не используется в приведенном выше файле XAML, но свойства Command и CommandParameter используются для загрузки различных представлений при нажатии кнопки. Теперь вам нужно определить реализацию команд в файле MainWindowViewModel.cs, а не в файле View. Ниже приводится полная реализация MainWindowViewModel.

using MVVMHierarchiesDemo.ViewModel; 
using MVVMHierarchiesDemo.Views; 

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo { 

   class MainWindowViewModel : BindableBase { 
	
      public MainWindowViewModel() { 
         NavCommand = new MyICommand<string>(OnNav); 
      } 

      private CustomerListViewModel custListViewModel = new CustomerListViewModel(); 
      private OrderViewModel orderViewModelModel = new OrderViewModel();

      private BindableBase _CurrentViewModel; 
		
      public BindableBase CurrentViewModel { 
         get { return _CurrentViewModel; } 
         set { SetProperty(ref _CurrentViewModel, value); } 
      } 
		
      public MyICommand<string> NavCommand { get; private set; }

      private void OnNav(string destination) { 
		
         switch (destination) { 
            case "orders": 
               CurrentViewModel = orderViewModelModel; 
               break; 
            case "customers":
               default: 
               CurrentViewModel = custListViewModel; 
               break; 
         } 
      } 
   }
}

Получите все свои модели представления из класса BindableBase. Когда приведенный выше код скомпилирован и выполнен, вы увидите следующий результат.

Как видите, мы добавили только две кнопки и CurrentViewModel в наш MainWindow. Теперь, если вы нажмете любую кнопку, он перейдет к этому конкретному представлению. Нажмите кнопку «Клиенты», и вы увидите, что отображается CustomerListView.

Мы рекомендуем вам выполнить приведенный выше пример пошаговым методом для лучшего понимания.

Идея модульного тестирования заключается в том, чтобы взять отдельные фрагменты кода (единицы) и написать методы тестирования, которые используют код ожидаемым образом, а затем проверить, получают ли они ожидаемые результаты.

  • Будучи самим кодом, модульные тесты компилируются так же, как и остальная часть проекта.

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

Давайте посмотрим на созданный ранее пример. Ниже приводится реализация модели ученика.

using System.ComponentModel;

namespace MVVMDemo.Model {
 
   public class StudentModel {}
	
   public class Student : INotifyPropertyChanged { 
      private string firstName; 
      private string lastName;

      public string FirstName { 
         get { return firstName; }
			
         set { 
            if (firstName != value) { 
               firstName = value; 
               RaisePropertyChanged("FirstName");
               RaisePropertyChanged("FullName"); 
            } 
         }
      }

      public string LastName { 
         get { return lastName; } 
			
         set { 
            if (lastName != value) { 
               lastName = value; 
               RaisePropertyChanged("LastName");
               RaisePropertyChanged("FullName");
            } 
         } 
      }

      public string FullName { 
         get { 
            return firstName + " " + lastName; 
         } 
      }

      public event PropertyChangedEventHandler PropertyChanged;

      private void RaisePropertyChanged(string property) { 
         if (PropertyChanged != null) { 
            PropertyChanged(this, new PropertyChangedEventArgs(property)); 
         } 
      } 
   } 
}

Ниже приводится реализация StudentView.

<UserControl x:Class="MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:data = "clr-namespace:MVVMDemo.Model" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">

   <UserControl.Resources> 
      <DataTemplate DataType = "{x:Type data:Student}"> 
		
         <StackPanel Orientation = "Horizontal"> 
            <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
               Width = "100" Margin = "3 5 3 5"/> 
					
            <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
               Width = "100" Margin = "0 5 3 5"/> 
					
            <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
               Margin = "0 5 3 5"/> 
         </StackPanel> 
			
      </DataTemplate> 
   </UserControl.Resources>

   <Grid> 
      <StackPanel Orientation = "Horizontal"> 
         <ListBox ItemsSource = "{Binding Students}"
            SelectedItem = "{Binding SelectedStudent}"/> 
				
         <Button Content = "Delete" 
            Command = "{Binding DeleteCommand}" 
            HorizontalAlignment = "Left" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
      </StackPanel> 
   </Grid>

</UserControl>

Ниже приводится реализация StudentViewModel.

using MVVMDemo.Model;
 
using System.Collections.ObjectModel; 
using System.Windows.Input; 
using System;

namespace MVVMDemo.ViewModel { 

   public class StudentViewModel { 
	
      public MyICommand DeleteCommand { get; set;}
		
      public StudentViewModel() { 
         LoadStudents(); 
         DeleteCommand = new MyICommand(OnDelete, CanDelete); 
      }

      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }

      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();

         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
			
         Students = students; 
      } 
		
      private Student _selectedStudent; 
		
      public Student SelectedStudent { 
         get { 
            return _selectedStudent; 
         } 
			
         set { 
            _selectedStudent = value;
            DeleteCommand.RaiseCanExecuteChanged(); 
         } 
      } 
		
      private void OnDelete() { 
         Students.Remove(SelectedStudent); 
      }

      private bool CanDelete() { 
         return SelectedStudent != null; 
      }
		
      public int GetStudentCount() { 
         return Students.Count; 
      } 
   } 
}

Ниже приведен файл MainWindow.xaml.

<Window x:Class = "MVVMDemo.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:local = "clr-namespace:MVVMDemo" 
   xmlns:views = "clr-namespace:MVVMDemo.Views" 
   mc:Ignorable = "d" 
   Title = "MainWindow" Height = "350" Width = "525">

   <Grid> 
      <views:StudentView x:Name = "StudentViewControl"/> 
   </Grid>
 
</Window>

Ниже приводится реализация MyICommand, реализующая интерфейс ICommand.

using System; 
using System.Windows.Input;

namespace MVVMDemo { 

   public class MyICommand : ICommand { 
      Action _TargetExecuteMethod; 
      Func<bool> _TargetCanExecuteMethod;

      public MyICommand(Action executeMethod) { 
         _TargetExecuteMethod = executeMethod; 
      }

      public MyICommand(Action executeMethod, Func<bool> canExecuteMethod) { 
         _TargetExecuteMethod = executeMethod;
         _TargetCanExecuteMethod = canExecuteMethod; 
      }

      public void RaiseCanExecuteChanged() {
         CanExecuteChanged(this, EventArgs.Empty); 
      }
		
      bool ICommand.CanExecute(object parameter) { 
		
         if (_TargetCanExecuteMethod != null) { 
            return _TargetCanExecuteMethod();
         } 
			
         if (_TargetExecuteMethod != null) { 
            return true; 
         } 
			
         return false; 
      }
		
      // Beware - should use weak references if command instance lifetime
         is longer than lifetime of UI objects that get hooked up to command
			
      // Prism commands solve this in their implementation
		
      public event EventHandler CanExecuteChanged = delegate { };

      void ICommand.Execute(object parameter) { 
         if (_TargetExecuteMethod != null) { 
            _TargetExecuteMethod(); 
         } 
      } 
   }
}

Когда приведенный выше код скомпилирован и выполнен, вы увидите следующий вывод в главном окне.

Чтобы написать модульный тест для приведенного выше примера, давайте добавим в решение новый тестовый проект.

Добавьте ссылку на проект, щелкнув правой кнопкой мыши ссылку "Ссылки".

Выберите существующий проект и нажмите ОК.

Давайте теперь добавим простой тест, который будет проверять количество студентов, как показано в следующем коде.

using System; 

using Microsoft.VisualStudio.TestTools.UnitTesting; 
using MVVMDemo.ViewModel;

namespace MVVMTest { 
   [TestClass] 
	
   public class UnitTest1 { 
      [TestMethod] 
		
      public void TestMethod1() { 
         StudentViewModel sViewModel = new StudentViewModel(); 
         int count = sViewModel.GetStudentCount();
         Assert.IsTrue(count == 3); 
      } 
   } 
}

Чтобы выполнить этот тест, выберите пункт меню Тест → Выполнить → Все тесты.

В обозревателе тестов вы можете увидеть, что тест пройден, потому что в StudentViewModel добавлены три студента. Измените условие счета с 3 на 4, как показано в следующем коде.

using System; 

using Microsoft.VisualStudio.TestTools.UnitTesting; 
using MVVMDemo.ViewModel;

namespace MVVMTest { 
   [TestClass] 
	
   public class UnitTest1 { 
      [TestMethod] public void TestMethod1() {
         StudentViewModel sViewModel = new StudentViewModel(); 
         int count = sViewModel.GetStudentCount();
         Assert.IsTrue(count == 4);
      } 
   } 
}

Когда план тестирования будет выполнен снова, вы увидите, что тест не прошел, потому что количество студентов не равно 4.

Мы рекомендуем вам выполнить приведенный выше пример пошаговым методом для лучшего понимания.

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

  • Prism
  • MVVM Light
  • Калибурн Микро

Призма

Prism предоставляет руководство в виде примеров и документации, которые помогут вам легко разрабатывать и создавать многофункциональные, гибкие и легко обслуживаемые настольные приложения Windows Presentation Foundation (WPF). Богатые Интернет-приложения (RIA), созданные с помощью подключаемого модуля браузера Microsoft Silverlight и приложений Windows.

  • Prism использует шаблоны проектирования, которые воплощают важные принципы архитектурного проектирования, такие как разделение задач и слабая связь.

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

  • Эти типы приложений известны как составные приложения.

Prism имеет ряд готовых функций. Ниже приведены некоторые из важных функций Prism.

Шаблон MVVM

Prism поддерживает шаблон MVVM. Он имеет класс Bindablebase, аналогичный тому, который реализован в предыдущих главах.

У него есть гибкий ViewModelLocator, который имеет к нему соглашения, но позволяет вам переопределить эти соглашения и декларативно подключить ваши представления и ViewModels слабосвязанным способом.

Модульность

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

Состав пользовательского интерфейса / регионы

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

Навигация

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

Команды

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

Pub / Sub События

Prism также поддерживает события Pub / Sub. Это слабо связанные события, в которых издатель и подписчик могут иметь разное время жизни и не должны иметь явных ссылок друг на друга для связи посредством событий.

MVVM Light

MVVM Light разработан Laurent Bugnion и помогает отделить представление от модели, что позволяет создавать более чистые приложения, которые проще поддерживать и расширять.

  • Он также создает тестируемые приложения и позволяет иметь гораздо более тонкий слой пользовательского интерфейса (который труднее тестировать автоматически).

  • В этом наборе инструментов особое внимание уделяется открытию и редактированию пользовательского интерфейса в Blend, включая создание данных во время разработки, чтобы пользователи Blend могли «что-то видеть», когда они работают с элементами управления данными.

Калибурн Микро

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

  • Caliburn Micro - это небольшая, но мощная платформа, предназначенная для создания приложений на всех платформах XAML.

  • Благодаря мощной поддержке MVVM и других проверенных шаблонов пользовательского интерфейса Caliburn Micro позволит вам быстро создать свое решение без необходимости жертвовать качеством кода или тестируемостью.