Пример MVVM (Model-View-ViewModel) или программирование на WPF (Silverlight)

создано: 30.11.2010 | опубликовано: 21.04.2014 | обновлено: 06.07.2017 | просмотров: 22009 | комментариев: 13 | Просто о NET

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

Вступление

Очень просто. Есть список персон, у которых в скобках есть цифра (сначала подразумевалось, что это возраст). Есть детализированное представление записи и кнопки “увеличение” и “уменьшение” этого самой цифры (пусть это будет, всё-таки, возраст). Написание данного приложения заняло немногим около получаса. Безусловно, что “маленькие” проекты писать с использованием шаблона MVVM (Model-View-ViewModel) по меньшей мере нецелесообразно в силу временных затрат. Но я использовал этот шаблон программирования именно для того, чтобы на простом примере показать “что это такое?” и “с чем его едят?”.

Статья

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

Очень просто. Есть список персон, у которых в скобках есть цифра (сначала подразумевалось, что это возраст). Есть детализированное представление записи и кнопки “увеличение” и “уменьшение” этого самой цифры (пусть это будет, всё-таки, возраст). Написание данного приложения заняло немногим около получаса. Безусловно, что “маленькие” проекты писать с использованием шаблона MVVM (Model-View-ViewModel) по меньшей мере нецелесообразно в силу временных затрат. Но я использовал этот шаблон программирования именно для того, чтобы на простом примере показать “что это такое?” и “с чем его едят?”.

Как Вы уже наверное успели заметить, в проекте существует три папки: Model, View, ViewModel. Итак, для начала создадим файл ViewModelBase.cs, который станет прародителем некоторых классов.

public class ViewModelBase : INotifyPropertyChanged
{
    public String DisplayName { get; set; }

    #region INotifyPropertyChanged Members

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

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}

Этот класс реализует интерфейс INotifyPropertyChanged, чтобы было проще.

Далее покажу содержание файла Person.cs. Как Вы уже заметили это есть та самая модель данных, что, естественно, лежит в папке Model. Класс прост, поэтому описывать его не хочется, просто посмотрите. Вот она, модель:

/// Это модель для теста. Простой класс Person ///
public class Person : ViewModelBase {
  private String firstName;
  public String FirstName {
    get { return firstName; }
    set {
      firstName = value;
      base.RaisePropertyChanged("FirstName");
    }
  }
  private String lastName;
  public String LastName {
    get { return lastName; }
    set {
      lastName = value;
      base.RaisePropertyChanged("LastName");
    }
  } private Int32 age;
  public Int32 Age {
    get { return age; }
    set {
      age = value; base.RaisePropertyChanged("Age");
    }
  }
}

Следующим листингом будет файл PeopleViewModel.cs. А я остановлюсь на ключевых моментах. В силу того, что проект называется “простой”, то и данные мы будет получать статично. Вот конструктор класса:

public PeopleViewModel()
{
    people = new ObservableCollection<Person>()
    {
        new Person() { Age = 23, FirstName = "Иван", LastName = "Иванов" },
        new Person() { Age = 22, FirstName = "Петр", LastName = "Петров" },
        new Person() { Age = 42, FirstName = "Сидор", LastName = "Сидоров" },
        new Person() { Age = 36, FirstName = "Сергей", LastName = "Сергеев" },
        new Person() { Age = 3, FirstName = "Анатолий", LastName = "Попов" }
    };
}

Стоит также упомянуть о добавленных свойствах:

#region Properties

public Person SelectedPerson
{
    get
    {
        return currentPerson;
    }
    set
    {
        if (currentPerson != value)
        {
            currentPerson = value;
            RaisePropertyChanged("SelectedPerson");
        }
    }
}

public ObservableCollection<Person> People
{
    get { return people; }
}

#endregion

Теперь пришло время показать, как же это всё привязывается к данным. Для этого заглянем в файл разметки PeopleViewer.xaml.  Это один из самых “главных” файлов в этом самом простом приложении. Для начала обратите внимание на строки:

<UserControl.Resources>
        <vm:PeopleViewModel x:Key="viewModel" />
<UserControl.Resources>

, в которых регистрируется ViewModel (не забудьте указать namespace).

xmlns:vm="clr-namespace:MVVMTest.ViewModel"

После того как ViewModel зарегистрирована, надо её быстренько отбиндить :) Как это делается, можно лицезреть

<Grid DataContext="{Binding Source={StaticResource viewModel}}">

