SlideShare ist ein Scribd-Unternehmen logo
1 von 170
Как разрабатывать UI XAML приложений
(Prism) и веб-приложений (Angular2,
React) без Message Bus
Денис Цветцих
One Systems
DotNext Msk
9 декабря 2016
http://dotnext-moscow.ru/
2
Message Bus – паттерн для интеграции
MessageBus
Application 1 Application 2
Application 3 Application 4
3
Чем плох MessageBus на UI
• Взаимодействие объектов неявное
• Сложно дебажить
• В больших проектах MessageHell
• Простор для костылей
• Messages вместо Events
• Messages вместо Bindings
4
Кто виноват?
5
Виноват Composite Application Blocks
public class MasterController : Controller
{
[EventPublication("GlobalEvent")]
public event EventHandler GlobalEvent;
}
public class DetailController : Controller
{
[EventSubscription("GlobalEvent")]
public void OnGlobalEvent(object sender, EventArgs e)
{
}
}
6
Виноват Composite Application Blocks
public class MasterController : Controller
{
[EventPublication("GlobalEvent")]
public event EventHandler GlobalEvent;
}
public class DetailController : Controller
{
[EventSubscription("GlobalEvent")]
public void OnGlobalEvent(object sender, EventArgs e)
{
}
}
7
Виноват Composite Application Blocks
public class MasterController : Controller
{
[EventPublication("GlobalEvent")]
public event EventHandler GlobalEvent;
}
public class DetailController : Controller
{
[EventSubscription("GlobalEvent")]
public void OnGlobalEvent(object sender, EventArgs e)
{
}
}
8
Виноват Composite Application Blocks
public class MasterController : Controller
{
[EventPublication("GlobalEvent")]
public event EventHandler GlobalEvent;
}
public class DetailController : Controller
{
[EventSubscription("GlobalEvent")]
public void OnGlobalEvent(object sender, EventArgs e)
{
}
}
9
Виноват Composite Application Blocks
public class MasterController : Controller
{
[EventPublication("GlobalEvent")]
public event EventHandler GlobalEvent;
}
public class DetailController : Controller
{
[EventSubscription("GlobalEvent")]
public void OnGlobalEvent(object sender, EventArgs e)
{
}
}
10
Виноват Composite Application Blocks
public class MasterController : Controller
{
[EventPublication("GlobalEvent")]
public event EventHandler GlobalEvent;
}
public class DetailController : Controller
{
[EventSubscription("GlobalEvent")]
public void OnGlobalEvent(object sender, EventArgs e)
{
}
}
11
Виноват Composite Application Blocks
public class MasterController : Controller
{
[EventPublication("GlobalEvent")]
public event EventHandler GlobalEvent;
}
public class DetailController : Controller
{
[EventSubscription("GlobalEvent")]
public void OnGlobalEvent(object sender, EventArgs e)
{
}
}
12
CAB UI: скрытая угроза
• Нет класса EventAggregator
• Но
• Любой контроллер может подписаться на
• Любое событие
• Любого другого контроллера
• Неявно (при помощи атрибута)
13
Message Bus есть во всех MVVM
• Prism – по инерции от Composite App Blocks
• Caliburn.Micro – для поддержки View-First и ViewModel-First
• MVVM от энтузиастов
• Не знают как по-другому
• Делают как у всех 
14
О чем мы поговорим?
• Типовые UI-задачи для MessageBus
• UI Composition
• Смена контекста
• Костыли
• Как без него обойтись
• MVVM-фреймворки для WPF и UWP без MessageBus
• Что c MessageBus на JS (Angular2, React)
15
Опрос
• Кто использовал MessageBus на UI?
• И считает что без него обойтись нельзя
• И считает, что без него использовать можно
• Кто не использовал MessageBus на UI?
16
Примеры с INPC кодом
public class DetailViewModel : BindableBase
{
public User User
{
get { return _user; }
set { SetProperty(ref _user, value); }
}
}
17
Реализацию INPC держим в уме
public class DetailViewModel
{
public User User { get; set; }
}
18
Задача 1: UI Composition
19
View-Switching Navigation (Prism)
20
ShellView
<ShellView>
<ContentControl
regions:RegionManager.RegionName="LeftRegion“ />
<ContentControl
regions:RegionManager.RegionName="MainRegion“ />
</ShellView>
21
View-Switching Navigation (View-First)
RegionManager.RequestNavigate("MainRegion", EmailUri);
Заменяем содержимое региона во время выполнения
22
UI Composition: MasterDetail форма
ShellView
MasterRegion DetailRegion
23
ShellView (ViewFirst)
<ShellView>
<ContentControl
regions:RegionManager.RegionName=“MasterRegion“ />
<ContentControl
regions:RegionManager.RegionName=“DetailRegion“ />
</ShellView>
24
View-First: по вьюхе создаем ViewModel
public partial class MasterView
{
[Import]
public MasterViewModel ViewModel
{
set { DataContext = value; }
}
}
25
Главный вопрос
Как DetailViewModel узнает об изменении
выделенного элемента в MasterViewModel?
26
MessageBus: обмен сообщениями между ViewModel
ShellView
MasterRegion DetailRegion
MessageBus
27
MasterViewModel
public class MasterViewModel
{
public User SelectedUser
{
get { return _selectedUser; }
set
{
SetProperty(ref _selectedUser, value);
EventAggregator.GetEvent<UserChangedMessage>()
.Publish(_selectedUser);
}
}
public ObservableCollection<User> Users { get; set; }
}
28
MasterViewModel
public class MasterViewModel
{
public User SelectedUser
{
get { return _selectedUser; }
set
{
SetProperty(ref _selectedUser, value);
EventAggregator.GetEvent<UserChangedMessage>()
.Publish(_selectedUser);
}
}
public ObservableCollection<User> Users { get; set; }
}
29
MasterViewModel
public class MasterViewModel
{
public User SelectedUser
{
get { return _selectedUser; }
set
{
SetProperty(ref _selectedUser, value);
EventAggregator.GetEvent<UserChangedMessage>()
.Publish(_selectedUser);
}
}
public ObservableCollection<User> Users { get; set; }
}
30
MasterViewModel
public class MasterViewModel
{
public User SelectedUser
{
get { return _selectedUser; }
set
{
SetProperty(ref _selectedUser, value);
EventAggregator.GetEvent<UserChangedMessage>()
.Publish(_selectedUser);
}
}
public ObservableCollection<User> Users { get; set; }
}
31
DetailViewModel
public class DetailViewModel : BindableBase
{
public DetailViewModel(IEventAggregator eventAggregator)
{
eventAggregator.GetEvent<UserChangedMessage>()
.Subscribe(OnUserChanged);
}
public User User { get; set; }
public void OnUserChanged(User user)
{
User = user;
}
}
32
DetailViewModel
public class DetailViewModel : BindableBase
{
public DetailViewModel(IEventAggregator eventAggregator)
{
eventAggregator.GetEvent<UserChangedMessage>()
.Subscribe(OnUserChanged);
}
public User User { get; set; }
public void OnUserChanged(User user)
{
User = user;
}
}
33
DetailViewModel
public class DetailViewModel : BindableBase
{
public DetailViewModel(IEventAggregator eventAggregator)
{
eventAggregator.GetEvent<UserChangedMessage>()
.Subscribe(OnUserChanged);
}
public User User { get; set; }
public void OnUserChanged(User user)
{
User = user;
}
}
34
DetailViewModel
public class DetailViewModel : BindableBase
{
public DetailViewModel(IEventAggregator eventAggregator)
{
eventAggregator.GetEvent<UserChangedMessage>()
.Subscribe(OnUserChanged);
}
public User User { get; set; }
public void OnUserChanged(User user)
{
User = user;
}
}
35
DetailViewModel
public class DetailViewModel : BindableBase
{
public DetailViewModel(IEventAggregator eventAggregator)
{
eventAggregator.GetEvent<UserChangedMessage>()
.Subscribe(OnUserChanged);
}
public User User { get; set; }
public void OnUserChanged(User user)
{
User = user;
}
}
36
Это работает, но …
Чем плохо:
• Для UserChangedMessage только один получатель
• MessageBus – из пушки по воробьям
• Но без него никак 
Чего хочется:
• Биндинг MasterVM.SelectedUser на DetailVM.User
37
Как обойтись: ViewModel-First
ShellRegion
MasterRegion DetailRegion
38
View-First vs ViewModel-First
View-First ViewModel-First
Первой создается View
MessageBus Родительская
ViewModel
ViewModel
Не нуженНеобходим
Взаимодействие
ViewModel
MessageBus
39
MasterViewModel
public class MasterViewModel
{
public User SelectedUser { get; set; }
public List<User> Users { get; set; }
}
40
DetailViewModel
public class DetailViewModel
{
public User User { get; set; }
}
41
ShellViewModel (ReactiveUI)
public class ShellViewModel
{
public MasterViewModel MasterViewModel { get; }
public DetailViewModel DetailViewModel { get; }
public ShellViewModel()
{
MasterViewModel
.WhenAny(m => m.SelectedUser, user => user.Value)
.BindTo(DetailViewModel, d => d.User);
}
}
42
ShellViewModel (ReactiveUI)
public class ShellViewModel
{
public MasterViewModel MasterViewModel { get; }
public DetailViewModel DetailViewModel { get; }
public ShellViewModel()
{
MasterViewModel
.WhenAny(m => m.SelectedUser, user => user.Value)
.BindTo(DetailViewModel, d => d.User);
}
}
43
ShellViewModel (ReactiveUI)
public class ShellViewModel
{
public MasterViewModel MasterViewModel { get; }
public DetailViewModel DetailViewModel { get; }
public ShellViewModel()
{
MasterViewModel
.WhenAny(m => m.SelectedUser, user => user.Value)
.BindTo(DetailViewModel, d => d.User);
}
}
44
ShellViewModel (ReactiveUI)
public class ShellViewModel
{
public MasterViewModel MasterViewModel { get; }
public DetailViewModel DetailViewModel { get; }
public ShellViewModel()
{
MasterViewModel
.WhenAny(m => m.SelectedUser, user => user.Value)
.BindTo(DetailViewModel, d => d.User);
}
}
45
ShellView (Caliburn)
<ShellView>
<ContentControl x:Name=“MasterViewModel" />
<ContentControl x:Name="DetailViewModel" />
</ShellView>
46
Такой разный Mediator
• MessageBus – универсальный Mediator
• Родительская ViewModel – частный медиатор
47
Подвох от Prism
Prism предлагает RegionManager (ViewFirt) для:
• View-Switching Navigation
• UI Composition
48
Что делать?
• Разделить задачи ViewSwitching Navigation и UI Composition
• RegionManager – только для навигации
• Не использовать RegionManager для UI Composition
49
UI Composition vs View-Switching Navigation
UI Composition View-Switching Navigation
Делим форму на части Да Да
Зачем делим Части проще и повторно
используются
Меняем регионы
на рантайме
Нет
Выделяем
статическую часть (меню) и
динамическую (регион)
Да
Нельзя (менять
подход к навигации)
Можно (скрепя сердце)Не использовать
MessageBus
50
ShellView: Было
<ShellView>
<ContentControl
regions:RegionManager.RegionName=“MasterRegion“ />
<ContentControl
regions:RegionManager.RegionName=“DetailRegion“ />
</ShellView>
51
ShellView: MasterDetail без RegionManager
<ShellView>
<ContentControl
regions:RegionManager.RegionName=“MasterDetailRegion“ />
</ShellView>
52
MasterDetailViewModel
public class MasterDetailViewModel
{
public MasterViewModel MasterViewModel { get; }
public DetailViewModel DetailViewModel { get; }
public MasterDetailViewModel()
{
MasterViewModel
.WhenAny(m => m.SelectedUser, user => user.Value)
.BindTo(DetailViewModel, d => d.User);
}
}
53
UI Composition без ViewSwitching
<MasterDetailView>
<MasterView DataContext="{Binding MasterViewModel}" />
<DetailView DataContext="{Binding DetailViewModel}" />
</MasterDetailView>
54
UI Composition: ViewSwitching ViewModel-Frist (Caliburn)
<Grid>
<ContentControl
x:Name=“MasterViewModel"
Grid.Column="0" />
<ContentControl
x:Name="DetailViewModel"
Grid.Column="1" />
</Grid>
55
View-Switching в разных MVVM
ViewModel-First (можно использовать для UI Composition)
• Caliburn.Micro
• MugenMvvmToolkit
View-First (нельзя использовать для UI Composition)
• Prism.WPF
• ReactiveUI (UI Composition сделать сложно)
56
Не умеют View-Switching
• Prism.Windows (UWP)
• MvvmLight
• MvvmCross
• ReactiveUI (умеет, UI Composition сделать сложно)
57
Более сложный MasterDetail
58
ShellViewModel
public class ShellViewModel
{
public MasterViewModel MasterViewModel { get; }
public DetailViewModel DetailViewModel { get; }
}
59
ViewSwitching для UI Composition (Caliburn)
<Grid>
<ContentControl
x:Name=“MasterViewModel"
Grid.Column="0" />
<ContentControl
x:Name="DetailViewModel"
Grid.Column="1" />
</Grid>
60
Как сделать ViewSwitching ViewModel-First?
• View-ViewModel маппинг –
создание View по ViewModel
• ViewModelPresenter –
отображение View
61
View-ViewModel маппинг
• Конвенция именования
ViewModels.MasterVewModel -> Views.MasterView
• Реестр
ViewResolver.Register<MasterView, MasterViewModel>();
var view = ViewResolver.Resolve(viewModel);
• Метаданные (MEF)
[View(typeof(BatchEditViewModel))] //наследник Export
public partial class BatchEditView : IView
пример кода №8 в списке полезных ссылок
62
Отображение View
• AttachedProperty (Prism, Caliburn.Micro)
<ContentControl regions:RegionManager.RegionName=“MainRegion“ />
• Xaml Extension (MugenMvvmToolkit)
<ContentPresenter Content="{ViewModelToViewBinding Path=ViewModel}" />
• Наследник ContentControl (ViewModelPresenter)
63
ViewModelPresenter: 1 экран кода
public class ViewModelPresenter : ContentControl
{
public static readonly DependencyProperty ViewModel …
private void OnViewModelChanged()
{
var viewTypeResolver =
ServiceLocator.Current.GetInstance<IViewTypeResolver>();
var view = viewTypeResolver.ResolveViewType(ViewModel);
view.DataContext = ViewModel;
Content = view;
}
}
64
ViewModelPresenter: 1 экран кода
public class ViewModelPresenter : ContentControl
{
public static readonly DependencyProperty ViewModel …
private void OnViewModelChanged()
{
var viewTypeResolver =
ServiceLocator.Current.GetInstance<IViewTypeResolver>();
var view = viewTypeResolver.ResolveViewType(ViewModel);
view.DataContext = ViewModel;
Content = view;
}
}
65
ViewModelPresenter: 1 экран кода
public class ViewModelPresenter : ContentControl
{
public static readonly DependencyProperty ViewModel …
private void OnViewModelChanged()
{
var viewTypeResolver =
ServiceLocator.Current.GetInstance<IViewTypeResolver>();
var view = viewTypeResolver.ResolveViewType(ViewModel);
view.DataContext = ViewModel;
Content = view;
}
}
66
ViewModelPresenter: 1 экран кода
public class ViewModelPresenter : ContentControl
{
public static readonly DependencyProperty ViewModel …
private void OnViewModelChanged()
{
var viewTypeResolver =
ServiceLocator.Current.GetInstance<IViewTypeResolver>();
var view = viewTypeResolver.ResolveViewType(ViewModel);
view.DataContext = ViewModel;
Content = view;
}
}
67
ViewModelPresenter: 1 экран кода
public class ViewModelPresenter : ContentControl
{
public static readonly DependencyProperty ViewModel …
private void OnViewModelChanged()
{
var viewTypeResolver =
ServiceLocator.Current.GetInstance<IViewTypeResolver>();
var view = viewTypeResolver.ResolveViewType(ViewModel);
view.DataContext = ViewModel;
Content = view;
}
}
68
ViewModelPresenter: 1 экран кода
public class ViewModelPresenter : ContentControl
{
public static readonly DependencyProperty ViewModel …
private void OnViewModelChanged()
{
var viewTypeResolver =
ServiceLocator.Current.GetInstance<IViewTypeResolver>();
var view = viewTypeResolver.ResolveViewType(ViewModel);
view.DataContext = ViewModel;
Content = view;
}
}
69
ViewModelPresenter: 1 экран кода
public class ViewModelPresenter : ContentControl
{
public static readonly DependencyProperty ViewModel …
private void OnViewModelChanged()
{
var viewTypeResolver =
ServiceLocator.Current.GetInstance<IViewTypeResolver>();
var view = viewTypeResolver.ResolveViewType(ViewModel);
view.DataContext = ViewModel;
Content = view;
}
}
70
ViewSwitching для UI Composition (Caliburn)
<Grid>
<ContentControl
x:Name=“MasterViewModel"
Grid.Column="0" />
<ContentControl
x:Name="DetailViewModel"
Grid.Column="1" />
</Grid>
71
ShellView ViewModel-First
<Grid>
<ViewModelPresenter
ViewModel="{Binding MasterViewModel}"
Grid.Column="0" />
<ViewModelPresenter
ViewModel="{Binding DetailViewModel}"
Grid.Column="1" />
</Grid>
72
View-Switching
• Отдельная задача от Navigation
• Табы
• MDI
• Отдельная задача от UI Composition
• UI Composition не требует менять UI на рантайме
73
UI Composition без MessageBus
• Разделить задачи View-Switching Navigation и UI Composition
• Не использовать ViewSwitching View-First для UI Composition
• RegionManager из Prism
• Можно использовать ViewSwitching ViewModel-First для UI Composition
• Caliburn.Micro
• MugenMvvmToolkit
• UI Composition без ViewSwitching (ViewModel в DataContext для View)
• MVVM без View-Switching (Prism.Windows)
• Если нужен ViewSwitching для UI Composition
• Отдельный View-ViewModel маппинг
• ViewModelPresenter
74
Задача 2: Глобальные события
• Изменение контекста
• Изменение этапов Lifecycle
75
Контекст: Log in/ Log out
76
Контекст: организация
77
Контекст: настройка
78
Этапы Lifecycle: Suspend, Resume
Suspended AppRunning App
Suspending
Resuming
79
Изменение контекста: Как обойтись попроще
• Частное решение вместо MessageBus
• Обычные события вместо широковещательных
80
ApplicationStateManager
public enum ApplicationState
{
LoggingIn,
Initializing,
Active,
Terminating,
}
public class ApplicationStateChangedEventArgs : EventArgs
{
public ApplicationState OldState { get; set; }
public ApplicationState NewState { get; set; }
}
81
ApplicationStateManager
public enum ApplicationState
{
LoggingIn,
Initializing,
Active,
Terminating,
}
public class ApplicationStateChangedEventArgs : EventArgs
{
public ApplicationState OldState { get; set; }
public ApplicationState NewState { get; set; }
}
82
ApplicationStateManager
public enum ApplicationState
{
LoggingIn,
Initializing,
Active,
Terminating,
}
public class ApplicationStateChangedEventArgs : EventArgs
{
public ApplicationState OldState { get; set; }
public ApplicationState NewState { get; set; }
}
83
ApplicationStateManager
public enum ApplicationState
{
LoggingIn,
Initializing,
Active,
Terminating,
}
public class ApplicationStateChangedEventArgs : EventArgs
{
public ApplicationState OldState { get; set; }
public ApplicationState NewState { get; set; }
}
84
Обычные события вместо широковещательных
public class ApplicationStateManager
{
public event EventHandler<ApplicationStateChangedEventArgs> ApplicationStateChanged;
public ApplicationState ApplicationState
{
get { return _applicationState; }
set
{
var oldValue = _applicationState;
_applicationState = value;
OnApplicationStateChanged(oldValue, _applicationState);
}
}
}
85
Обычные события вместо широковещательных
public class ApplicationStateManager
{
public event EventHandler<ApplicationStateChangedEventArgs> ApplicationStateChanged;
public ApplicationState ApplicationState
{
get { return _applicationState; }
set
{
var oldValue = _applicationState;
_applicationState = value;
OnApplicationStateChanged(oldValue, _applicationState);
}
}
}
86
Обычные события вместо широковещательных
public class ApplicationStateManager
{
public event EventHandler<ApplicationStateChangedEventArgs> ApplicationStateChanged;
public ApplicationState ApplicationState
{
get { return _applicationState; }
set
{
var oldValue = _applicationState;
_applicationState = value;
OnApplicationStateChanged(oldValue, _applicationState);
}
}
}
87
Обычные события вместо широковещательных
public class ApplicationStateManager
{
public event EventHandler<ApplicationStateChangedEventArgs> ApplicationStateChanged;
public ApplicationState ApplicationState
{
get { return _applicationState; }
set
{
var oldValue = _applicationState;
_applicationState = value;
OnApplicationStateChanged(oldValue, _applicationState);
}
}
}
88
ApplicationStateManager vs MessageBus
Частных решений не будет много (у нас - одно)
ApplicationStateManager MessageBus
Подписчик Любой Любой
Отправитель ApplicationStateManager Любой
Событие ApplicationStateChanged Любое
89
Изменение контекста: Как обойтись лучше
Цепочка вызовов методов
90
Пример цепочки вызовов методов
Package Prism.Windows
Класс PrismApplication.cs
Метод OnSuspending
https://github.com/PrismLibrary/Prism/blob/master/Source/Windows10/Prism.Windows/PrismApplication.cs
91
Цепочка вызовов vs частное решение
Цепочка вызовов
+ Не требует генерации событий
- Нужна продуманная архитектура
Частное решение
+ Их не будет много
- Хочется заменить частное на универсальное (MessageBus)
92
Костыли – Messages межу уровнями
View
ViewModel
Logic
93
Фокус в поле при нажатии Add
94
Message между ViewModel и View
MessageBus
DetailViewModel DetailView
UserChanged Focus Focus
95
DetailViewModel
public class DetailViewModel : IHandle<UserChangedMessage>
{
public User User { get; set; }
public void Handle(UserChangedMessage message)
{
User = message.User;
if (User?.IsNew)
{
EventAggregator.PublishOnUIThread(new FocusMessage());
}
}
}
96
DetailViewModel
public class DetailViewModel : IHandle<UserChangedMessage>
{
public User User { get; set; }
public void Handle(UserChangedMessage message)
{
User = message.User;
if (User?.IsNew)
{
EventAggregator.PublishOnUIThread(new FocusMessage());
}
}
}
97
DetailViewModel
public class DetailViewModel : IHandle<UserChangedMessage>
{
public User User { get; set; }
public void Handle(UserChangedMessage message)
{
User = message.User;
if (User?.IsNew)
{
EventAggregator.PublishOnUIThread(new FocusMessage());
}
}
}
98
DetailView
public partial class DetailView : UserControl, IHandle<FocusMessage>
{
public void Handle(FocusMessage message)
{
FirstNameTextBox.Focus();
}
}
99
DetailView
public partial class DetailView : UserControl, IHandle<FocusMessage>
{
public void Handle(FocusMessage message)
{
FirstNameTextBox.Focus();
}
}
100
Как обойтись – Behavior (или Trigger)
public class FocusNewUserBehavior : Behavior<TextBox>
{
public static readonly DependencyProperty User /* код опущен */
private static void OnUserChanged
(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = (FocusNewUserBehavior) d;
if (behavior.User?.IsNew)
{
behavior.AssociatedObject.Focus();
}
}
}
101
Как обойтись – Behavior (или Trigger)
public class FocusNewUserBehavior : Behavior<TextBox>
{
public static readonly DependencyProperty User /* код опущен */
private static void OnUserChanged
(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = (FocusNewUserBehavior) d;
if (behavior.User?.IsNew)
{
behavior.AssociatedObject.Focus();
}
}
}
102
Как обойтись – Behavior (или Trigger)
public class FocusNewUserBehavior : Behavior<TextBox>
{
public static readonly DependencyProperty User /* код опущен */
private static void OnUserChanged
(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = (FocusNewUserBehavior) d;
if (behavior.User?.IsNew)
{
behavior.AssociatedObject.Focus();
}
}
}
103
Как обойтись – Behavior (или Trigger)
public class FocusNewUserBehavior : Behavior<TextBox>
{
public static readonly DependencyProperty User /* код опущен */
private static void OnUserChanged
(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = (FocusNewUserBehavior) d;
if (behavior.User?.IsNew)
{
behavior.AssociatedObject.Focus();
}
}
}
104
Как обойтись – Behavior (или Trigger)
public class FocusNewUserBehavior : Behavior<TextBox>
{
public static readonly DependencyProperty User /* код опущен */
private static void OnUserChanged
(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = (FocusNewUserBehavior) d;
if (behavior.User?.IsNew)
{
behavior.AssociatedObject.Focus();
}
}
}
105
Как обойтись – Behavior (или Trigger)
public class FocusNewUserBehavior : Behavior<TextBox>
{
public static readonly DependencyProperty User /* код опущен */
private static void OnUserChanged
(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = (FocusNewUserBehavior) d;
if (behavior.User?.IsNew)
{
behavior.AssociatedObject.Focus();
}
}
}
106
Attach Behavior к TestBox
<TextBox Name="FirstNameTextBox"
Text="{Binding User.FirstName,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Behaviors>
<behaviors:FocusNewUserBehavior User="{Binding User}"/>
</i:Interaction.Behaviors>
</TextBox>
107
Logic-ViewModel Message – не Observable модель
• Изменение объектов кеша
• Изменение пользовательских настроек
108
Пример: Stock Trader (Prism)
109
Не Observable модели (Prism)
protected void UpdatePrice(string tickerSymbol, decimal newPrice)
{
_priceList[tickerSymbol] = newPrice;
OnPricesUpdated();
}
private void OnPricesUpdated()
{
var clonedPriceList = new Dictionary<string, decimal>(_priceList);
EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList);
}
110
Не Observable модели (Prism)
protected void UpdatePrice(string tickerSymbol, decimal newPrice)
{
_priceList[tickerSymbol] = newPrice;
OnPricesUpdated();
}
private void OnPricesUpdated()
{
var clonedPriceList = new Dictionary<string, decimal>(_priceList);
EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList);
}
111
Не Observable модели (Prism)
protected void UpdatePrice(string tickerSymbol, decimal newPrice)
{
_priceList[tickerSymbol] = newPrice;
OnPricesUpdated();
}
private void OnPricesUpdated()
{
var clonedPriceList = new Dictionary<string, decimal>(_priceList);
EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList);
}
112
Не Observable модели (Prism)
protected void UpdatePrice(string tickerSymbol, decimal newPrice)
{
_priceList[tickerSymbol] = newPrice;
OnPricesUpdated();
}
private void OnPricesUpdated()
{
var clonedPriceList = new Dictionary<string, decimal>(_priceList);
EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList);
}
113
Не Observable модели (Prism)
protected void UpdatePrice(string tickerSymbol, decimal newPrice)
{
_priceList[tickerSymbol] = newPrice;
OnPricesUpdated();
}
private void OnPricesUpdated()
{
var clonedPriceList = new Dictionary<string, decimal>(_priceList);
EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList);
}
114
Не Observable модели (Prism)
protected void UpdatePrice(string tickerSymbol, decimal newPrice)
{
_priceList[tickerSymbol] = newPrice;
OnPricesUpdated();
}
private void OnPricesUpdated()
{
var clonedPriceList = new Dictionary<string, decimal>(_priceList);
EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList);
}
115
Не Observable модели (Prism)
protected void UpdatePrice(string tickerSymbol, decimal newPrice)
{
_priceList[tickerSymbol] = newPrice;
OnPricesUpdated();
}
private void OnPricesUpdated()
{
var clonedPriceList = new Dictionary<string, decimal>(_priceList);
EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList);
}
116
Решение – Observable модель
public class ObservableDictionary<TKey, TValue>:
INotifyCollectionChanged,
INotifyPropertyChanged
{
}
https://code.msdn.microsoft.com/windowsdesktop/Samples-for-Parallel-b4b76364
117
Не Observable модели (Prism)
protected void UpdatePrice(string tickerSymbol, decimal newPrice)
{
_priceList[tickerSymbol] = newPrice;
OnPricesUpdated();
}
private void OnPricesUpdated()
{
var clonedPriceList = new Dictionary<string, decimal>(_priceList);
EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList);
}
118
Observable модель
protected void UpdatePrice(string tickerSymbol, decimal newPrice)
{
_priceList[tickerSymbol] = newPrice;
OnPricesUpdated();
}
private void OnPricesUpdated()
{
var clonedPriceList = new Dictionary<string, decimal>(_priceList);
EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList);
}
119
Как избежать Messages между уровнями
Messages
120
MVVM фреймворки без MessageBus
• Таких нет 
• В отдельном пакете
• Prism 5 – SubEvents (в Prism 6 уже неотделяем)
• MvvmCross – Messenger plugin
121
MessageBus нужно знать в лицо!
Prism – EventAggregator
Caliburn.Micro – EventAggregator
MugenMvvmToolkit - EventAggregator
MVVM Light – Messenger
ReactiveUI – MessageBus
122
Как жить без MessageBus на WPF и UWP
• Caliburn.Micro
• MugenMvvmToolkit
• MVVM без UI Composition
• + свой ViewModel-First UI Composition
123
Не позволяет жить без MessageBus
WPF
• Prism.WPF
UWP
• Таких нет 
• Prism.Windows без UI Composition
Как добавить UI Composition: https://github.com/denis-tsv/Prism.StoreApps.Extensions.Mvvm
124
MessageBus на UI
Для чего используется Как обойтись
UI Composition Разделение UI Composition и ViewSwitching
Глобальные события
(смена контекста или lifecycle)
Цепочка вызовов методов
Частные решения с обычными событиями
ViewModel-View Message Triggers, Behaviors
Logic-ViewModel Message Observable Model
Избежать Messages между уровнями Разные сборки для View, ViewModel, Logic
Messages в сборке ViewModel
125
Главная проблема MessageBus на UI – костыли!
Messages между слоями
• Messages вместо Events (Logic -> ViewModel Messages)
• Messages вместо Bindings (ViewModel -> View Messages)
126
Как можно жить с MessageBus на UI
• UI Composition в стиле View-First (Prism.WPF)
• Отдельные сборки View, ViewModel, Logic
• Messages только в ViewModel
• Глобальные Messages
127
Моя позиция
• Большинство бизнес-приложений маленькие и средние
• Команда 3-7 человек
• Срок пара месяцев – пара лет
• UI сложнее бэкэнда
• Не нужно CQRS, Microservices, DataAccess Abstraction
• MessageBus
• Ещё сильнее запутывает и так сложный UI
• Любую UI задачу можно решить без MessageBus
• Примеры Prism без EventAggregator полезная ссылка №2
• Строка «MessageBus» ломает билд  полезная ссылка №9
128
Paul Betts (ReactiveUI author) about MessageBus
… there are often
more correct ways
to solve problems,
given a bit of
ingenuity …
129
В параллельной вселенной JS
Сходства
• Fontend не уступает Backend по сложности
• Развесистые JS-фреймворки (Angular, React)
• Компонент JS = Регион XAML
• Компонент (Шаблон + Поведение)
• Регион (View + ViewModel)
130
Olical EventEmitter
var ee = new EventEmitter();
function listener() { }
ee.addListener('event', listener);
ee.emitEvent('event');
ee.removeListener('event', listener);
131
Olical EventEmitter
var ee = new EventEmitter();
function listener() { }
ee.addListener('event', listener);
ee.emitEvent('event');
ee.removeListener('event', listener);
132
Olical EventEmitter
var ee = new EventEmitter();
function listener() { }
ee.addListener('event', listener);
ee.emitEvent('event');
ee.removeListener('event', listener);
133
Angular2 EventEmitter – Observer, а не MessageBus
class EventEmitter {
constructor(isAsync?: boolean)
emit(value?: T)
subscribe(generatorOrNext?: any, error?: any, complete?: any)
: any
}
134
Angular2 EventEmitter – Observer, а не MessageBus
class EventEmitter {
constructor(isAsync?: boolean)
emit(value?: T)
subscribe(generatorOrNext?: any, error?: any, complete?: any)
: any
}
135
Angular2 EventEmitter – Observer, а не MessageBus
class EventEmitter {
constructor(isAsync?: boolean)
emit(value?: T)
subscribe(generatorOrNext?: any, error?: any, complete?: any)
: any
}
136
Angular2 EventEmitter – Observer, а не MessageBus
class EventEmitter {
constructor(isAsync?: boolean)
emit(value?: T)
subscribe(generatorOrNext?: any, error?: any, complete?: any)
: any
}
137
MasterDetail: Parent
@Component({
selector: 'my-app',
template: `
<my-hero-master [heroes]="heroes" (onHeroSelected)="onSelect($event)“ />
<my-hero-detail [hero]="selectedHero“ />
`,
})
export class AppComponent {
selectedHero: Hero;
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
}
138
MasterDetail: Parent
@Component({
selector: 'my-app',
template: `
<my-hero-master [heroes]="heroes" (onHeroSelected)="onSelect($event)“ />
<my-hero-detail [hero]="selectedHero“ />
`,
})
export class AppComponent {
selectedHero: Hero;
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
}
139
MasterDetail: Parent
@Component({
selector: 'my-app',
template: `
<my-hero-master [heroes]="heroes" (onHeroSelected)="onSelect($event)“ />
<my-hero-detail [hero]="selectedHero“ />
`,
})
export class AppComponent {
selectedHero: Hero;
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
}
140
MasterDetail: Parent
@Component({
selector: 'my-app',
template: `
<my-hero-master [heroes]="heroes" (onHeroSelected)="onSelect($event)“ />
<my-hero-detail [hero]="selectedHero“ />
`,
})
export class AppComponent {
selectedHero: Hero;
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
}
141
MasterDetail: Parent
@Component({
selector: 'my-app',
template: `
<my-hero-master [heroes]="heroes" (onHeroSelected)="onSelect($event)“ />
<my-hero-detail [hero]="selectedHero“ />
`,
})
export class AppComponent {
selectedHero: Hero;
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
}
142
MasterDetail: Master
@Component({
selector: 'my-hero-master',
template: `
<ul>
<li *ngFor="let hero of heroes“ (click)="onSelect(hero)">
{{hero.name}}
</li>
</ul> `,
})
export class HeroMasterComponent {
@Input() heroes: Hero[];
@Output() onHeroSelected = new EventEmitter<Hero>();
onSelect(hero: Hero): void {
this.onHeroSelected.emit(hero);
}
}
143
MasterDetail: Master
@Component({
selector: 'my-hero-master',
template: `
<ul>
<li *ngFor="let hero of heroes“ (click)="onSelect(hero)">
{{hero.name}}
</li>
</ul> `,
})
export class HeroMasterComponent {
@Input() heroes: Hero[];
@Output() onHeroSelected = new EventEmitter<Hero>();
onSelect(hero: Hero): void {
this.onHeroSelected.emit(hero);
}
}
144
MasterDetail: Master
@Component({
selector: 'my-hero-master',
template: `
<ul>
<li *ngFor="let hero of heroes“ (click)="onSelect(hero)">
{{hero.name}}
</li>
</ul> `,
})
export class HeroMasterComponent {
@Input() heroes: Hero[];
@Output() onHeroSelected = new EventEmitter<Hero>();
onSelect(hero: Hero): void {
this.onHeroSelected.emit(hero);
}
}
145
MasterDetail: Master
@Component({
selector: 'my-hero-master',
template: `
<ul>
<li *ngFor="let hero of heroes“ (click)="onSelect(hero)">
{{hero.name}}
</li>
</ul> `,
})
export class HeroMasterComponent {
@Input() heroes: Hero[];
@Output() onHeroSelected = new EventEmitter<Hero>();
onSelect(hero: Hero): void {
this.onHeroSelected.emit(hero);
}
}
146
MasterDetail: Detail
@Component({
selector: 'my-hero-detail',
template: `
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name"/>
</div>
`
})
export class HeroDetailComponent {
@Input() hero: Hero;
}
147
MasterDetail: Detail
@Component({
selector: 'my-hero-detail',
template: `
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name"/>
</div>
`
})
export class HeroDetailComponent {
@Input() hero: Hero;
}
148
React: Lifting State Up
149
Lifting State Up: Child
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onChange(e.target.value);
}
render() {
const value = this.props.value;
return (
<input value={value} onChange={this.handleChange} />
);
}
}
150
Lifting State Up: Child
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onChange(e.target.value);
}
render() {
const value = this.props.value;
return (
<input value={value} onChange={this.handleChange} />
);
}
}
151
Lifting State Up: Child
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onChange(e.target.value);
}
render() {
const value = this.props.value;
return (
<input value={value} onChange={this.handleChange} />
);
}
}
152
Lifting State Up: Child
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onChange(e.target.value);
}
render() {
const value = this.props.value;
return (
<input value={value} onChange={this.handleChange} />
);
}
}
153
Lifting State Up: Parent
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
}
handleCelsiusChange(value) {
this.setState({value});
}
handleFahrenheitChange(value) {
this.setState({value});
}
}
154
Lifting State Up: Parent
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
}
handleCelsiusChange(value) {
this.setState({value});
}
handleFahrenheitChange(value) {
this.setState({value});
}
}
155
Lifting State Up: Parent
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
}
handleCelsiusChange(value) {
this.setState({value});
}
handleFahrenheitChange(value) {
this.setState({value});
}
}
156
Lifting State Up: Parent
render() {
const value = this.state.value;
const celsius = Convert(value, toCelsius);
const fahrenheit = Convert(value, toFahrenheit);
return (
<div>
<TemperatureInput value={celsius} onChange={this.handleCelsiusChange} />
<TemperatureInput value={fahrenheit} onChange={this.handleFahrenheitChange} />
</div>
);
}
157
Lifting State Up: Parent
render() {
const value = this.state.value;
const celsius = Convert(value, toCelsius);
const fahrenheit = Convert(value, toFahrenheit);
return (
<div>
<TemperatureInput value={celsius} onChange={this.handleCelsiusChange} />
<TemperatureInput value={fahrenheit} onChange={this.handleFahrenheitChange} />
</div>
);
}
158
Lifting State Up: Parent
render() {
const value = this.state.value;
const celsius = Convert(value, toCelsius);
const fahrenheit = Convert(value, toFahrenheit);
return (
<div>
<TemperatureInput value={celsius} onChange={this.handleCelsiusChange} />
<TemperatureInput value={fahrenheit} onChange={this.handleFahrenheitChange} />
</div>
);
}
159
React: Context
160
Context: Child
class TemperatureInput extends React.Component {
render() {
return (
<input style={{background: this.context.color}} />
);
}
}
TemperatureInput.contextTypes = {
color: React.PropTypes.string
};
161
Context: Child
class TemperatureInput extends React.Component {
render() {
return (
<input style={{background: this.context.color}} />
);
}
}
TemperatureInput.contextTypes = {
color: React.PropTypes.string
};
162
Context: Child
class TemperatureInput extends React.Component {
render() {
return (
<input style={{background: this.context.color}} />
);
}
}
TemperatureInput.contextTypes = {
color: React.PropTypes.string
};
163
Context: Parent
class Calculator extends React.Component {
getChildContext() {
return {color: this.state.color};
}
handleCelsiusChange(value) {
this.setState({color : 'green'});
}
}
Calculator.childContextTypes = {
color: React.PropTypes.string
};
164
Context: Parent
class Calculator extends React.Component {
getChildContext() {
return {color: this.state.color};
}
handleCelsiusChange(value) {
this.setState({color : 'green'});
}
}
Calculator.childContextTypes = {
color: React.PropTypes.string
};
165
Context: Parent
class Calculator extends React.Component {
getChildContext() {
return {color: this.state.color};
}
handleCelsiusChange(value) {
this.setState({color : 'green'});
}
}
Calculator.childContextTypes = {
color: React.PropTypes.string
};
166
Context: Parent
class Calculator extends React.Component {
getChildContext() {
return {color: this.state.color};
}
handleCelsiusChange(value) {
this.setState({color : 'green'});
}
}
Calculator.childContextTypes = {
color: React.PropTypes.string
};
167
React Context – не MessageBus
Задача
• Передача от родителя к вложенным компонентам
• С уровня 1 на уровень 3 минуя уровень 2
Особенности реализации
• Доступен родительскому и вложенным компонентам (не любым
компонентам)
• Обновление контекста во вложенных компонентах не работает
Это не MessageBus 
168
Резюме: MessageBus на JS
• MessageBus существует, но в виде отдельных библиотек
• MessageBus нет в коробке флагманов Angular2 и React
• Официальный гайд не рекомендует MesageBus
• Компонент != ViewModel
• Компонент = Шаблон + Поведение
• ViewModel = Поведение
• На JS нет разных видов композиции ViewFirst и ViewModelFirst
• Для JS MessageBus не является проблемой, как в XAML
• Продуманная архитектура JS фреймворков – аргумент за
переписывание UI на web-стек 
169
Полезные ссылки
1. MessageBus, врага нужно знать в лицо
https://msdn.microsoft.com/en-us/library/ff647328.aspx
2. Примеры StockTrader и UIComposition для Prism.WPF, откуда выпилен EventAggregator
https://github.com/denis-tsv/Prism-Samples-Wpf-NoEventAggregetor
3. Как добавить UI Composition для Prism.Windows (UWP)
https://github.com/denis-tsv/Prism.StoreApps.Extensions.Mvvm
4. Paul Betts. MessageBus and why you shouldn't use it
http://log.paulbetts.org/messagebus-and-why-you-shouldnt-use-it/
5, Caliburn.Micro Классный, минималистичный, кроссплатформенный и мультипарадигменный MVVM, в котором нет ничего лишнего
http://caliburnmicro.com/
6. MugenMvvmToolkit. Мощный UI фреймворк с офигительными кастомными XAML биндингами
https://github.com/MugenMvvmToolkit
https://habrahabr.ru/post/236745/
7. ReactiveUI. Биндинг свойств одной ViewModel на свойства другой ViewModel в родительской ViewModel
http://reactiveui.net/
8. Пример View-ViewModel mapping с использованием MEF
http://www.intuit.ru/EDI/14_12_14_2/1418505481-18353/tutorial/1050/objects/1/files/SampleSolution.zip
9. Как сломать билд (о, да!) словом MessageBus, EventAggregator, Messenger и любой другой реализацией MessageBus
http://hmemcpy.com/2013/03/even-more-resharper-annotations-hackery-marking-3rd-party-code-as-obsolete/
170
Спасибо за внимание
Вопросы?
Денис Цветцих
den.tsvettsih@yandex.ru

