Пример MVVM (Model-View-ViewModel) или программирование на WPF (Silverlight)
WPF, MVVM, Silverlight | создано: 30.11.2010 | опубликовано: 22.04.2014 | обновлено: 26.06.2024 | просмотров: 44112 | всего комментариев: 13
Для того чтобы как можно проще рассказать о шаблоне 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)
Спасибо, простой и доходчивый пример. Но разве правильно наследовать Person от ViewModelBase? Это же модель, которая, по идее, не должна ни от чего зависеть.
Это просто название класса. А в классе реализация INotifyPropertyChanged
А где ссылка на файл проекта?
Илья, мне просто не хотелось что была привязка к конкретному фреймворку (MVVM Light, Prism или к моей собственной сборке) поэтому пример абстракно описывает концепцию.
Цитата:
Следующим листингом будет файл PeopleViewModel.cs. Целиком можно будет посмотреть скачав файл проекта (ссылка в конце статьи).
Ввела в заблуждение. :D
Поправил статью.
MVVM реализован неверно!
GooRoo, слишком громкое заявление для безосновательного выкрика! А где обоснования, примеры, подтверждения, доказательства? :)
mvvm реализован неверно так как у вас нет Model. Всё просто.
"...чтобы как можно проще рассказать о шаблоне MVVM..." Не вижу, чтобы о чём-то здесь рассказывалось. Статья в стиле: "Внимание, сейчас будет кусок кода: %кусок_кода%. А теперь - другой кусок кода: %другой_кусок_кода%." Информации, раскрывающей суть mvvm, в статье нет. Простой пример простого приложения.
Дмитрий,
> mvvm реализован неверно так как у вас нет Model. Всё просто."
Тогда что по-вашему Person собой представляет? И где указание на источник с правильной реализацией? :)
Altherial,
Разве в заголовке написано "MVVM - как паттерн проектирования: описание, история, авторы и т.д."? Кажется вы не очень внимательно вчитываетесь в материал, или некорректно трактуете содержимое. На мой взгляд, пример есть пример, а ссылки в начале статьи на описание паттерна достаточно для самостоятельного изучения.
Статья ГА.НО!!! Бесполезные куски кода