. На представленном листинге видно что ListBox  биндится свойству People, которое было создано специально для этого и являет собой ни что иное как ObservableCollection.

Обратите внимание на

<Grid x:Name="PersonDetails"
                  Grid.Row="0"
                  DataContext="{Binding SelectedPerson}"
                  Margin="5">

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

И, наконец, про кнопки

<StackPanel Orientation="Horizontal"

                       HorizontalAlignment="Center"
                       Grid.Row="1">
               <Button x:Name="button"
                       Content="-"
                       Width="32"
                       Height="32"
                       Command="{Binding DecreaseCommand}">
               Button>
               <Button x:Name="button1"
                       Content="+"
                       Width="32"
                       Height="32"
                       Command="{Binding IncreaseCommand}">
               Button>
           StackPanel>

Обратите внимание, как элегантно смотрится привязка (Binding). Вот теперь пришло время показать еще одну немаловажную часть шаблона MVVM.

Вот содержание класса RelayCommand.cs:

public class RelayCommand : ICommand
    {
        #region Fields

        readonly Action<object> _execute;
        readonly Predicate<object> _canExecute;

        #endregion // Fields

        #region Constructors

        public RelayCommand(Action<object> execute)
            : this(execute, null)
        {
        }

        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");

            _execute = execute;
            _canExecute = canExecute;
        }
        #endregion // Constructors

        #region ICommand Members

        [DebuggerStepThrough]
        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute(parameter);
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }

        #endregion // ICommand Members
    }

Хочу немного пояснить. На данный момент в WPF присутствует интерфейс ICommand, чего не скажешь о  Silverlight. На момент написания статьи версия Silverlight имеет номер 3. И как утверждает Microsoft, 12 апреля 2010 года свет увидит четвертая версия Silverlight, где интерфейс ICommand уже будет реализован.

Осталось показать из чего состоит Window1.xaml, в котором представление и подключается (смотрите на строку номер 9):

<Window x:Class="MVVMTest.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:MVVMTest.View"
        WindowStartupLocation="CenterScreen"
        Title="Тест MVVM"
        MinHeight="300"
        MinWidth="300">
    <vm:PeopleViewer />
</Window>

Хотелось бы отметить что Window1.xaml.cs и PeopleViewer.xaml.cs содержат только пустые конструкторы.

Подробнее о MVVM

Комментарии к статье (13)

19.05.2017 8:24:06 param-pam-pam

Спасибо, простой и доходчивый пример. Но разве правильно наследовать Person от ViewModelBase? Это же модель, которая, по идее, не должна ни от чего зависеть.

30.07.2011 12:55:00 Calabonga

Это просто название класса. А в классе реализация INotifyPropertyChanged

22.06.2012 8:44:36 Илья
22.06.2012 9:02:50 calabonga

Илья, мне просто не хотелось что была привязка к конкретному фреймворку (MVVM Light, Prism или к моей собственной сборке) поэтому пример абстракно описывает концепцию.

08.08.2012 8:33:11 Руслан

Цитата:

Следующим листингом будет файл PeopleViewModel.cs. Целиком можно будет посмотреть скачав файл проекта (ссылка в конце статьи).

Ввела в заблуждение. :D

18.05.2015 23:17:38 Calabonga

GooRoo, слишком громкое заявление для безосновательного выкрика! А где обоснования, примеры, подтверждения, доказательства? :)

15.07.2015 10:25:30 Дмитрий

mvvm реализован неверно так как у вас нет Model. Всё просто.

12.11.2015 8:59:29 Altherial

"...чтобы как можно проще рассказать о шаблоне MVVM..." Не вижу, чтобы о чём-то здесь рассказывалось. Статья в стиле: "Внимание, сейчас будет кусок кода: %кусок_кода%. А теперь - другой кусок кода: %другой_кусок_кода%." Информации, раскрывающей суть mvvm, в статье нет. Простой пример простого приложения.

20.11.2015 20:25:46 Calabonga

Дмитрий,
> mvvm реализован неверно так как у вас нет Model. Всё просто."
Тогда что по-вашему Person собой представляет? И где указание на источник с правильной реализацией? :)

20.11.2015 20:25:46 Calabonga

Altherial,

Разве в заголовке написано "MVVM - как паттерн проектирования: описание, история, авторы и т.д."? Кажется вы не очень внимательно вчитываетесь в материал, или некорректно трактуете содержимое. На мой взгляд, пример есть пример, а ссылки в начале статьи на описание паттерна достаточно для самостоятельного изучения.

24.12.2016 12:30:18 Иваныч