Weitere ähnliche Inhalte

Was ist angesagt?

CodeFest 2011. Крестьянинов М. — Обзор аспектно-ориентированного программиров...
CodeFest 2011. Крестьянинов М. — Обзор аспектно-ориентированного программиров...CodeFest 2011. Крестьянинов М. — Обзор аспектно-ориентированного программиров...
CodeFest 2011. Крестьянинов М. — Обзор аспектно-ориентированного программиров...
CodeFest
 
МРТ для данных, Frontend Conf 2016
МРТ для данных, Frontend Conf 2016МРТ для данных, Frontend Conf 2016
МРТ для данных, Frontend Conf 2016
Anastasia Goryacheva
 
Радости и гадости регрессионного тестирования вёрстки / Алексей Малейков (HTM...
Радости и гадости регрессионного тестирования вёрстки / Алексей Малейков (HTM...Радости и гадости регрессионного тестирования вёрстки / Алексей Малейков (HTM...
Радости и гадости регрессионного тестирования вёрстки / Алексей Малейков (HTM...
Ontico
 
Школа-студия разработки приложений для iOS. 2 лекция. MVC, View, Controllers
Школа-студия разработки приложений для iOS. 2 лекция. MVC, View, ControllersШкола-студия разработки приложений для iOS. 2 лекция. MVC, View, Controllers
Школа-студия разработки приложений для iOS. 2 лекция. MVC, View, Controllers
Глеб Тарасов
 

Was ist angesagt? (20)

«Как я научился не волноваться и полюбил Android-MVP», Никита Бартишок, ABBYY
«Как я научился не волноваться и полюбил Android-MVP», Никита Бартишок, ABBYY«Как я научился не волноваться и полюбил Android-MVP», Никита Бартишок, ABBYY
«Как я научился не волноваться и полюбил Android-MVP», Никита Бартишок, ABBYY
 
Moxy. Из чего состоит и как этим пользоваться
Moxy. Из чего состоит и как этим пользоватьсяMoxy. Из чего состоит и как этим пользоваться
Moxy. Из чего состоит и как этим пользоваться
 
Gwt jug basic
Gwt jug basicGwt jug basic
Gwt jug basic
 
«Buzzwords everywhere, или Борьба с ветряными мельницами», Артём Дроздов, Mai...
«Buzzwords everywhere, или Борьба с ветряными мельницами», Артём Дроздов, Mai...«Buzzwords everywhere, или Борьба с ветряными мельницами», Артём Дроздов, Mai...
«Buzzwords everywhere, или Борьба с ветряными мельницами», Артём Дроздов, Mai...
 
HTML 5: будущее уже сегодня, Сергей Байдачный, Microsoft Ukraine
HTML 5: будущее уже сегодня, Сергей Байдачный, Microsoft UkraineHTML 5: будущее уже сегодня, Сергей Байдачный, Microsoft Ukraine
HTML 5: будущее уже сегодня, Сергей Байдачный, Microsoft Ukraine
 
Windows Azure and node js
Windows Azure and node jsWindows Azure and node js
Windows Azure and node js
 
Vue.js и его брат-близнец Vue-server.js / Андрей Солодовников (НГС)
Vue.js и его брат-близнец Vue-server.js / Андрей Солодовников (НГС)Vue.js и его брат-близнец Vue-server.js / Андрей Солодовников (НГС)
Vue.js и его брат-близнец Vue-server.js / Андрей Солодовников (НГС)
 
55+1 прием для улучшения Javascript-кода / Татьяна Бабич (Simbirsoft)
55+1 прием для улучшения Javascript-кода / Татьяна Бабич (Simbirsoft)55+1 прием для улучшения Javascript-кода / Татьяна Бабич (Simbirsoft)
55+1 прием для улучшения Javascript-кода / Татьяна Бабич (Simbirsoft)
 
CodeFest 2011. Крестьянинов М. — Обзор аспектно-ориентированного программиров...
CodeFest 2011. Крестьянинов М. — Обзор аспектно-ориентированного программиров...CodeFest 2011. Крестьянинов М. — Обзор аспектно-ориентированного программиров...
CodeFest 2011. Крестьянинов М. — Обзор аспектно-ориентированного программиров...
 
МРТ для данных, Frontend Conf 2016
МРТ для данных, Frontend Conf 2016МРТ для данных, Frontend Conf 2016
МРТ для данных, Frontend Conf 2016
 
Enterprise flex pure mvc, slides, russian
Enterprise flex pure mvc, slides, russianEnterprise flex pure mvc, slides, russian
Enterprise flex pure mvc, slides, russian
 
Лекция 2. Activity.
Лекция 2. Activity.Лекция 2. Activity.
Лекция 2. Activity.
 
JavaScript Базовый. Занятие 09.
JavaScript Базовый. Занятие 09.JavaScript Базовый. Занятие 09.
JavaScript Базовый. Занятие 09.
 
Радости и гадости регрессионного тестирования вёрстки / Алексей Малейков (HTM...
Радости и гадости регрессионного тестирования вёрстки / Алексей Малейков (HTM...Радости и гадости регрессионного тестирования вёрстки / Алексей Малейков (HTM...
Радости и гадости регрессионного тестирования вёрстки / Алексей Малейков (HTM...
 
Лекция Android. Fragments, ActionBar, Drawer
Лекция Android. Fragments, ActionBar, DrawerЛекция Android. Fragments, ActionBar, Drawer
Лекция Android. Fragments, ActionBar, Drawer
 
#12 "Создание двух desktop приложений на node-webkit и Electron” Михаил Реенко
#12 "Создание двух desktop приложений на node-webkit и Electron” Михаил Реенко#12 "Создание двух desktop приложений на node-webkit и Electron” Михаил Реенко
#12 "Создание двух desktop приложений на node-webkit и Electron” Михаил Реенко
 
Как мы разрабатываем новый фронтенд / Филипп Нехаев (Tinkoff.ru)
Как мы разрабатываем новый фронтенд / Филипп Нехаев (Tinkoff.ru)Как мы разрабатываем новый фронтенд / Филипп Нехаев (Tinkoff.ru)
Как мы разрабатываем новый фронтенд / Филипп Нехаев (Tinkoff.ru)
 
Fragments, ActionBar, Drawer
Fragments, ActionBar, DrawerFragments, ActionBar, Drawer
Fragments, ActionBar, Drawer
 
Обзор возможностей HTML5
Обзор возможностей HTML5Обзор возможностей HTML5
Обзор возможностей HTML5
 
Школа-студия разработки приложений для iOS. 2 лекция. MVC, View, Controllers
Школа-студия разработки приложений для iOS. 2 лекция. MVC, View, ControllersШкола-студия разработки приложений для iOS. 2 лекция. MVC, View, Controllers
Школа-студия разработки приложений для iOS. 2 лекция. MVC, View, Controllers
 

Andere mochten auch

Control inalámbrico para sistemas eléctricos 2013 2.jpg
Control inalámbrico para sistemas eléctricos 2013 2.jpgControl inalámbrico para sistemas eléctricos 2013 2.jpg
Control inalámbrico para sistemas eléctricos 2013 2.jpg
Alvaro Romero Acero
 
Presentación a identidade dixital
Presentación    a identidade dixitalPresentación    a identidade dixital
Presentación a identidade dixital
cintia.vazquez
 
La guerra civil española (copia en conflicto de felipe 2013 05-19 (1))
La guerra civil española (copia en conflicto de felipe 2013 05-19 (1))La guerra civil española (copia en conflicto de felipe 2013 05-19 (1))
La guerra civil española (copia en conflicto de felipe 2013 05-19 (1))
Felipe Retamal
 

Andere mochten auch (20)

Разработка Windows 8 приложений глазами WPF/Silverlight программиста
Разработка Windows 8 приложений глазами WPF/Silverlight программистаРазработка Windows 8 приложений глазами WPF/Silverlight программиста
Разработка Windows 8 приложений глазами WPF/Silverlight программиста
 
Roslyn API : SyntaxTree vs CodeDom, SemanticModel vs Reflection
Roslyn API: SyntaxTree vs CodeDom, SemanticModel vs ReflectionRoslyn API: SyntaxTree vs CodeDom, SemanticModel vs Reflection
Roslyn API : SyntaxTree vs CodeDom, SemanticModel vs Reflection
 
Эффективные email коммуникации
Эффективные email коммуникацииЭффективные email коммуникации
Эффективные email коммуникации
 
Control inalámbrico para sistemas eléctricos 2013 2.jpg
Control inalámbrico para sistemas eléctricos 2013 2.jpgControl inalámbrico para sistemas eléctricos 2013 2.jpg
Control inalámbrico para sistemas eléctricos 2013 2.jpg
 
Galápagos
GalápagosGalápagos
Galápagos
 
Moda Tasarimcilari
Moda TasarimcilariModa Tasarimcilari
Moda Tasarimcilari
 
Trankrip
TrankripTrankrip
Trankrip
 
Cançó d'abril
Cançó d'abrilCançó d'abril
Cançó d'abril
 
Conservación del medio ambiente
Conservación del medio ambienteConservación del medio ambiente
Conservación del medio ambiente
 
10 rosas
10 rosas10 rosas
10 rosas
 
Aprendamos con nuestros sentidos
Aprendamos con nuestros sentidosAprendamos con nuestros sentidos
Aprendamos con nuestros sentidos
 
Presentación a identidade dixital
Presentación    a identidade dixitalPresentación    a identidade dixital
Presentación a identidade dixital
 
490B report
490B report490B report
490B report
 
The difference between the Small Business Owner Mindset and the Entrepreneuri...
The difference between the Small Business Owner Mindset and the Entrepreneuri...The difference between the Small Business Owner Mindset and the Entrepreneuri...
The difference between the Small Business Owner Mindset and the Entrepreneuri...
 
La guerra civil española (copia en conflicto de felipe 2013 05-19 (1))
La guerra civil española (copia en conflicto de felipe 2013 05-19 (1))La guerra civil española (copia en conflicto de felipe 2013 05-19 (1))
La guerra civil española (copia en conflicto de felipe 2013 05-19 (1))
 
Distocias de hombro y pelviana
Distocias de hombro y pelvianaDistocias de hombro y pelviana
Distocias de hombro y pelviana
 
Catéter venoso central, punción subclavia y venodisecciones
Catéter venoso central, punción subclavia y venodiseccionesCatéter venoso central, punción subclavia y venodisecciones
Catéter venoso central, punción subclavia y venodisecciones
 
Mapa conceptual. Conservación del Ambiente
Mapa conceptual. Conservación del  Ambiente Mapa conceptual. Conservación del  Ambiente
Mapa conceptual. Conservación del Ambiente
 
Digital B2B Inbound Content Marketing Excellence -Zaheer Nooruddin GOLIN asia
Digital B2B Inbound Content Marketing Excellence -Zaheer Nooruddin GOLIN asiaDigital B2B Inbound Content Marketing Excellence -Zaheer Nooruddin GOLIN asia
Digital B2B Inbound Content Marketing Excellence -Zaheer Nooruddin GOLIN asia
 
Capacitación Repartidores.
Capacitación Repartidores.Capacitación Repartidores.
Capacitación Repartidores.
 

Ähnlich wie Как написать XAML-приложение без Message Bus

Управление зависимостями в программном коде
Управление зависимостями в программном кодеУправление зависимостями в программном коде
Управление зависимостями в программном коде
Dmitrey Naumenko
 
Разработка Web-приложений на Angular JS. Архитектурные семинары Softengi
Разработка Web-приложений на Angular JS. Архитектурные семинары SoftengiРазработка Web-приложений на Angular JS. Архитектурные семинары Softengi
Разработка Web-приложений на Angular JS. Архитектурные семинары Softengi
Softengi
 
Java осень 2012 лекция 5
Java осень 2012 лекция 5Java осень 2012 лекция 5
Java осень 2012 лекция 5
Technopark
 
белогорцев глеб белогорцев
белогорцев глеб белогорцевбелогорцев глеб белогорцев
белогорцев глеб белогорцев
rit2010
 

Ähnlich wie Как написать XAML-приложение без Message Bus (20)

Модульная структура. Цветцих Денис D2D Just.NET
Модульная структура. Цветцих Денис D2D Just.NETМодульная структура. Цветцих Денис D2D Just.NET
Модульная структура. Цветцих Денис D2D Just.NET
 
Антон Валюх - Использование паттерна Mvvm в android
Антон Валюх - Использование паттерна Mvvm в androidАнтон Валюх - Использование паттерна Mvvm в android
Антон Валюх - Использование паттерна Mvvm в android
 
IT-инфраструктура. FAQ для разработчика
IT-инфраструктура. FAQ для разработчикаIT-инфраструктура. FAQ для разработчика
IT-инфраструктура. FAQ для разработчика
 
#3 "Webpack и Vue.JS: Создание больших приложений и их расширение" Кирилл Кай...
#3 "Webpack и Vue.JS: Создание больших приложений и их расширение" Кирилл Кай...#3 "Webpack и Vue.JS: Создание больших приложений и их расширение" Кирилл Кай...
#3 "Webpack и Vue.JS: Создание больших приложений и их расширение" Кирилл Кай...
 
Всеволод Шмыров, Яндекс
Всеволод Шмыров, ЯндексВсеволод Шмыров, Яндекс
Всеволод Шмыров, Яндекс
 
Как навести порядок в коде вашего web-приложения, Андрей Чебукин
Как навести порядок в коде вашего web-приложения, Андрей Чебукин Как навести порядок в коде вашего web-приложения, Андрей Чебукин
Как навести порядок в коде вашего web-приложения, Андрей Чебукин
 
JavaScript-модули "из прошлого в будущее"
JavaScript-модули "из прошлого в будущее"JavaScript-модули "из прошлого в будущее"
JavaScript-модули "из прошлого в будущее"
 
Android service
Android serviceAndroid service
Android service
 
Михаил Давыдов "Масштабируемые JavaScript-приложения"
Михаил Давыдов "Масштабируемые JavaScript-приложения"Михаил Давыдов "Масштабируемые JavaScript-приложения"
Михаил Давыдов "Масштабируемые JavaScript-приложения"
 
Frontend: Путешествие в мир модульных загрузчиков
Frontend: Путешествие в мир модульных загрузчиковFrontend: Путешествие в мир модульных загрузчиков
Frontend: Путешествие в мир модульных загрузчиков
 
Управление зависимостями в программном коде
Управление зависимостями в программном кодеУправление зависимостями в программном коде
Управление зависимостями в программном коде
 
Разработка Web-приложений на Angular JS. Архитектурные семинары Softengi
Разработка Web-приложений на Angular JS. Архитектурные семинары SoftengiРазработка Web-приложений на Angular JS. Архитектурные семинары Softengi
Разработка Web-приложений на Angular JS. Архитектурные семинары Softengi
 
Михаил Давыдов "Масштабируемые JavaScript-приложения"
Михаил Давыдов "Масштабируемые JavaScript-приложения"Михаил Давыдов "Масштабируемые JavaScript-приложения"
Михаил Давыдов "Масштабируемые JavaScript-приложения"
 
Viper - чистая архитектура iOS-приложения (И. Чирков)
Viper - чистая архитектура iOS-приложения (И. Чирков)Viper - чистая архитектура iOS-приложения (И. Чирков)
Viper - чистая архитектура iOS-приложения (И. Чирков)
 
Архитектура кода нового 2ГИС Web API или куда мы дели MVC
Архитектура кода нового 2ГИС Web API или куда мы дели MVCАрхитектура кода нового 2ГИС Web API или куда мы дели MVC
Архитектура кода нового 2ГИС Web API или куда мы дели MVC
 
2016-08-20 02 Антон Ковалев, Антон Кормаков. Viper. Чистая архитектура для iOS
2016-08-20 02 Антон Ковалев, Антон Кормаков. Viper. Чистая архитектура для iOS2016-08-20 02 Антон Ковалев, Антон Кормаков. Viper. Чистая архитектура для iOS
2016-08-20 02 Антон Ковалев, Антон Кормаков. Viper. Чистая архитектура для iOS
 
JavaScript Design Patterns overview by Ksenia Redunova
JavaScript Design Patterns overview by Ksenia RedunovaJavaScript Design Patterns overview by Ksenia Redunova
JavaScript Design Patterns overview by Ksenia Redunova
 
Java осень 2012 лекция 5
Java осень 2012 лекция 5Java осень 2012 лекция 5
Java осень 2012 лекция 5
 
Backbone lesson 1
Backbone lesson 1Backbone lesson 1
Backbone lesson 1
 
белогорцев глеб белогорцев
белогорцев глеб белогорцевбелогорцев глеб белогорцев
белогорцев глеб белогорцев
 

Kürzlich hochgeladen

Cyber Defense Doctrine Managing the Risk Full Applied Guide to Organizational...
Cyber Defense Doctrine Managing the Risk Full Applied Guide to Organizational...Cyber Defense Doctrine Managing the Risk Full Applied Guide to Organizational...
Cyber Defense Doctrine Managing the Risk Full Applied Guide to Organizational...
Ирония безопасности
 
ИСТОЧНИКИ ИННОВАЦИОННОСТИ КИТАЯ (ПО ВЕРСИИ DGAP) | The Sources of China’s Inn...
ИСТОЧНИКИ ИННОВАЦИОННОСТИ КИТАЯ (ПО ВЕРСИИ DGAP) | The Sources of China’s Inn...ИСТОЧНИКИ ИННОВАЦИОННОСТИ КИТАЯ (ПО ВЕРСИИ DGAP) | The Sources of China’s Inn...
ИСТОЧНИКИ ИННОВАЦИОННОСТИ КИТАЯ (ПО ВЕРСИИ DGAP) | The Sources of China’s Inn...
Ирония безопасности
 
2023 Q4. The Ransomware report. [RU].pdf
2023 Q4. The Ransomware report. [RU].pdf2023 Q4. The Ransomware report. [RU].pdf
2023 Q4. The Ransomware report. [RU].pdf
Хроники кибер-безопасника
 
СИСТЕМА ОЦЕНКИ УЯЗВИМОСТЕЙ CVSS 4.0 / CVSS v4.0 [RU].pdf
СИСТЕМА ОЦЕНКИ УЯЗВИМОСТЕЙ CVSS 4.0 / CVSS v4.0 [RU].pdfСИСТЕМА ОЦЕНКИ УЯЗВИМОСТЕЙ CVSS 4.0 / CVSS v4.0 [RU].pdf
СИСТЕМА ОЦЕНКИ УЯЗВИМОСТЕЙ CVSS 4.0 / CVSS v4.0 [RU].pdf
Хроники кибер-безопасника
 
Cyberprint. Dark Pink Apt Group [RU].pdf
Cyberprint. Dark Pink Apt Group [RU].pdfCyberprint. Dark Pink Apt Group [RU].pdf
Cyberprint. Dark Pink Apt Group [RU].pdf
Хроники кибер-безопасника
 
CVE. The Fortra's GoAnywhere MFT [RU].pdf
CVE. The Fortra's GoAnywhere MFT [RU].pdfCVE. The Fortra's GoAnywhere MFT [RU].pdf
CVE. The Fortra's GoAnywhere MFT [RU].pdf
Хроники кибер-безопасника
 

Kürzlich hochgeladen (9)

Cyber Defense Doctrine Managing the Risk Full Applied Guide to Organizational...
Cyber Defense Doctrine Managing the Risk Full Applied Guide to Organizational...Cyber Defense Doctrine Managing the Risk Full Applied Guide to Organizational...
Cyber Defense Doctrine Managing the Risk Full Applied Guide to Organizational...
 
ИСТОЧНИКИ ИННОВАЦИОННОСТИ КИТАЯ (ПО ВЕРСИИ DGAP) | The Sources of China’s Inn...
ИСТОЧНИКИ ИННОВАЦИОННОСТИ КИТАЯ (ПО ВЕРСИИ DGAP) | The Sources of China’s Inn...ИСТОЧНИКИ ИННОВАЦИОННОСТИ КИТАЯ (ПО ВЕРСИИ DGAP) | The Sources of China’s Inn...
ИСТОЧНИКИ ИННОВАЦИОННОСТИ КИТАЯ (ПО ВЕРСИИ DGAP) | The Sources of China’s Inn...
 
2023 Q4. The Ransomware report. [RU].pdf
2023 Q4. The Ransomware report. [RU].pdf2023 Q4. The Ransomware report. [RU].pdf
2023 Q4. The Ransomware report. [RU].pdf
 
Malware. DCRAT (DARK CRYSTAL RAT) [RU].pdf
Malware. DCRAT (DARK CRYSTAL RAT) [RU].pdfMalware. DCRAT (DARK CRYSTAL RAT) [RU].pdf
Malware. DCRAT (DARK CRYSTAL RAT) [RU].pdf
 
СИСТЕМА ОЦЕНКИ УЯЗВИМОСТЕЙ CVSS 4.0 / CVSS v4.0 [RU].pdf
СИСТЕМА ОЦЕНКИ УЯЗВИМОСТЕЙ CVSS 4.0 / CVSS v4.0 [RU].pdfСИСТЕМА ОЦЕНКИ УЯЗВИМОСТЕЙ CVSS 4.0 / CVSS v4.0 [RU].pdf
СИСТЕМА ОЦЕНКИ УЯЗВИМОСТЕЙ CVSS 4.0 / CVSS v4.0 [RU].pdf
 
Ransomware_Q3 2023. The report [RU].pdf
Ransomware_Q3 2023.  The report [RU].pdfRansomware_Q3 2023.  The report [RU].pdf
Ransomware_Q3 2023. The report [RU].pdf
 
Cyberprint. Dark Pink Apt Group [RU].pdf
Cyberprint. Dark Pink Apt Group [RU].pdfCyberprint. Dark Pink Apt Group [RU].pdf
Cyberprint. Dark Pink Apt Group [RU].pdf
 
CVE. The Fortra's GoAnywhere MFT [RU].pdf
CVE. The Fortra's GoAnywhere MFT [RU].pdfCVE. The Fortra's GoAnywhere MFT [RU].pdf
CVE. The Fortra's GoAnywhere MFT [RU].pdf
 
MS Navigating Incident Response [RU].pdf
MS Navigating Incident Response [RU].pdfMS Navigating Incident Response [RU].pdf
MS Navigating Incident Response [RU].pdf
 

Как написать XAML-приложение без Message Bus

  • 1. Как разрабатывать UI XAML приложений (Prism) и веб-приложений (Angular2, React) без Message Bus Денис Цветцих One Systems DotNext Msk 9 декабря 2016 http://dotnext-moscow.ru/
  • 2. 2 Message Bus – паттерн для интеграции MessageBus Application 1 Application 2 Application 3 Application 4
  • 3. 3 Чем плох MessageBus на UI • Взаимодействие объектов неявное • Сложно дебажить • В больших проектах MessageHell • Простор для костылей • Messages вместо Events • Messages вместо Bindings
  • 5. 5 Виноват Composite Application Blocks public class MasterController : Controller { [EventPublication("GlobalEvent")] public event EventHandler GlobalEvent; } public class DetailController : Controller { [EventSubscription("GlobalEvent")] public void OnGlobalEvent(object sender, EventArgs e) { } }
  • 6. 6 Виноват Composite Application Blocks public class MasterController : Controller { [EventPublication("GlobalEvent")] public event EventHandler GlobalEvent; } public class DetailController : Controller { [EventSubscription("GlobalEvent")] public void OnGlobalEvent(object sender, EventArgs e) { } }
  • 7. 7 Виноват Composite Application Blocks public class MasterController : Controller { [EventPublication("GlobalEvent")] public event EventHandler GlobalEvent; } public class DetailController : Controller { [EventSubscription("GlobalEvent")] public void OnGlobalEvent(object sender, EventArgs e) { } }
  • 8. 8 Виноват Composite Application Blocks public class MasterController : Controller { [EventPublication("GlobalEvent")] public event EventHandler GlobalEvent; } public class DetailController : Controller { [EventSubscription("GlobalEvent")] public void OnGlobalEvent(object sender, EventArgs e) { } }
  • 9. 9 Виноват Composite Application Blocks public class MasterController : Controller { [EventPublication("GlobalEvent")] public event EventHandler GlobalEvent; } public class DetailController : Controller { [EventSubscription("GlobalEvent")] public void OnGlobalEvent(object sender, EventArgs e) { } }
  • 10. 10 Виноват Composite Application Blocks public class MasterController : Controller { [EventPublication("GlobalEvent")] public event EventHandler GlobalEvent; } public class DetailController : Controller { [EventSubscription("GlobalEvent")] public void OnGlobalEvent(object sender, EventArgs e) { } }
  • 11. 11 Виноват Composite Application Blocks public class MasterController : Controller { [EventPublication("GlobalEvent")] public event EventHandler GlobalEvent; } public class DetailController : Controller { [EventSubscription("GlobalEvent")] public void OnGlobalEvent(object sender, EventArgs e) { } }
  • 12. 12 CAB UI: скрытая угроза • Нет класса EventAggregator • Но • Любой контроллер может подписаться на • Любое событие • Любого другого контроллера • Неявно (при помощи атрибута)
  • 13. 13 Message Bus есть во всех MVVM • Prism – по инерции от Composite App Blocks • Caliburn.Micro – для поддержки View-First и ViewModel-First • MVVM от энтузиастов • Не знают как по-другому • Делают как у всех 
  • 14. 14 О чем мы поговорим? • Типовые UI-задачи для MessageBus • UI Composition • Смена контекста • Костыли • Как без него обойтись • MVVM-фреймворки для WPF и UWP без MessageBus • Что c MessageBus на JS (Angular2, React)
  • 15. 15 Опрос • Кто использовал MessageBus на UI? • И считает что без него обойтись нельзя • И считает, что без него использовать можно • Кто не использовал MessageBus на UI?
  • 16. 16 Примеры с INPC кодом public class DetailViewModel : BindableBase { public User User { get { return _user; } set { SetProperty(ref _user, value); } } }
  • 17. 17 Реализацию INPC держим в уме public class DetailViewModel { public User User { get; set; } }
  • 18. 18 Задача 1: UI Composition
  • 21. 21 View-Switching Navigation (View-First) RegionManager.RequestNavigate("MainRegion", EmailUri); Заменяем содержимое региона во время выполнения
  • 22. 22 UI Composition: MasterDetail форма ShellView MasterRegion DetailRegion
  • 24. 24 View-First: по вьюхе создаем ViewModel public partial class MasterView { [Import] public MasterViewModel ViewModel { set { DataContext = value; } } }
  • 25. 25 Главный вопрос Как DetailViewModel узнает об изменении выделенного элемента в MasterViewModel?
  • 26. 26 MessageBus: обмен сообщениями между ViewModel ShellView MasterRegion DetailRegion MessageBus
  • 27. 27 MasterViewModel public class MasterViewModel { public User SelectedUser { get { return _selectedUser; } set { SetProperty(ref _selectedUser, value); EventAggregator.GetEvent<UserChangedMessage>() .Publish(_selectedUser); } } public ObservableCollection<User> Users { get; set; } }
  • 28. 28 MasterViewModel public class MasterViewModel { public User SelectedUser { get { return _selectedUser; } set { SetProperty(ref _selectedUser, value); EventAggregator.GetEvent<UserChangedMessage>() .Publish(_selectedUser); } } public ObservableCollection<User> Users { get; set; } }
  • 29. 29 MasterViewModel public class MasterViewModel { public User SelectedUser { get { return _selectedUser; } set { SetProperty(ref _selectedUser, value); EventAggregator.GetEvent<UserChangedMessage>() .Publish(_selectedUser); } } public ObservableCollection<User> Users { get; set; } }
  • 30. 30 MasterViewModel public class MasterViewModel { public User SelectedUser { get { return _selectedUser; } set { SetProperty(ref _selectedUser, value); EventAggregator.GetEvent<UserChangedMessage>() .Publish(_selectedUser); } } public ObservableCollection<User> Users { get; set; } }
  • 31. 31 DetailViewModel public class DetailViewModel : BindableBase { public DetailViewModel(IEventAggregator eventAggregator) { eventAggregator.GetEvent<UserChangedMessage>() .Subscribe(OnUserChanged); } public User User { get; set; } public void OnUserChanged(User user) { User = user; } }
  • 32. 32 DetailViewModel public class DetailViewModel : BindableBase { public DetailViewModel(IEventAggregator eventAggregator) { eventAggregator.GetEvent<UserChangedMessage>() .Subscribe(OnUserChanged); } public User User { get; set; } public void OnUserChanged(User user) { User = user; } }
  • 33. 33 DetailViewModel public class DetailViewModel : BindableBase { public DetailViewModel(IEventAggregator eventAggregator) { eventAggregator.GetEvent<UserChangedMessage>() .Subscribe(OnUserChanged); } public User User { get; set; } public void OnUserChanged(User user) { User = user; } }
  • 34. 34 DetailViewModel public class DetailViewModel : BindableBase { public DetailViewModel(IEventAggregator eventAggregator) { eventAggregator.GetEvent<UserChangedMessage>() .Subscribe(OnUserChanged); } public User User { get; set; } public void OnUserChanged(User user) { User = user; } }
  • 35. 35 DetailViewModel public class DetailViewModel : BindableBase { public DetailViewModel(IEventAggregator eventAggregator) { eventAggregator.GetEvent<UserChangedMessage>() .Subscribe(OnUserChanged); } public User User { get; set; } public void OnUserChanged(User user) { User = user; } }
  • 36. 36 Это работает, но … Чем плохо: • Для UserChangedMessage только один получатель • MessageBus – из пушки по воробьям • Но без него никак  Чего хочется: • Биндинг MasterVM.SelectedUser на DetailVM.User
  • 38. 38 View-First vs ViewModel-First View-First ViewModel-First Первой создается View MessageBus Родительская ViewModel ViewModel Не нуженНеобходим Взаимодействие ViewModel MessageBus
  • 39. 39 MasterViewModel public class MasterViewModel { public User SelectedUser { get; set; } public List<User> Users { get; set; } }
  • 41. 41 ShellViewModel (ReactiveUI) public class ShellViewModel { public MasterViewModel MasterViewModel { get; } public DetailViewModel DetailViewModel { get; } public ShellViewModel() { MasterViewModel .WhenAny(m => m.SelectedUser, user => user.Value) .BindTo(DetailViewModel, d => d.User); } }
  • 42. 42 ShellViewModel (ReactiveUI) public class ShellViewModel { public MasterViewModel MasterViewModel { get; } public DetailViewModel DetailViewModel { get; } public ShellViewModel() { MasterViewModel .WhenAny(m => m.SelectedUser, user => user.Value) .BindTo(DetailViewModel, d => d.User); } }
  • 43. 43 ShellViewModel (ReactiveUI) public class ShellViewModel { public MasterViewModel MasterViewModel { get; } public DetailViewModel DetailViewModel { get; } public ShellViewModel() { MasterViewModel .WhenAny(m => m.SelectedUser, user => user.Value) .BindTo(DetailViewModel, d => d.User); } }
  • 44. 44 ShellViewModel (ReactiveUI) public class ShellViewModel { public MasterViewModel MasterViewModel { get; } public DetailViewModel DetailViewModel { get; } public ShellViewModel() { MasterViewModel .WhenAny(m => m.SelectedUser, user => user.Value) .BindTo(DetailViewModel, d => d.User); } }
  • 45. 45 ShellView (Caliburn) <ShellView> <ContentControl x:Name=“MasterViewModel" /> <ContentControl x:Name="DetailViewModel" /> </ShellView>
  • 46. 46 Такой разный Mediator • MessageBus – универсальный Mediator • Родительская ViewModel – частный медиатор
  • 47. 47 Подвох от Prism Prism предлагает RegionManager (ViewFirt) для: • View-Switching Navigation • UI Composition
  • 48. 48 Что делать? • Разделить задачи ViewSwitching Navigation и UI Composition • RegionManager – только для навигации • Не использовать RegionManager для UI Composition
  • 49. 49 UI Composition vs View-Switching Navigation UI Composition View-Switching Navigation Делим форму на части Да Да Зачем делим Части проще и повторно используются Меняем регионы на рантайме Нет Выделяем статическую часть (меню) и динамическую (регион) Да Нельзя (менять подход к навигации) Можно (скрепя сердце)Не использовать MessageBus
  • 51. 51 ShellView: MasterDetail без RegionManager <ShellView> <ContentControl regions:RegionManager.RegionName=“MasterDetailRegion“ /> </ShellView>
  • 52. 52 MasterDetailViewModel public class MasterDetailViewModel { public MasterViewModel MasterViewModel { get; } public DetailViewModel DetailViewModel { get; } public MasterDetailViewModel() { MasterViewModel .WhenAny(m => m.SelectedUser, user => user.Value) .BindTo(DetailViewModel, d => d.User); } }
  • 53. 53 UI Composition без ViewSwitching <MasterDetailView> <MasterView DataContext="{Binding MasterViewModel}" /> <DetailView DataContext="{Binding DetailViewModel}" /> </MasterDetailView>
  • 54. 54 UI Composition: ViewSwitching ViewModel-Frist (Caliburn) <Grid> <ContentControl x:Name=“MasterViewModel" Grid.Column="0" /> <ContentControl x:Name="DetailViewModel" Grid.Column="1" /> </Grid>
  • 55. 55 View-Switching в разных MVVM ViewModel-First (можно использовать для UI Composition) • Caliburn.Micro • MugenMvvmToolkit View-First (нельзя использовать для UI Composition) • Prism.WPF • ReactiveUI (UI Composition сделать сложно)
  • 56. 56 Не умеют View-Switching • Prism.Windows (UWP) • MvvmLight • MvvmCross • ReactiveUI (умеет, UI Composition сделать сложно)
  • 58. 58 ShellViewModel public class ShellViewModel { public MasterViewModel MasterViewModel { get; } public DetailViewModel DetailViewModel { get; } }
  • 59. 59 ViewSwitching для UI Composition (Caliburn) <Grid> <ContentControl x:Name=“MasterViewModel" Grid.Column="0" /> <ContentControl x:Name="DetailViewModel" Grid.Column="1" /> </Grid>
  • 60. 60 Как сделать ViewSwitching ViewModel-First? • View-ViewModel маппинг – создание View по ViewModel • ViewModelPresenter – отображение View
  • 61. 61 View-ViewModel маппинг • Конвенция именования ViewModels.MasterVewModel -> Views.MasterView • Реестр ViewResolver.Register<MasterView, MasterViewModel>(); var view = ViewResolver.Resolve(viewModel); • Метаданные (MEF) [View(typeof(BatchEditViewModel))] //наследник Export public partial class BatchEditView : IView пример кода №8 в списке полезных ссылок
  • 62. 62 Отображение View • AttachedProperty (Prism, Caliburn.Micro) <ContentControl regions:RegionManager.RegionName=“MainRegion“ /> • Xaml Extension (MugenMvvmToolkit) <ContentPresenter Content="{ViewModelToViewBinding Path=ViewModel}" /> • Наследник ContentControl (ViewModelPresenter)
  • 63. 63 ViewModelPresenter: 1 экран кода public class ViewModelPresenter : ContentControl { public static readonly DependencyProperty ViewModel … private void OnViewModelChanged() { var viewTypeResolver = ServiceLocator.Current.GetInstance<IViewTypeResolver>(); var view = viewTypeResolver.ResolveViewType(ViewModel); view.DataContext = ViewModel; Content = view; } }
  • 64. 64 ViewModelPresenter: 1 экран кода public class ViewModelPresenter : ContentControl { public static readonly DependencyProperty ViewModel … private void OnViewModelChanged() { var viewTypeResolver = ServiceLocator.Current.GetInstance<IViewTypeResolver>(); var view = viewTypeResolver.ResolveViewType(ViewModel); view.DataContext = ViewModel; Content = view; } }
  • 65. 65 ViewModelPresenter: 1 экран кода public class ViewModelPresenter : ContentControl { public static readonly DependencyProperty ViewModel … private void OnViewModelChanged() { var viewTypeResolver = ServiceLocator.Current.GetInstance<IViewTypeResolver>(); var view = viewTypeResolver.ResolveViewType(ViewModel); view.DataContext = ViewModel; Content = view; } }
  • 66. 66 ViewModelPresenter: 1 экран кода public class ViewModelPresenter : ContentControl { public static readonly DependencyProperty ViewModel … private void OnViewModelChanged() { var viewTypeResolver = ServiceLocator.Current.GetInstance<IViewTypeResolver>(); var view = viewTypeResolver.ResolveViewType(ViewModel); view.DataContext = ViewModel; Content = view; } }
  • 67. 67 ViewModelPresenter: 1 экран кода public class ViewModelPresenter : ContentControl { public static readonly DependencyProperty ViewModel … private void OnViewModelChanged() { var viewTypeResolver = ServiceLocator.Current.GetInstance<IViewTypeResolver>(); var view = viewTypeResolver.ResolveViewType(ViewModel); view.DataContext = ViewModel; Content = view; } }
  • 68. 68 ViewModelPresenter: 1 экран кода public class ViewModelPresenter : ContentControl { public static readonly DependencyProperty ViewModel … private void OnViewModelChanged() { var viewTypeResolver = ServiceLocator.Current.GetInstance<IViewTypeResolver>(); var view = viewTypeResolver.ResolveViewType(ViewModel); view.DataContext = ViewModel; Content = view; } }
  • 69. 69 ViewModelPresenter: 1 экран кода public class ViewModelPresenter : ContentControl { public static readonly DependencyProperty ViewModel … private void OnViewModelChanged() { var viewTypeResolver = ServiceLocator.Current.GetInstance<IViewTypeResolver>(); var view = viewTypeResolver.ResolveViewType(ViewModel); view.DataContext = ViewModel; Content = view; } }
  • 70. 70 ViewSwitching для UI Composition (Caliburn) <Grid> <ContentControl x:Name=“MasterViewModel" Grid.Column="0" /> <ContentControl x:Name="DetailViewModel" Grid.Column="1" /> </Grid>
  • 71. 71 ShellView ViewModel-First <Grid> <ViewModelPresenter ViewModel="{Binding MasterViewModel}" Grid.Column="0" /> <ViewModelPresenter ViewModel="{Binding DetailViewModel}" Grid.Column="1" /> </Grid>
  • 72. 72 View-Switching • Отдельная задача от Navigation • Табы • MDI • Отдельная задача от UI Composition • UI Composition не требует менять UI на рантайме
  • 73. 73 UI Composition без MessageBus • Разделить задачи View-Switching Navigation и UI Composition • Не использовать ViewSwitching View-First для UI Composition • RegionManager из Prism • Можно использовать ViewSwitching ViewModel-First для UI Composition • Caliburn.Micro • MugenMvvmToolkit • UI Composition без ViewSwitching (ViewModel в DataContext для View) • MVVM без View-Switching (Prism.Windows) • Если нужен ViewSwitching для UI Composition • Отдельный View-ViewModel маппинг • ViewModelPresenter
  • 74. 74 Задача 2: Глобальные события • Изменение контекста • Изменение этапов Lifecycle
  • 78. 78 Этапы Lifecycle: Suspend, Resume Suspended AppRunning App Suspending Resuming
  • 79. 79 Изменение контекста: Как обойтись попроще • Частное решение вместо MessageBus • Обычные события вместо широковещательных
  • 80. 80 ApplicationStateManager public enum ApplicationState { LoggingIn, Initializing, Active, Terminating, } public class ApplicationStateChangedEventArgs : EventArgs { public ApplicationState OldState { get; set; } public ApplicationState NewState { get; set; } }
  • 81. 81 ApplicationStateManager public enum ApplicationState { LoggingIn, Initializing, Active, Terminating, } public class ApplicationStateChangedEventArgs : EventArgs { public ApplicationState OldState { get; set; } public ApplicationState NewState { get; set; } }
  • 82. 82 ApplicationStateManager public enum ApplicationState { LoggingIn, Initializing, Active, Terminating, } public class ApplicationStateChangedEventArgs : EventArgs { public ApplicationState OldState { get; set; } public ApplicationState NewState { get; set; } }
  • 83. 83 ApplicationStateManager public enum ApplicationState { LoggingIn, Initializing, Active, Terminating, } public class ApplicationStateChangedEventArgs : EventArgs { public ApplicationState OldState { get; set; } public ApplicationState NewState { get; set; } }
  • 84. 84 Обычные события вместо широковещательных public class ApplicationStateManager { public event EventHandler<ApplicationStateChangedEventArgs> ApplicationStateChanged; public ApplicationState ApplicationState { get { return _applicationState; } set { var oldValue = _applicationState; _applicationState = value; OnApplicationStateChanged(oldValue, _applicationState); } } }
  • 85. 85 Обычные события вместо широковещательных public class ApplicationStateManager { public event EventHandler<ApplicationStateChangedEventArgs> ApplicationStateChanged; public ApplicationState ApplicationState { get { return _applicationState; } set { var oldValue = _applicationState; _applicationState = value; OnApplicationStateChanged(oldValue, _applicationState); } } }
  • 86. 86 Обычные события вместо широковещательных public class ApplicationStateManager { public event EventHandler<ApplicationStateChangedEventArgs> ApplicationStateChanged; public ApplicationState ApplicationState { get { return _applicationState; } set { var oldValue = _applicationState; _applicationState = value; OnApplicationStateChanged(oldValue, _applicationState); } } }
  • 87. 87 Обычные события вместо широковещательных public class ApplicationStateManager { public event EventHandler<ApplicationStateChangedEventArgs> ApplicationStateChanged; public ApplicationState ApplicationState { get { return _applicationState; } set { var oldValue = _applicationState; _applicationState = value; OnApplicationStateChanged(oldValue, _applicationState); } } }
  • 88. 88 ApplicationStateManager vs MessageBus Частных решений не будет много (у нас - одно) ApplicationStateManager MessageBus Подписчик Любой Любой Отправитель ApplicationStateManager Любой Событие ApplicationStateChanged Любое
  • 89. 89 Изменение контекста: Как обойтись лучше Цепочка вызовов методов
  • 90. 90 Пример цепочки вызовов методов Package Prism.Windows Класс PrismApplication.cs Метод OnSuspending https://github.com/PrismLibrary/Prism/blob/master/Source/Windows10/Prism.Windows/PrismApplication.cs
  • 91. 91 Цепочка вызовов vs частное решение Цепочка вызовов + Не требует генерации событий - Нужна продуманная архитектура Частное решение + Их не будет много - Хочется заменить частное на универсальное (MessageBus)
  • 92. 92 Костыли – Messages межу уровнями View ViewModel Logic
  • 93. 93 Фокус в поле при нажатии Add
  • 94. 94 Message между ViewModel и View MessageBus DetailViewModel DetailView UserChanged Focus Focus
  • 95. 95 DetailViewModel public class DetailViewModel : IHandle<UserChangedMessage> { public User User { get; set; } public void Handle(UserChangedMessage message) { User = message.User; if (User?.IsNew) { EventAggregator.PublishOnUIThread(new FocusMessage()); } } }
  • 96. 96 DetailViewModel public class DetailViewModel : IHandle<UserChangedMessage> { public User User { get; set; } public void Handle(UserChangedMessage message) { User = message.User; if (User?.IsNew) { EventAggregator.PublishOnUIThread(new FocusMessage()); } } }
  • 97. 97 DetailViewModel public class DetailViewModel : IHandle<UserChangedMessage> { public User User { get; set; } public void Handle(UserChangedMessage message) { User = message.User; if (User?.IsNew) { EventAggregator.PublishOnUIThread(new FocusMessage()); } } }
  • 98. 98 DetailView public partial class DetailView : UserControl, IHandle<FocusMessage> { public void Handle(FocusMessage message) { FirstNameTextBox.Focus(); } }
  • 99. 99 DetailView public partial class DetailView : UserControl, IHandle<FocusMessage> { public void Handle(FocusMessage message) { FirstNameTextBox.Focus(); } }
  • 100. 100 Как обойтись – Behavior (или Trigger) public class FocusNewUserBehavior : Behavior<TextBox> { public static readonly DependencyProperty User /* код опущен */ private static void OnUserChanged (DependencyObject d, DependencyPropertyChangedEventArgs e) { var behavior = (FocusNewUserBehavior) d; if (behavior.User?.IsNew) { behavior.AssociatedObject.Focus(); } } }
  • 101. 101 Как обойтись – Behavior (или Trigger) public class FocusNewUserBehavior : Behavior<TextBox> { public static readonly DependencyProperty User /* код опущен */ private static void OnUserChanged (DependencyObject d, DependencyPropertyChangedEventArgs e) { var behavior = (FocusNewUserBehavior) d; if (behavior.User?.IsNew) { behavior.AssociatedObject.Focus(); } } }
  • 102. 102 Как обойтись – Behavior (или Trigger) public class FocusNewUserBehavior : Behavior<TextBox> { public static readonly DependencyProperty User /* код опущен */ private static void OnUserChanged (DependencyObject d, DependencyPropertyChangedEventArgs e) { var behavior = (FocusNewUserBehavior) d; if (behavior.User?.IsNew) { behavior.AssociatedObject.Focus(); } } }
  • 103. 103 Как обойтись – Behavior (или Trigger) public class FocusNewUserBehavior : Behavior<TextBox> { public static readonly DependencyProperty User /* код опущен */ private static void OnUserChanged (DependencyObject d, DependencyPropertyChangedEventArgs e) { var behavior = (FocusNewUserBehavior) d; if (behavior.User?.IsNew) { behavior.AssociatedObject.Focus(); } } }
  • 104. 104 Как обойтись – Behavior (или Trigger) public class FocusNewUserBehavior : Behavior<TextBox> { public static readonly DependencyProperty User /* код опущен */ private static void OnUserChanged (DependencyObject d, DependencyPropertyChangedEventArgs e) { var behavior = (FocusNewUserBehavior) d; if (behavior.User?.IsNew) { behavior.AssociatedObject.Focus(); } } }
  • 105. 105 Как обойтись – Behavior (или Trigger) public class FocusNewUserBehavior : Behavior<TextBox> { public static readonly DependencyProperty User /* код опущен */ private static void OnUserChanged (DependencyObject d, DependencyPropertyChangedEventArgs e) { var behavior = (FocusNewUserBehavior) d; if (behavior.User?.IsNew) { behavior.AssociatedObject.Focus(); } } }
  • 106. 106 Attach Behavior к TestBox <TextBox Name="FirstNameTextBox" Text="{Binding User.FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> <i:Interaction.Behaviors> <behaviors:FocusNewUserBehavior User="{Binding User}"/> </i:Interaction.Behaviors> </TextBox>
  • 107. 107 Logic-ViewModel Message – не Observable модель • Изменение объектов кеша • Изменение пользовательских настроек
  • 109. 109 Не Observable модели (Prism) protected void UpdatePrice(string tickerSymbol, decimal newPrice) { _priceList[tickerSymbol] = newPrice; OnPricesUpdated(); } private void OnPricesUpdated() { var clonedPriceList = new Dictionary<string, decimal>(_priceList); EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList); }
  • 110. 110 Не Observable модели (Prism) protected void UpdatePrice(string tickerSymbol, decimal newPrice) { _priceList[tickerSymbol] = newPrice; OnPricesUpdated(); } private void OnPricesUpdated() { var clonedPriceList = new Dictionary<string, decimal>(_priceList); EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList); }
  • 111. 111 Не Observable модели (Prism) protected void UpdatePrice(string tickerSymbol, decimal newPrice) { _priceList[tickerSymbol] = newPrice; OnPricesUpdated(); } private void OnPricesUpdated() { var clonedPriceList = new Dictionary<string, decimal>(_priceList); EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList); }
  • 112. 112 Не Observable модели (Prism) protected void UpdatePrice(string tickerSymbol, decimal newPrice) { _priceList[tickerSymbol] = newPrice; OnPricesUpdated(); } private void OnPricesUpdated() { var clonedPriceList = new Dictionary<string, decimal>(_priceList); EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList); }
  • 113. 113 Не Observable модели (Prism) protected void UpdatePrice(string tickerSymbol, decimal newPrice) { _priceList[tickerSymbol] = newPrice; OnPricesUpdated(); } private void OnPricesUpdated() { var clonedPriceList = new Dictionary<string, decimal>(_priceList); EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList); }
  • 114. 114 Не Observable модели (Prism) protected void UpdatePrice(string tickerSymbol, decimal newPrice) { _priceList[tickerSymbol] = newPrice; OnPricesUpdated(); } private void OnPricesUpdated() { var clonedPriceList = new Dictionary<string, decimal>(_priceList); EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList); }
  • 115. 115 Не Observable модели (Prism) protected void UpdatePrice(string tickerSymbol, decimal newPrice) { _priceList[tickerSymbol] = newPrice; OnPricesUpdated(); } private void OnPricesUpdated() { var clonedPriceList = new Dictionary<string, decimal>(_priceList); EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList); }
  • 116. 116 Решение – Observable модель public class ObservableDictionary<TKey, TValue>: INotifyCollectionChanged, INotifyPropertyChanged { } https://code.msdn.microsoft.com/windowsdesktop/Samples-for-Parallel-b4b76364
  • 117. 117 Не Observable модели (Prism) protected void UpdatePrice(string tickerSymbol, decimal newPrice) { _priceList[tickerSymbol] = newPrice; OnPricesUpdated(); } private void OnPricesUpdated() { var clonedPriceList = new Dictionary<string, decimal>(_priceList); EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList); }
  • 118. 118 Observable модель protected void UpdatePrice(string tickerSymbol, decimal newPrice) { _priceList[tickerSymbol] = newPrice; OnPricesUpdated(); } private void OnPricesUpdated() { var clonedPriceList = new Dictionary<string, decimal>(_priceList); EventAggregator.GetEvent<MarketPricesUpdatedEvent>().Publish(clonedPriceList); }
  • 119. 119 Как избежать Messages между уровнями Messages
  • 120. 120 MVVM фреймворки без MessageBus • Таких нет  • В отдельном пакете • Prism 5 – SubEvents (в Prism 6 уже неотделяем) • MvvmCross – Messenger plugin
  • 121. 121 MessageBus нужно знать в лицо! Prism – EventAggregator Caliburn.Micro – EventAggregator MugenMvvmToolkit - EventAggregator MVVM Light – Messenger ReactiveUI – MessageBus
  • 122. 122 Как жить без MessageBus на WPF и UWP • Caliburn.Micro • MugenMvvmToolkit • MVVM без UI Composition • + свой ViewModel-First UI Composition
  • 123. 123 Не позволяет жить без MessageBus WPF • Prism.WPF UWP • Таких нет  • Prism.Windows без UI Composition Как добавить UI Composition: https://github.com/denis-tsv/Prism.StoreApps.Extensions.Mvvm
  • 124. 124 MessageBus на UI Для чего используется Как обойтись UI Composition Разделение UI Composition и ViewSwitching Глобальные события (смена контекста или lifecycle) Цепочка вызовов методов Частные решения с обычными событиями ViewModel-View Message Triggers, Behaviors Logic-ViewModel Message Observable Model Избежать Messages между уровнями Разные сборки для View, ViewModel, Logic Messages в сборке ViewModel
  • 125. 125 Главная проблема MessageBus на UI – костыли! Messages между слоями • Messages вместо Events (Logic -> ViewModel Messages) • Messages вместо Bindings (ViewModel -> View Messages)
  • 126. 126 Как можно жить с MessageBus на UI • UI Composition в стиле View-First (Prism.WPF) • Отдельные сборки View, ViewModel, Logic • Messages только в ViewModel • Глобальные Messages
  • 127. 127 Моя позиция • Большинство бизнес-приложений маленькие и средние • Команда 3-7 человек • Срок пара месяцев – пара лет • UI сложнее бэкэнда • Не нужно CQRS, Microservices, DataAccess Abstraction • MessageBus • Ещё сильнее запутывает и так сложный UI • Любую UI задачу можно решить без MessageBus • Примеры Prism без EventAggregator полезная ссылка №2 • Строка «MessageBus» ломает билд  полезная ссылка №9
  • 128. 128 Paul Betts (ReactiveUI author) about MessageBus … there are often more correct ways to solve problems, given a bit of ingenuity …
  • 129. 129 В параллельной вселенной JS Сходства • Fontend не уступает Backend по сложности • Развесистые JS-фреймворки (Angular, React) • Компонент JS = Регион XAML • Компонент (Шаблон + Поведение) • Регион (View + ViewModel)
  • 130. 130 Olical EventEmitter var ee = new EventEmitter(); function listener() { } ee.addListener('event', listener); ee.emitEvent('event'); ee.removeListener('event', listener);
  • 131. 131 Olical EventEmitter var ee = new EventEmitter(); function listener() { } ee.addListener('event', listener); ee.emitEvent('event'); ee.removeListener('event', listener);
  • 132. 132 Olical EventEmitter var ee = new EventEmitter(); function listener() { } ee.addListener('event', listener); ee.emitEvent('event'); ee.removeListener('event', listener);
  • 133. 133 Angular2 EventEmitter – Observer, а не MessageBus class EventEmitter { constructor(isAsync?: boolean) emit(value?: T) subscribe(generatorOrNext?: any, error?: any, complete?: any) : any }
  • 134. 134 Angular2 EventEmitter – Observer, а не MessageBus class EventEmitter { constructor(isAsync?: boolean) emit(value?: T) subscribe(generatorOrNext?: any, error?: any, complete?: any) : any }
  • 135. 135 Angular2 EventEmitter – Observer, а не MessageBus class EventEmitter { constructor(isAsync?: boolean) emit(value?: T) subscribe(generatorOrNext?: any, error?: any, complete?: any) : any }
  • 136. 136 Angular2 EventEmitter – Observer, а не MessageBus class EventEmitter { constructor(isAsync?: boolean) emit(value?: T) subscribe(generatorOrNext?: any, error?: any, complete?: any) : any }
  • 137. 137 MasterDetail: Parent @Component({ selector: 'my-app', template: ` <my-hero-master [heroes]="heroes" (onHeroSelected)="onSelect($event)“ /> <my-hero-detail [hero]="selectedHero“ /> `, }) export class AppComponent { selectedHero: Hero; onSelect(hero: Hero): void { this.selectedHero = hero; } }
  • 138. 138 MasterDetail: Parent @Component({ selector: 'my-app', template: ` <my-hero-master [heroes]="heroes" (onHeroSelected)="onSelect($event)“ /> <my-hero-detail [hero]="selectedHero“ /> `, }) export class AppComponent { selectedHero: Hero; onSelect(hero: Hero): void { this.selectedHero = hero; } }
  • 139. 139 MasterDetail: Parent @Component({ selector: 'my-app', template: ` <my-hero-master [heroes]="heroes" (onHeroSelected)="onSelect($event)“ /> <my-hero-detail [hero]="selectedHero“ /> `, }) export class AppComponent { selectedHero: Hero; onSelect(hero: Hero): void { this.selectedHero = hero; } }
  • 140. 140 MasterDetail: Parent @Component({ selector: 'my-app', template: ` <my-hero-master [heroes]="heroes" (onHeroSelected)="onSelect($event)“ /> <my-hero-detail [hero]="selectedHero“ /> `, }) export class AppComponent { selectedHero: Hero; onSelect(hero: Hero): void { this.selectedHero = hero; } }
  • 141. 141 MasterDetail: Parent @Component({ selector: 'my-app', template: ` <my-hero-master [heroes]="heroes" (onHeroSelected)="onSelect($event)“ /> <my-hero-detail [hero]="selectedHero“ /> `, }) export class AppComponent { selectedHero: Hero; onSelect(hero: Hero): void { this.selectedHero = hero; } }
  • 142. 142 MasterDetail: Master @Component({ selector: 'my-hero-master', template: ` <ul> <li *ngFor="let hero of heroes“ (click)="onSelect(hero)"> {{hero.name}} </li> </ul> `, }) export class HeroMasterComponent { @Input() heroes: Hero[]; @Output() onHeroSelected = new EventEmitter<Hero>(); onSelect(hero: Hero): void { this.onHeroSelected.emit(hero); } }
  • 143. 143 MasterDetail: Master @Component({ selector: 'my-hero-master', template: ` <ul> <li *ngFor="let hero of heroes“ (click)="onSelect(hero)"> {{hero.name}} </li> </ul> `, }) export class HeroMasterComponent { @Input() heroes: Hero[]; @Output() onHeroSelected = new EventEmitter<Hero>(); onSelect(hero: Hero): void { this.onHeroSelected.emit(hero); } }
  • 144. 144 MasterDetail: Master @Component({ selector: 'my-hero-master', template: ` <ul> <li *ngFor="let hero of heroes“ (click)="onSelect(hero)"> {{hero.name}} </li> </ul> `, }) export class HeroMasterComponent { @Input() heroes: Hero[]; @Output() onHeroSelected = new EventEmitter<Hero>(); onSelect(hero: Hero): void { this.onHeroSelected.emit(hero); } }
  • 145. 145 MasterDetail: Master @Component({ selector: 'my-hero-master', template: ` <ul> <li *ngFor="let hero of heroes“ (click)="onSelect(hero)"> {{hero.name}} </li> </ul> `, }) export class HeroMasterComponent { @Input() heroes: Hero[]; @Output() onHeroSelected = new EventEmitter<Hero>(); onSelect(hero: Hero): void { this.onHeroSelected.emit(hero); } }
  • 146. 146 MasterDetail: Detail @Component({ selector: 'my-hero-detail', template: ` <div> <label>name: </label> <input [(ngModel)]="hero.name" placeholder="name"/> </div> ` }) export class HeroDetailComponent { @Input() hero: Hero; }
  • 147. 147 MasterDetail: Detail @Component({ selector: 'my-hero-detail', template: ` <div> <label>name: </label> <input [(ngModel)]="hero.name" placeholder="name"/> </div> ` }) export class HeroDetailComponent { @Input() hero: Hero; }
  • 149. 149 Lifting State Up: Child class TemperatureInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange(e) { this.props.onChange(e.target.value); } render() { const value = this.props.value; return ( <input value={value} onChange={this.handleChange} /> ); } }
  • 150. 150 Lifting State Up: Child class TemperatureInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange(e) { this.props.onChange(e.target.value); } render() { const value = this.props.value; return ( <input value={value} onChange={this.handleChange} /> ); } }
  • 151. 151 Lifting State Up: Child class TemperatureInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange(e) { this.props.onChange(e.target.value); } render() { const value = this.props.value; return ( <input value={value} onChange={this.handleChange} /> ); } }
  • 152. 152 Lifting State Up: Child class TemperatureInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange(e) { this.props.onChange(e.target.value); } render() { const value = this.props.value; return ( <input value={value} onChange={this.handleChange} /> ); } }
  • 153. 153 Lifting State Up: Parent class Calculator extends React.Component { constructor(props) { super(props); this.handleCelsiusChange = this.handleCelsiusChange.bind(this); this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this); } handleCelsiusChange(value) { this.setState({value}); } handleFahrenheitChange(value) { this.setState({value}); } }
  • 154. 154 Lifting State Up: Parent class Calculator extends React.Component { constructor(props) { super(props); this.handleCelsiusChange = this.handleCelsiusChange.bind(this); this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this); } handleCelsiusChange(value) { this.setState({value}); } handleFahrenheitChange(value) { this.setState({value}); } }
  • 155. 155 Lifting State Up: Parent class Calculator extends React.Component { constructor(props) { super(props); this.handleCelsiusChange = this.handleCelsiusChange.bind(this); this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this); } handleCelsiusChange(value) { this.setState({value}); } handleFahrenheitChange(value) { this.setState({value}); } }
  • 156. 156 Lifting State Up: Parent render() { const value = this.state.value; const celsius = Convert(value, toCelsius); const fahrenheit = Convert(value, toFahrenheit); return ( <div> <TemperatureInput value={celsius} onChange={this.handleCelsiusChange} /> <TemperatureInput value={fahrenheit} onChange={this.handleFahrenheitChange} /> </div> ); }
  • 157. 157 Lifting State Up: Parent render() { const value = this.state.value; const celsius = Convert(value, toCelsius); const fahrenheit = Convert(value, toFahrenheit); return ( <div> <TemperatureInput value={celsius} onChange={this.handleCelsiusChange} /> <TemperatureInput value={fahrenheit} onChange={this.handleFahrenheitChange} /> </div> ); }
  • 158. 158 Lifting State Up: Parent render() { const value = this.state.value; const celsius = Convert(value, toCelsius); const fahrenheit = Convert(value, toFahrenheit); return ( <div> <TemperatureInput value={celsius} onChange={this.handleCelsiusChange} /> <TemperatureInput value={fahrenheit} onChange={this.handleFahrenheitChange} /> </div> ); }
  • 160. 160 Context: Child class TemperatureInput extends React.Component { render() { return ( <input style={{background: this.context.color}} /> ); } } TemperatureInput.contextTypes = { color: React.PropTypes.string };
  • 161. 161 Context: Child class TemperatureInput extends React.Component { render() { return ( <input style={{background: this.context.color}} /> ); } } TemperatureInput.contextTypes = { color: React.PropTypes.string };
  • 162. 162 Context: Child class TemperatureInput extends React.Component { render() { return ( <input style={{background: this.context.color}} /> ); } } TemperatureInput.contextTypes = { color: React.PropTypes.string };
  • 163. 163 Context: Parent class Calculator extends React.Component { getChildContext() { return {color: this.state.color}; } handleCelsiusChange(value) { this.setState({color : 'green'}); } } Calculator.childContextTypes = { color: React.PropTypes.string };
  • 164. 164 Context: Parent class Calculator extends React.Component { getChildContext() { return {color: this.state.color}; } handleCelsiusChange(value) { this.setState({color : 'green'}); } } Calculator.childContextTypes = { color: React.PropTypes.string };
  • 165. 165 Context: Parent class Calculator extends React.Component { getChildContext() { return {color: this.state.color}; } handleCelsiusChange(value) { this.setState({color : 'green'}); } } Calculator.childContextTypes = { color: React.PropTypes.string };
  • 166. 166 Context: Parent class Calculator extends React.Component { getChildContext() { return {color: this.state.color}; } handleCelsiusChange(value) { this.setState({color : 'green'}); } } Calculator.childContextTypes = { color: React.PropTypes.string };
  • 167. 167 React Context – не MessageBus Задача • Передача от родителя к вложенным компонентам • С уровня 1 на уровень 3 минуя уровень 2 Особенности реализации • Доступен родительскому и вложенным компонентам (не любым компонентам) • Обновление контекста во вложенных компонентах не работает Это не MessageBus 
  • 168. 168 Резюме: MessageBus на JS • MessageBus существует, но в виде отдельных библиотек • MessageBus нет в коробке флагманов Angular2 и React • Официальный гайд не рекомендует MesageBus • Компонент != ViewModel • Компонент = Шаблон + Поведение • ViewModel = Поведение • На JS нет разных видов композиции ViewFirst и ViewModelFirst • Для JS MessageBus не является проблемой, как в XAML • Продуманная архитектура JS фреймворков – аргумент за переписывание UI на web-стек 
  • 169. 169 Полезные ссылки 1. MessageBus, врага нужно знать в лицо https://msdn.microsoft.com/en-us/library/ff647328.aspx 2. Примеры StockTrader и UIComposition для Prism.WPF, откуда выпилен EventAggregator https://github.com/denis-tsv/Prism-Samples-Wpf-NoEventAggregetor 3. Как добавить UI Composition для Prism.Windows (UWP) https://github.com/denis-tsv/Prism.StoreApps.Extensions.Mvvm 4. Paul Betts. MessageBus and why you shouldn't use it http://log.paulbetts.org/messagebus-and-why-you-shouldnt-use-it/ 5, Caliburn.Micro Классный, минималистичный, кроссплатформенный и мультипарадигменный MVVM, в котором нет ничего лишнего http://caliburnmicro.com/ 6. MugenMvvmToolkit. Мощный UI фреймворк с офигительными кастомными XAML биндингами https://github.com/MugenMvvmToolkit https://habrahabr.ru/post/236745/ 7. ReactiveUI. Биндинг свойств одной ViewModel на свойства другой ViewModel в родительской ViewModel http://reactiveui.net/ 8. Пример View-ViewModel mapping с использованием MEF http://www.intuit.ru/EDI/14_12_14_2/1418505481-18353/tutorial/1050/objects/1/files/SampleSolution.zip 9. Как сломать билд (о, да!) словом MessageBus, EventAggregator, Messenger и любой другой реализацией MessageBus http://hmemcpy.com/2013/03/even-more-resharper-annotations-hackery-marking-3rd-party-code-as-obsolete/

