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

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

AppMVVM
(рис.1)

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

В окне VS2008 проект выглядит так (слева “дизайнер студии”, справа “окно решения”):

designerVs proectMVVM 
(рис.2)

Как Вы уже наверное успели заметить, в проекте существует три папки: 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. Класс прост, поэтому описывать его не хочется, просто посмотрите. Вот она, модель:

/// <summary>
   /// Это модель для теста. Простой класс Person
   /// </summary>
   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<Person>.

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

<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
Скачать проект MVVMTest2008.zip | MVVMTest2010.zip

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