Весь код этого туториала можно найти здесь на моем github.

Введение в демонстрационное приложение

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

  • Секундомер, который может начать останавливаться и очищать время
  • Get time добавит текущее значение секундомера в ListView слева.
  • Очистить время очистит ListView

Компонент демонстрационного приложения

Вот его компоненты;

1. Представление пользовательского интерфейса приложения (XAML-файл)

В приложении C# wpf внешний вид приложения будет описан в файле XAML.

ВОТ КОД (MainWindow.xaml)

2. Логика пользовательского интерфейса приложения (.xaml.cs)

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

ВОТ КОД (MainWindow.xaml.cs)

Объяснение демо-приложения

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

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

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

Реализация ViewModel

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

Интерфейс между ViewModel и Model прост. ViewModel просто будет вызывать методы или использовать объекты класса, расположенные в классах нашей модели. Итак, нам нужно сосредоточиться на интерфейсе между View и ViewModel.

Три интерфейса;

  1. Привязка данных: привяжите значение пользовательского интерфейса к свойству внутри ViewModel.
  2. Команда: когда пользователь манипулирует пользовательским интерфейсом, он переходит в ViewModel в виде команд, которые определяют стандартный интерфейс.
  3. Уведомления: это когда данные были обновлены внутри ViewModel, и он отправляет событие уведомления в представление и сообщает ему, что нужно получить данные для их обновления в пользовательском интерфейсе.

Привязка данных

Любой элемент пользовательского интерфейса, который должен взаимодействовать с данными в ViewModel, должен быть привязан к данным. Из нашей демонстрации элементы, которые нам нужно связать, следующие:

  • Значение секундомера (текст TextBlock)
  • все кнопки (команда Button)
  • Посмотреть список

Модификация в представлении пользовательского интерфейса

Вот привязка данных внутри представления пользовательского интерфейса (файл xaml)

Модификация в логике пользовательского интерфейса (код позади)

Весь связующий код внутри логики пользовательского интерфейса (файл xaml.cs) перемещается, что осталось, это метод, чтобы сказать, где будет подключаться тот элемент привязки данных, к которому относится наш ViewMode.

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

Реализация ViewModel

А вот и наш новый компонент. Рекомендуется заканчивать имя файла словом ViewModel.

в этом демонстрационном приложении я назвал его TimerTrackerViewModel.cs.

то, что мы собираемся реализовать в первую очередь, — это объявление всех данных, которые можно привязать.

для ICommand — это специальный интерфейс, который будет использоваться для нашей команды (нажатие кнопки).
тогда меньшая часть ViewModel будет перемещенным связующим кодом.

СМОТРИТЕ ПОЛНЫЙ КОД ЗДЕСЬ (timerTrackerViewModel.cs)

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

Команды

Этот раздел будет о том, как мы можем связать команду (или команду пользовательского интерфейса, например, нажатие кнопки) с нашей ViewModel.

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

Что нам нужно сделать, так это реализовать эту ICommand и просто соединить их вместе! просто не правда ли.

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

Вот реализация под названием RelayCommand.

Эти элементы: CanExecuteChanged, CanExecute, Execute — это то, что интерфейс требует от нас реализации.

  • Для этой реализации нас не будет интересовать CanExecuteChanged, поэтому мы просто передаем событие в пустой метод.
  • Наша RelayCommand всегда сможет выполниться, поэтому мы просто возвращаем true
  • И когда команда запускается, мы просто выполняем функцию, которая передается ей при создании этого объекта RealayCommand.

А вот как мы используем его в ViewModel. Мы просто передаем метод, который будет выполняться для каждой команды.

StartCommand = new RelayCommand(StartFunc);
StopCommand = new RelayCommand(StopFunc);
ClearCommand = new RelayCommand(ClearFunc);
GetTimeCommand = new RelayCommand(GetTimeFunc);
ClearTimeCommand = new RelayCommand(ClearTimeFunction);

Два компонента готовы. Еще один, чтобы пойти. На этом этапе вы можете попробовать запустить приложение. если вы поместите точку останова внутри StartFunc и нажмете кнопку запуска в пользовательском интерфейсе. Вы можете видеть, что функция выполняется.
и значение currentTime будет обновлено функцией UpdateFunc
Но пользовательский интерфейс не будет обновляться.

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

Уведомление

Для этого нам нужно реализовать что-то под названием INotifyPropertyChanged.

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

вы можете найти учебник Microsoft здесь.

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

Простая реализация

Вот модификация нашей декларации ViewModel;

class timerTrackerViewModel: INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };

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

private string _currentTime = "00:00:00";
public string CurrentTime
{
 get
 {
  return _currentTime;
 }
 set
 {
  if (_currentTime == value)
   return;
  _currentTime = value;
  PropertyChanged(this, new PropertyChangedEventArgs(nameof(CurrentTime)));
 }
}

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

Улучшение реализации

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

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

Еще одно улучшение

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

public class BaseViewModel : INotifyPropertyChanged
    {
        /// <summary>
        /// The event that is fired when any child property changes its value
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
    }

Теперь объявление свойства в нашей ViewModel снова выглядит чистым;

class timerTrackerViewModel: BaseViewModel
{
 #region Public Properties
 public bool btnStartEnable { get; set; }
 public bool btnStopEnable { get; set; }
 public ICommand StartCommand { get; private set; }
 public ICommand StopCommand { get; private set; }
 public ICommand ClearCommand { get; private set; }
 public ICommand GetTimeCommand { get; private set; }
 public ICommand ClearTimeCommand { get; private set; }
 public ObservableCollection<string> SavedTimeList { get; set; }
 public string CurrentTime { get; set; }

Запустите код, теперь кнопка очистки старт-стоп в нашем коде должна работать.

Вы должны заметить в объявлении свойства выше, что появится новый элемент (я выделил его желтым цветом!)

Итак, давайте познакомимся с ним.

public ObservableCollection<string> SavedTimeList { get; set; }

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

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

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

Вот почему мы используем эту ObservableCollection. Это реализация INotifyCollectionChanged. Он уведомит пользовательский интерфейс, если этот список (ObservableCollection) был добавлен, удален или очищен.

и теперь мы можем использовать его в наших оставшихся кнопках;

private void GetTimeFunc()
{
 SavedTimeList.Add(CurrentTime);
}
private void ClearTimeFunc()
{
 SavedTimeList.Clear();
}

Резюме кода

Резюме

Это довольно долгая трансформация. Итак, я хотел бы подвести итоги того, что мы сделали.

Чтобы реализовать MVVM, нам нужно сделать 3 вещи.

1. Привязка данных, мы связываем значение пользовательского интерфейса со свойствами в нашей модели представления.

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

3. Уведомление, когда данные в ViewModel будут обновлены, оно отправит уведомление в пользовательский интерфейс, используя INotifyPropertyChanged и INotifyCollectionChanged.

И Мы Сделали!!! Всем удачного кодирования (почему это предложение так популярно в учебниках по программированию)

Ссылка

1. Обзор привязки данных