Слайд 1Разработка WPF приложений в стиле ViewModel First
Денис Цветцих
АстроСофт
http://www.astrosoft.ru/
11-я конференция .NET
разработчиков
31 октября 2015
dotnetconf.ru
Слайд 2Почему это актуально
WPF все ещё жив
MVVM – тема множества
докладов и статей
Множество разных реализаций
от MVVM.Light
до Prism с мануалом
на 250 страниц
Слайд 3В чем проблема?
Много разных реализаций MVVM
Непонятно, чем они отличаются
Непонятно, какую
из них использовать и в каких случаях
Слайд 4Почему я здесь
Накоплен интересный опыт участия в WPF проектах
Есть опыт,
связанный с фреймворками:
Использование сторонних
Доработка сторонних
Изобретение своего MVVM фреймворка
Слайд 5Опрос
Кто собирается написать WPF приложение?
Кто при этом использовал какие-нибудь
MVVM библиотеки/фреймворки?
Кто их вас изобрел свой собственный MVVM велосипед фреймворк?
Слайд 6О чем мы поговорим?
Что такое MVVM?
Какими бывают подходы к его
реализации?
Как отличаются организация CompositeUI и дочерних окон в разных подходах?
Какой
подход лучше использовать?
Где найти реализацию этого подхода?
Рекомендации на основе нашего опыта
Слайд 8Что со всем этим делать?
Как решать типовые задачи?
CompositeUI (MasterDetail форма)
Навигация
(дочерние окна)
Слайд 9Нам нужен MVVM фреймворк!
Бывает 2 типов:
ViewFirst
ViewModelFirst
Это не разные
реализации паттерна MVVM
Это разные подходы к решению типовых задач
с использованием MVVM
Слайд 12ViewFirst (Prism)
Отображение нового региона:
Создать View
Создать ViewModel для View
View.DataContext = ViewModel
Инициализировать
ViewModel
Grid.Row="1" x:Name="UserListBox"
SelectedItem="{Binding SelectedUser, Mode=TwoWay}"
ItemsSource="{Binding Users}">
public UserListView()
{
DataContext = new UserListViewModel();
}
Text="{Binding User.FirstName}" />
public UserDetailsView()
{
DataContext = new UserDetailsViewModel();
}
Слайд 16UserListViewModel
public class UserListViewModel : ViewModel
{
private User _selectedUser;
public IEnumerable Users { get; } // инициализация
public
User SelectedUser
{
get { return _selectedUser; }
set
{
_selectedUser = value;
OnPropertyChanged();
}
}
}
Слайд 17UserDetailsViewModel
public class UserDetailsViewModel : ViewModel
{
private User _user;
public
User User
{
get { return
_user; }
set
{
_user = value;
OnPropertyChanged();
}
}
}
Слайд 18Вопрос
Как сделать так, чтобы UserListViewModel.SelectedUser синхронизировалось с UserDetailsViewModel.User?
Ответ в стиле
ViewFirst – MessageBus
Слайд 20MessageBus
public class MessageBus
{
public static MessageBus Instance = new
MessageBus();
public event EventHandler SelectedUserChanged;
public void OnSelectedUserChanged(User
user)
{
SelectedUserChanged?.Invoke(this, new UserChangedEventArgs(user));
}
}
public class UserChangedEventArgs : EventArgs
{
public UserChangedEventArgs(User user)
{
User = user;
}
public User User { get; }
}
Слайд 21UserListViewModel
public class UserListViewModel : ViewModel
{
public User SelectedUser
{
get { return _selectedUser; }
set
{
_selectedUser = value;
OnPropertyChanged();
MessageBus.Instance.OnSelectedUserChanged(value);
}
}
}
Слайд 22UserDetailsViewModel
public class UserDetailsViewModel : ViewModel
{
public UserDetailsViewModel()
{
MessageBus.Instance.SelectedUserChanged +=
(s, e) => User
= e.User;
}
}
Слайд 23Недостатки
1) Используется MessageBus, предназначенный для интеграции систем
2) На широковещательное
событие может подписаться любой объект
3) Поведение системы становится запутанным и
неочевидным
Слайд 24ViewModelFirst (энтузиасты)
Отображение нового региона:
1) Создать ViewModel
2) Инициализировать ViewModel
3) Создать View
для ViewModel
4) View.DataContext = ViewModel
Слайд 25Чистим CodeBehind
public UserListView()
{
DataContext = new UserListViewModel();
}
public
UserDetailsView()
{
DataContext = new UserDetailsViewModel();
}
Слайд 26Убираем MessageBus
public class UserListViewModel : ViewModel
{
public User SelectedUser
{
get { return _selectedUser; }
set
{
_selectedUser = value;
OnPropertyChanged();
MessageBus.Instance.OnSelectedUserChanged(value);
}
}
}
Слайд 27Убираем MessageBus
public class UserDetailsViewModel : ViewModel
{
public UserDetailsViewModel()
{
MessageBus.Instance.SelectedUserChanged +=
(s, e) => User
= e.User;
}
}
Слайд 28Вопрос
Как сделать так, чтобы UserListViewModel.SelectedUser синхронизировалось с UserDetailsViewModel.User?
Ответ в стиле
ViewModelFirst – нам нужна родительская ViewModel
Слайд 29MainWindowViewModel
public class MainWindowViewModel : ViewModel
{
public UserDetailsViewModel UserDetailsViewModel {
get; private set; }
public UserListViewModel UserListViewModel { get;
private set; }
public void Initialize()
{
UserListViewModel.PropertyChanged += (s, e) =>
{
if (e.PropertyName == "SelectedUser")
UserDetailsViewModel.User = UserListViewModel.SelectedUser;
};
}
}
DataContext="{Binding UserListViewModel}"
Grid.Column="0" />
DataContext="{Binding UserDetailsViewModel}"
Grid.Column="1" />
Слайд 33ViewFirst: показать новый элемент
Дочернее окно, новый таб:
Navigation.Show(Value);
или
Navigation.Show("View", Value);
Аналогично вебу: http://address.ru/?arg=value
ViewFirst
предлагает в WPF организовать навигацию аналогично веб-приложению
Слайд 34ViewModelFirst: показать новый элемент
var vm = Navigation.Get();
vm.Arg = Value;
vm.Show();
Аналогично окну
WPF:
var wnd = new Window();
wnd.Arg1 = Value1;
wnd.Show();
Слайд 37Отображение дочернего окна
private void OnShowDetails(User user)
{
var detailsVM =
Factory.Resolve();
detailsVM.User = user;
detailsVM.Closed +=
(s, e) => { /* обработка e.DialogResult */ };
detailsVM.Show();
}
Слайд 38ChildViewModel
public abstract class ChildViewModel : ViewModel, IChildViewModel
{
[Dependency]
public IChildViewModelManager ChildViewModelManager { private get; set; }
public
bool IsClosed { get; private set; } // уже закрыли или нет?
protected void Close()
{
if (IsClosed) throw new InvalidOperationException(“closed");
IsClosed = true;
ChildViewModelManager.Close(this);
}
public void Show()
{
ChildViewModelManager.Show(this);
}
}
Слайд 39ChildViewModelManager
public class ChildViewModelManager : IChildViewModelManager
{
// открытые окна
private
readonly Dictionary _openedWindows
= new Dictionary();
//
по типу ViewModel возвращает View
[Dependency]
public IViewTypeResolver ViewTypeResolver
{ private get; set; }
}
Слайд 40ChildViewModelManager: Show
private void Show(IChildViewModel viewModel)
{
// получить тип окна,
которое будем открывать
var windowType = ViewTypeResolver.
ResolveViewType(viewModel.GetType());
// создать экземпляр
окна
var window =
(Window)Activator.CreateInstance(windowType);
// запомнить, какое окно открываем
_openedWindows.Add(viewModel.GetType(), window);
window.DataContext = viewModel;
// показать окно
window.Show();
}
Слайд 41ChildViewModelManager: Close
public void Close(IChildViewModel viewModel)
{
// какое окно закрываем
var window = _openedWindows[viewModel.GetType()];
// убираем из
списка открытых
_openedWindows.Remove(viewModel.GetType());
// закрываем
Application.Current.Dispatcher.BeginInvoke(
new Action(() => window.Close()), null);
}
Слайд 42ChildWindow
public abstract class ChildWindow : Window
{
protected override void
OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
var viewModel = (ChildViewModel)DataContext;
// если ViewModel на находится в состоянии «Закрыто»
if (!viewModel.IsClosed)
{
e.Cancel = true; // не закрываем окно
// запрашиваем изменение состояния ViewModel
viewModel.Close();
}
}
}
Слайд 43Особенности ViewModelFirst
Достоинства
Позволяет реализовать CompositeUI
Не требует реализации MessageBus
Взаимодействие ViewModel более очевидное
Нет
MessageBus – нет его использования не по назначению
Позволяет удобно реализовать
поддержку дочерних окон
Недостатки
Не имеет вендорской поддержки
Слайд 44Наш рецепт
ViewModelFirst
Свой велосипед
Mugen MVVM Toolkit
IoC контейнер
ReactiveUI (ограничено)
ReactiveCommand
ObservableForProperty
Отдельная сборка для
ViewModel
Слайд 45Материалы по ViewModelFirst
Материалы доклада на GitHub:
https://github.com/denis-tsv/ViewFirst-vs-ViewModelFirst
Курс «Методология синхронной разработки приложений
в Microsoft Visual Studio 2010»
www.intuit.ru/studies/courses/2322/622/info
Mugen MVVM Toolkit
http://habrahabr.ru/post/236745/
Слайд 46Спасибо за внимание
Денис Цветцих
АстроСофт
den.tsvettsih@yandex.ru