Hinweis der Redaktion

  1. Паттерн, предназначенный для обмена сообщениями между процессами (бэкэнд) Про него много говорят в области микросервисов MessageBus позволяет любому процессу отправить сообщение любому другому процессу
  2. Кто виноват и что делать?
  3. Кто по картинке узнал виновника? Как и при авиакатастрофах все списывают на мертвых
  4. Нет явной подписки, она в инфраструктуре CAB UI
  5. Мышки кололись и плакали но продолжали есть кактус
  6. UI Composition – это разделение больших и сложных форм на более просты составляющие и настройка взаимодействия между ними. Маленькие части проще писать и их можно повторно использовать MessageBus – для настройки взаимодействия между формами
  7. Содержимое правого региона меняется на рантайме
  8. Только делим форму на части, на рантайме не меняем содержимое региона
  9. Нет родительской ViewModel Messages между ViewModel регионов
  10. Родительская ViewModel Взаимодействие ViewModel регионов в родительской ViewModel
  11. Более продвинутое решение для View-Switching Navigation используется для более простой задачи UI Composition Далее цепочка View-Switching Navigation (View-First) для UI Composition Message Bus для передачи сообщений между ViewModel
  12. View-Switching Navigation и UI Composition – разные задачи View-Switching Navigation – вторична UI Composition – первична
  13. Разделить задачи View-Switching Navigation и UI Composition
  14. Это решение используется в Prism.Windows
  15. TODO рассказать про навигацию и Composite UI, Что это разные задачи. CompositeUI не зависит от выбранного подхода к навигации
  16. В списке слева – права и роли, при выборе элемента – отображение специализированной detail формы Как это сделать при использовании MVVM без ViewSwitching?
  17. Делает UI Composition более мощным (замена на рантайме вьюх) и используется для навигации
  18. https://blogs.windows.com/buildingapps/2016/04/28/the-lifecycle-of-a-uwp-app/#Tr8hYBskx1AlWbFj.97
  19. https://app.genmymodel.com/edit/_VmmK0HOJEeaXi_zZ4LdZkg#
  20. Серебряной пули нет, каждый случай нужно диагностировать и лечить индивидуально Но между уровнями – однозначно костыль
  21. View, ViewModel и Logic – разные сборки Messages внутри сборки ViewModel
  22. MvvmCross для Xamarin – тоже Composite UI есть криво и в одном сэмпле, но не в коробке
  23. http://plnkr.co/edit/aEjlwIKKhcErWAnIhY3C?p=preview http://plnkr.co/edit/v9mJA2hG4lJTJjfHv39i?p=preview – моя доработка
  24. http://plnkr.co/edit/aEjlwIKKhcErWAnIhY3C?p=preview http://plnkr.co/edit/v9mJA2hG4lJTJjfHv39i?p=preview – моя доработка
  25. http://plnkr.co/edit/aEjlwIKKhcErWAnIhY3C?p=preview http://plnkr.co/edit/v9mJA2hG4lJTJjfHv39i?p=preview – моя доработка
  26. http://plnkr.co/edit/aEjlwIKKhcErWAnIhY3C?p=preview http://plnkr.co/edit/v9mJA2hG4lJTJjfHv39i?p=preview – моя доработка
  27. http://plnkr.co/edit/aEjlwIKKhcErWAnIhY3C?p=preview http://plnkr.co/edit/v9mJA2hG4lJTJjfHv39i?p=preview – моя доработка
  28. http://plnkr.co/edit/v9mJA2hG4lJTJjfHv39i?p=preview
  29. http://plnkr.co/edit/v9mJA2hG4lJTJjfHv39i?p=preview
  30. http://plnkr.co/edit/v9mJA2hG4lJTJjfHv39i?p=preview
  31. http://plnkr.co/edit/v9mJA2hG4lJTJjfHv39i?p=preview
  32. const scaleNames = { c: 'Celsius', f: 'Fahrenheit' }; function toCelsius(fahrenheit) { return (fahrenheit - 32) * 5 / 9; } function toFahrenheit(celsius) { return (celsius * 9 / 5) + 32; } function tryConvert(value, convert) { const input = parseFloat(value); if (Number.isNaN(input)) { return ''; } const output = convert(input); const rounded = Math.round(output * 1000) / 1000; return rounded.toString(); } function BoilingVerdict(props) { if (props.celsius >= 100) { return <p>The water would boil.</p>; } return <p>The water would not boil.</p>; } class TemperatureInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange(e) { this.props.onChange(e.target.value); //this.setState({color: 'green'}); } render() { const value = this.props.value; const scale = this.props.scale; return ( <fieldset> <legend>Enter temperature in {scaleNames[scale]}:</legend> <input value={value} onChange={this.handleChange} style={{background: this.context.color}} /> </fieldset> ); } } TemperatureInput.contextTypes = { color: React.PropTypes.string }; class Calculator extends React.Component { constructor(props) { super(props); this.handleCelsiusChange = this.handleCelsiusChange.bind(this); this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this); this.state = {value: '', scale: 'c', color:'red'}; } getChildContext() { return {color: this.state.color}; } handleCelsiusChange(value) { this.setState({scale: 'c'}); this.setState({value}); this.setState({color : 'green'}); } handleFahrenheitChange(value) { this.setState({scale: 'f'}); this.setState({value}); this.setState({color: 'blue'}); } render() { const scale = this.state.scale; const value = this.state.value; const celsius = scale === 'f' ? tryConvert(value, toCelsius) : value; const fahrenheit = scale === 'c' ? tryConvert(value, toFahrenheit) : value; return ( <div> <TemperatureInput scale="c" value={celsius} onChange={this.handleCelsiusChange} /> <TemperatureInput scale="f" value={fahrenheit} onChange={this.handleFahrenheitChange} /> <BoilingVerdict celsius={parseFloat(celsius)} /> </div> ); } } Calculator.childContextTypes = { color: React.PropTypes.string }; ReactDOM.render( <Calculator />, document.getElementById('root') );
  33. const scaleNames = { c: 'Celsius', f: 'Fahrenheit' }; function toCelsius(fahrenheit) { return (fahrenheit - 32) * 5 / 9; } function toFahrenheit(celsius) { return (celsius * 9 / 5) + 32; } function tryConvert(value, convert) { const input = parseFloat(value); if (Number.isNaN(input)) { return ''; } const output = convert(input); const rounded = Math.round(output * 1000) / 1000; return rounded.toString(); } function BoilingVerdict(props) { if (props.celsius >= 100) { return <p>The water would boil.</p>; } return <p>The water would not boil.</p>; } class TemperatureInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange(e) { this.props.onChange(e.target.value); //this.setState({color: 'green'}); } render() { const value = this.props.value; const scale = this.props.scale; return ( <fieldset> <legend>Enter temperature in {scaleNames[scale]}:</legend> <input value={value} onChange={this.handleChange} style={{background: this.context.color}} /> </fieldset> ); } } TemperatureInput.contextTypes = { color: React.PropTypes.string }; class Calculator extends React.Component { constructor(props) { super(props); this.handleCelsiusChange = this.handleCelsiusChange.bind(this); this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this); this.state = {value: '', scale: 'c', color:'red'}; } getChildContext() { return {color: this.state.color}; } handleCelsiusChange(value) { this.setState({scale: 'c'}); this.setState({value}); this.setState({color : 'green'}); } handleFahrenheitChange(value) { this.setState({scale: 'f'}); this.setState({value}); this.setState({color: 'blue'}); } render() { const scale = this.state.scale; const value = this.state.value; const celsius = scale === 'f' ? tryConvert(value, toCelsius) : value; const fahrenheit = scale === 'c' ? tryConvert(value, toFahrenheit) : value; return ( <div> <TemperatureInput scale="c" value={celsius} onChange={this.handleCelsiusChange} /> <TemperatureInput scale="f" value={fahrenheit} onChange={this.handleFahrenheitChange} /> <BoilingVerdict celsius={parseFloat(celsius)} /> </div> ); } } Calculator.childContextTypes = { color: React.PropTypes.string }; ReactDOM.render( <Calculator />, document.getElementById('root') );
  34. const scaleNames = { c: 'Celsius', f: 'Fahrenheit' }; function toCelsius(fahrenheit) { return (fahrenheit - 32) * 5 / 9; } function toFahrenheit(celsius) { return (celsius * 9 / 5) + 32; } function tryConvert(value, convert) { const input = parseFloat(value); if (Number.isNaN(input)) { return ''; } const output = convert(input); const rounded = Math.round(output * 1000) / 1000; return rounded.toString(); } function BoilingVerdict(props) { if (props.celsius >= 100) { return <p>The water would boil.</p>; } return <p>The water would not boil.</p>; } class TemperatureInput extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } handleChange(e) { this.props.onChange(e.target.value); //this.setState({color: 'green'}); } render() { const value = this.props.value; const scale = this.props.scale; return ( <fieldset> <legend>Enter temperature in {scaleNames[scale]}:</legend> <input value={value} onChange={this.handleChange} style={{background: this.context.color}} /> </fieldset> ); } } TemperatureInput.contextTypes = { color: React.PropTypes.string }; class Calculator extends React.Component { constructor(props) { super(props); this.handleCelsiusChange = this.handleCelsiusChange.bind(this); this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this); this.state = {value: '', scale: 'c', color:'red'}; } getChildContext() { return {color: this.state.color}; } handleCelsiusChange(value) { this.setState({scale: 'c'}); this.setState({value}); this.setState({color : 'green'}); } handleFahrenheitChange(value) { this.setState({scale: 'f'}); this.setState({value}); this.setState({color: 'blue'}); } render() { const scale = this.state.scale; const value = this.state.value; const celsius = scale === 'f' ? tryConvert(value, toCelsius) : value; const fahrenheit = scale === 'c' ? tryConvert(value, toFahrenheit) : value; return ( <div> <TemperatureInput scale="c" value={celsius} onChange={this.handleCelsiusChange} /> <TemperatureInput scale="f" value={fahrenheit} onChange={this.handleFahrenheitChange} /> <BoilingVerdict celsius={parseFloat(celsius)} /> </div> ); } } Calculator.childContextTypes = { color: React.PropTypes.string }; ReactDOM.render( <Calculator />, document.getElementById('root') );
  35. https://facebook.github.io/react/docs/context.html