1 mvvm pour wpf - romagny13 | blog .net | web |...

79
1 1 Mvvm pour Wpf J. ROMAGNY I. MVVM ..................................................................................................................................................... 5 1. VUES ............................................................................................................................................................ 5 a. « View First » ......................................................................................................................................... 5 b. Fenêtre principale de l’application (MainWindow/ Shell) ..................................................................... 6 Régions ........................................................................................................................................................................ 6 c. Vues ....................................................................................................................................................... 6 d. Ressources ............................................................................................................................................. 6 e. Source de données ................................................................................................................................. 7 DataContext ................................................................................................................................................................. 7 ViewModelLocator....................................................................................................................................................... 7 Sans conteneur ....................................................................................................................................................... 7 Avec conteneur IoC ................................................................................................................................................. 8 ViewModelLocator « générique » ........................................................................................................................... 9 Bootsrapper … Prévention .................................................................................................................................... 10 f. Converters ............................................................................................................................................ 10 g. TargetNullValue ................................................................................................................................... 11 h. Animations ........................................................................................................................................... 11 Transitions ................................................................................................................................................................. 11 Contrôle de chargement ............................................................................................................................................ 12 VisualStates................................................................................................................................................................ 13 2. VIEWMODEL................................................................................................................................................ 14 a. Classes de base de ViewModel ............................................................................................................ 14 b. ViewModel Liste et détails ................................................................................................................... 15 c. Filtrer, trier, grouper (CollectionViewSource) ...................................................................................... 18 d. Commandes ......................................................................................................................................... 20 e. Injection de dépendance ...................................................................................................................... 22 Unity .......................................................................................................................................................................... 22 f. « ViewModel First » ............................................................................................................................. 23 g. Navigation ........................................................................................................................................... 26 h. Messenger ........................................................................................................................................... 28 i. ServiceLocator...................................................................................................................................... 29 j. DialogService ....................................................................................................................................... 30 k. Design Time data ................................................................................................................................. 30 3. MODELS...................................................................................................................................................... 32 a. ObservableBase ................................................................................................................................... 32 b. Validation ............................................................................................................................................ 33 1. Validation sur exceptions ................................................................................................................................. 33 2. ValidationrRule ................................................................................................................................................. 33 3. IDataErrorInfo .................................................................................................................................................. 35 4. INotifyDataErrorInfo ........................................................................................................................................ 36 5. Une classe de validation réutlisable ................................................................................................................. 37 6. Templates ......................................................................................................................................................... 40 II. MVVM LIGHT ......................................................................................................................................... 41 1-Installation ................................................................................................................................................ 41 a. Templates Mvvm Light ..................................................................................................................................... 42 b. Items Mvvm Light ............................................................................................................................................. 42 c. Snippets............................................................................................................................................................ 42 d. Projet « from scratch » ..................................................................................................................................... 43

Upload: others

Post on 24-Jan-2020

7 views

Category:

Documents


0 download

TRANSCRIPT

1

1

Mvvm pour Wpf J. ROMAGNY

I. MVVM ..................................................................................................................................................... 5

1. VUES ............................................................................................................................................................ 5 a. « View First » ......................................................................................................................................... 5 b. Fenêtre principale de l’application (MainWindow/ Shell) ..................................................................... 6

Régions ........................................................................................................................................................................ 6 c. Vues ....................................................................................................................................................... 6 d. Ressources ............................................................................................................................................. 6 e. Source de données ................................................................................................................................. 7

DataContext ................................................................................................................................................................. 7 ViewModelLocator ....................................................................................................................................................... 7

Sans conteneur ....................................................................................................................................................... 7 Avec conteneur IoC ................................................................................................................................................. 8 ViewModelLocator « générique » ........................................................................................................................... 9 Bootsrapper … Prévention .................................................................................................................................... 10

f. Converters ............................................................................................................................................ 10 g. TargetNullValue ................................................................................................................................... 11 h. Animations ........................................................................................................................................... 11

Transitions ................................................................................................................................................................. 11 Contrôle de chargement ............................................................................................................................................ 12 VisualStates ................................................................................................................................................................ 13

2. VIEWMODEL ................................................................................................................................................ 14 a. Classes de base de ViewModel ............................................................................................................ 14 b. ViewModel Liste et détails ................................................................................................................... 15 c. Filtrer, trier, grouper (CollectionViewSource) ...................................................................................... 18 d. Commandes ......................................................................................................................................... 20 e. Injection de dépendance ...................................................................................................................... 22

Unity .......................................................................................................................................................................... 22 f. « ViewModel First » ............................................................................................................................. 23 g. Navigation ........................................................................................................................................... 26 h. Messenger ........................................................................................................................................... 28 i. ServiceLocator ...................................................................................................................................... 29 j. DialogService ....................................................................................................................................... 30 k. Design Time data ................................................................................................................................. 30

3. MODELS...................................................................................................................................................... 32 a. ObservableBase ................................................................................................................................... 32 b. Validation ............................................................................................................................................ 33

1. Validation sur exceptions ................................................................................................................................. 33 2. ValidationrRule ................................................................................................................................................. 33 3. IDataErrorInfo .................................................................................................................................................. 35 4. INotifyDataErrorInfo ........................................................................................................................................ 36 5. Une classe de validation réutlisable ................................................................................................................. 37 6. Templates ......................................................................................................................................................... 40

II. MVVM LIGHT ......................................................................................................................................... 41

1-Installation ................................................................................................................................................ 41 a. Templates Mvvm Light ..................................................................................................................................... 42 b. Items Mvvm Light ............................................................................................................................................. 42 c. Snippets ............................................................................................................................................................ 42 d. Projet « from scratch » ..................................................................................................................................... 43

2

2

2-Model ........................................................................................................................................................ 43 3. ViewModel ................................................................................................................................................ 44

a-Commandes ............................................................................................................................................................ 44 b-Messenger .............................................................................................................................................................. 44

4. SimpleIoC .................................................................................................................................................. 45

III. PRISM .................................................................................................................................................... 47

1. INSTALLATION .............................................................................................................................................. 47 2. MEMENTO .................................................................................................................................................. 47

a. Shell ..................................................................................................................................................... 48 b. Boostrapper ......................................................................................................................................... 48 c. Lancement de l’application .................................................................................................................. 48 d. Régions ................................................................................................................................................ 48

Toolbar et navigation ................................................................................................................................................. 49 Adaptation de région ................................................................................................................................................. 50

c. ShellViewModel ................................................................................................................................... 50 d. Modules ............................................................................................................................................... 52

Avoir plus de contrôle sur la vue affichée (Activate/ Deactivate) .............................................................................. 53 « ViewModel First » ................................................................................................................................................... 54 Module chargé à la demande .................................................................................................................................... 56

e. Navigation ........................................................................................................................................... 56 Navigation avec le « regionManager » ...................................................................................................................... 56 Avec passage de paramètre ....................................................................................................................................... 57 Avec NavigationParameters ....................................................................................................................................... 57 Création d’une commande de navigation globale ..................................................................................................... 58 Navigation Journal ..................................................................................................................................................... 59 Region Context........................................................................................................................................................... 59 Confirmer, Annuler la navigation (IConfirmNavigationRequest) ............................................................................... 60 RegionMemberLifetime ............................................................................................................................................. 61 Navigation grâce à VisualStateManager .................................................................................................................... 61

3. MVVM (PRISM.MVVM) ................................................................................................................................. 62 a. DelegateCommand .............................................................................................................................. 62 b. CompositeCommand ........................................................................................................................... 62 c. BindableBase ....................................................................................................................................... 63 d. ViewModelLocator ............................................................................................................................... 63

4. PUBSUBEVENTS (PRISM.PUBSUBEVENTS) ......................................................................................................... 65 5. SERVICES ..................................................................................................................................................... 66

a. Service de module ................................................................................................................................ 66 b. Services partagés ................................................................................................................................. 67

6. PROJET INFRASTRUCTURE ............................................................................................................................... 69 7. INTERACTIVITY (PRISM.INTERACTIVITY) .............................................................................................................. 70

a. Notification .......................................................................................................................................... 70 Avec « InteractionRequest » ...................................................................................................................................... 70 Avec « DefaultNotificationWindow » ........................................................................................................................ 71

b. Confirmation ........................................................................................................................................ 71 Avec « InteractionRequest » ...................................................................................................................................... 71 Avec « DefaultConfirmationWindow » ...................................................................................................................... 72

c. Custom ................................................................................................................................................. 73 Custom popup ........................................................................................................................................................... 73 Custom Notification ................................................................................................................................................... 74

d. BusyIndicator ....................................................................................................................................... 77 e. InvokeCommandAction ........................................................................................................................ 79

3

3

« Vue d’ensemble »

Vues

L’application a une fenêtre principale (MainWIndow ou « Shell »).

Les vues sont des contrôles utilisateurs/ fenêtres. Celles-ci ont un un viewmodel en source de

données et les contrôles sont bindés.

On définit la source avec le DataContext, CollectionViewSource si besoin de trier/filtrer/grouper, on

peut utiliser également un ViewModelLocator

L’application a des dictionnaires de ressources (styles, templates, fonts, brushes, colors, etc.).

…Converter, TargetNullValue

ViewModels

Ensemble des propriétés que la vue aura besoin d’afficher + déclencheurs (commandes)

... utilise les services … méthodes pour aller chercher les données pour remplir les propriétés

(chargement de la page souvent).

Plusieurs « types » de viewmodels : liste, détails affichant le détail d’un élément/ élément courant

Navigation : entre vues, avec passage de paramètre, historique de navigation

Messenger sert à échanger des messages. L’emeteur envoie un message, les abonnés recoivent une

notification

Models

Model défini la structure des données (lecture seule, nom affiché, etc.), les stocke et sont bindées

dans la vue. Implémente INotifyPropertyChanged si besoin de notifier la vue des changements, data

annotations si besoin de validation et ieditableobject si besoin d’annuler les changements.

Prism

Utile pour l’UI Composition, c’est-à-dire avoir une « page unique » divisée en régions (conteneurs

pour vues), avec navigation.

- Bootstrapper : sert à définir la vue de base, IoC, à afficher la page

- Shell qui est la vue/ page de base, le conteneur, divisé en régions

- Modules divisent les features

Chaque vue est enregistrée pour une région avec le regionManager.

Les viewmodels et services sont enregistrés dans un container.

- Navigation avec le regionManager on indique la region ciblée pour le changement de vue,

une uri avec le nom de la nouvelle vue et enfin les paramètres

- Modularité : permet de diviser les features et de charger à la demande les modules au lieu

de tout charger au lancement de l’application

4

4

Différences entre Mvvm pour Windows / Windows Phone/Windows Phone SliverLight … Pourquoi on

est obligé de différencier Mvvm pour Wpf et Windows, Windows Phone, etc.

WPF

CommandManager pour RelayCommand

DialogService WinRT :

Pas de propetychanging

Pas de listCollectionView

Pas de IDataErrorInfo > INotifyDataErrorInfo

Reflection légérement différente

Pas de commandManager pour relayCommand WP8

Ajouter une référence aux data annotations (C:\Program Files (x86)\Microsoft SDKs\Silverlight\v5.0\Libraries\Client )

WP8 SL

Frame > PhoneApplicationFrame

Pas de paramètre dans uri

NavigationService existe déjà (dans la Page de base)

Problème avec les data annotations…

5

5

I. Mvvm On peut se créer ses propres classes d’aides et pas forcément utiliser un Framework :

1. Vues

a. « View First »

Membres,

commandes du shell

et de l’application

Membres affichés

par la vue,

commandes et

utilisation services

(injection possible)

View1

(UserControl / fenêtre)

Fenêtre principale

Vues (UserControls) ou

ContentControls

ShellViewModel

Enregistrement

possible services,

viewmodels dans

conteneur.

Fenêtre de départ

(StartupUri)

Ressources

ViewModel deView1

App

Views ViewModels

Un ViewModelLocator peut faire

l’intermédiaire. Retourne une

instance du ViewModel (avec

injection) .Utilisation conteneur

possible

Autre ViewModel

Service

IService

Echange de messages

possible par Messenger

entre ViewModels

Models

View2

6

6

b. Fenêtre principale de l’application (MainWindow/ Shell)

StartupUri

<Application x:Class="MvvmDemo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> </Application> Bootstrapping … Définir et lancer soi-même sa fenêtre

public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); // enregistrement des services avec un container possible avant var shell = new Shell(); shell.Show(); } } On peut supprimer le paramètre StartupUri

Régions

Utiliser des ContentControls qui recevront les vues (UserControls)

c. Vues

Ce sont soit des contrôles utilisateurs, soit des fenêtres.

d. Ressources

(Styles, templates, fonts, brushes, colors, etc.)

<Application x:Class="MvvmDemo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:MvvmDemo.ViewModels" StartupUri="MainWindow.xaml"> <Application.Resources> <ResourceDictionary> <vm:ViewModelLocator x:Key="ViewModelLocator"/> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="pack://application:,,,/EasyMvvm;component/Resources/Styles.xaml" /> <ResourceDictionary Source="Resources/Styles.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </Application>

Ressource d’un

autre projet

Une région qui pourra recevoir plusieurs vues,

par exemple la vue détails

On supprimer « MainWindow » et

ajoute une fenêtre appelée « Shell »

par exemple

Ressource du

projet

MainWindow

7

7

e. Source de données

DataContext

Xaml

<UserControl … xmlns:vm ="clr-namespace:MvvmDemo.ViewModels"> <UserControl.DataContext> <vm:PeopleViewModel /> </UserControl.DataContext> <Grid> </Grid> </UserControl> Ou code-behind

public partial class Shell : Window { public Shell() { InitializeComponent(); DataContext = new PeopleViewModel(); } } CollectionViewSource (pour filtrer, trier, grouper) sur listes

ViewModelLocator

(Enregistrement des services injectés dans un conteneur IoC)

Listing des ViewModels

Pour chaque ViewModel : Création de l’instance (grâce au conteneur ou non) du ViewModel

et retour de l’instance

Sans conteneur public class ViewModelLocator { private static ViewModelLocator _default; public static ViewModelLocator Default { get { return _default ?? (_default = new ViewModelLocator()); } } private MainWindowViewModel _mainWindowViewModel; public MainWindowViewModel MainWindowViewModel { get { if (_mainWindowViewModel == null) { IService service = new Service(); _mainWindowViewModel = new MainWindowViewModel(service); } return _mainWindowViewModel; } } }

Namespace

Si l’instance du ViewModel est nulle,

on la créé ainsi que les services

injectés dans le constructeur

DataContext ici pour un « UserControl » ,

cela serait la même chose pour « Window »

8

8

Exemple de ViewModel et services pour les exemples

public class MainWindowViewModel { public string Message { get; set; } private IService _service; public MainWindowViewModel(IService service) { _service = service; Message = "Bonjour!"; } } public interface IService { } public class Service : IService { }

Avec conteneur IoC

Enregistrement des services injectés et des VioewModels dans un conteneur IoC (dans « App » ..

« OnStartup » ou dans le constructeur du ViewModelLocator)

Création de l’instance du ViewModel grâce au conteneur (avec injection des dépendances) et retour

de l’instance

public class ViewModelLocator { private static ViewModelLocator _default; public static ViewModelLocator Default { get { return _default ?? (_default = new ViewModelLocator()); } } public ViewModelLocator() { EasyIoC.Default.Register<IService, Service>(); EasyIoC.Default.Register<MainWindowViewModel>(); } public MainWindowViewModel MainWindowViewModel { get { return EasyIoC.Default.Resolve<MainWindowViewModel>(); } } } Utilisation du ViewModelLocator dans les 2 cas idem (avec et sans conteneur)

Dans le code-behind de la vue

DataContext = ViewModelLocator.Default.MainWindowViewModel;

Enregistrement des services

puis des ViewModels dans le

conteneur

Le conteneur se charge de créer

l’instance que l’on retourne

9

9

Xaml

Référencement dans « App »

<Application x:Class="MvvmDemo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:MvvmDemo.ViewModels" StartupUri="MainWindow.xaml"> <Application.Resources> <vm:ViewModelLocator x:Key="ViewModelLocator" /> </Application.Resources> </Application> Puis dans la vue

<Window … DataContext="{Binding Source={StaticResource ViewModelLocator},Path=MainWindowViewModel}"> <Grid> </Grid> </Window> On pourrait également définir le DataContext sur l’instance des UserControls d’une fenêtre/vue

<Views:PeopleView DataContext="{Binding PeopleViewModel, Source={StaticResource ViewModelLocator}}" />

ViewModelLocator « générique » public class ViewModelLocator { private readonly static Dictionary<string, object> _viewModels = new Dictionary<string, object>(); public object this[string key] { get { object viewModel; _viewModels.TryGetValue(key, out viewModel); return viewModel; } } private static ViewModelLocator _default; public static ViewModelLocator Default { get { return _default ?? (_default = new ViewModelLocator()); } } public void Register(string key, object viewModel) { _viewModels.Add(key, viewModel); } public void Register<T>(object viewModel) { Register(typeof(T).Name, viewModel); } public void Register(object viewModel) { Register(viewModel.GetType().Name, viewModel); } }

10

10

Enregistrement des services et ViewModels

IService service = new Service(); var vm = new MainWindowViewModel(service); ViewModelLocator.Default.Register("MainWindowViewModel",vm);

Utilisation

Code-behind de la vue

DataContext = ViewModelLocator.Default["MainWindowViewModel"]; Xaml

Référencer le ViewModelLocator dans « App » comme précédemment puis dans les vues

DataContext="{Binding Source={StaticResource ViewModelLocator},Path=[MainWindowViewModel]}"

Bootsrapper … Prévention

Avec le ViewModelLocator référencé dans « App » … Si on supprime StartupUri (en gros on fait un

bootstrapper) il se peut qu’il y ait ensuite une erreur « impossible de retrouver ViewModelLocator »

(du à un problème de msbuild qui ne génére tout simplement pas le fichier). Dans ce cas il faut au

moins avoir un fichier de resources (Styles par exemple) ou tout simplement rajouter StartupUri

f. Converters

public sealed class BooleanToVisibilityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return (value is bool && (bool)value) ? Visibility.Visible : Visibility.Collapsed; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return value is Visibility && (Visibility)value == Visibility.Visible; } } Utilisation dans la vue

Déclarer en ressource

<Window.Resources> <Views:BooleanToVisibilityConverter x:Key="BooleanToVisibiltyConverter"/> </Window.Resources> … Puis

<Views:LoadingView Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibiltyConverter}}" />

Implémente IValueConverter

Les crochets permettents de récupérer la valeur avec « this »

11

11

g. TargetNullValue

TargetNullValue permet d’éviter les champs vides et apporter une indication à l’utilisateur sur les

données à saisir.

On peut définir directement <TextBox Text="{Binding Description,TargetNullValue='Entrez la description'}" /> On peut églement définir les chaines en ressources, cela permet également de contourner le

problème des « apostrophes »

<sys:String x:Key="NullName">Entrez le nom de l'article</sys:String> (namespace xmlns:sys="clr-namespace:System;assembly=mscorlib" )

On peut ainsi accéder facilement à la ressource depuis l’application et contourner le problème des

apostrophes

<TextBox Text="{Binding ArticleName,TargetNullValue={StaticResource NullName} }"/>

h. Animations

Transitions

Opacité

private void OpacityAnimate(UIElement elementToAnimate, double from, double to, int milliseconds, EventHandler completed = null) { var animation = new DoubleAnimation(from, to, new Duration(TimeSpan.FromMilliseconds(milliseconds))); if (completed != null) animation.Completed += completed; elementToAnimate.BeginAnimation(Control.OpacityProperty, animation); } Utilisation

OpacityAnimate(old, 1, 0, 100); « Slide »

private void HorizontalSlide(UIElement elementToAnimate, double from, double to, int milliseconds, IEasingFunction ease = null, EventHandler completed = null) { var transform = new TranslateTransform(); elementToAnimate.RenderTransform = transform; var animation = new DoubleAnimation(from, to, new Duration(TimeSpan.FromMilliseconds(milliseconds))); if (ease != null) animation.EasingFunction = ease; if (completed != null) animation.Completed += completed; transform.BeginAnimation(TranslateTransform.XProperty, animation); } Utilisation

HorizontalSlide(old, 0, 30, 700, new CubicEase { EasingMode = EasingMode.EaseOut }, (s, Ev) => { old.Visibility = Visibility.Hidden; });

12

12

Contrôle de chargement

Créer un UserControl avec une progressbar par exemple

<UserControl x:Class="MvvmDemo.Views.LoadingView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" > <Grid Background="#99959292"> <ProgressBar Height="39" HorizontalAlignment="Center" VerticalAlignment="Center" Width="210" IsIndeterminate="True"/> </Grid> </UserControl>

Placer ce contrôle en dernier élément du conteneur pricipal de la fenêtre

<Views:LoadingView Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibiltyConverter}}" />

Utilisation d’un converter pour l’afficher ou le masquer.

Bindé à une propriété du ViewModel de la fenêtre principale (« ShellViewModel »)

public class ShellViewModel : ViewModelBase { private bool _isLoading; public bool IsLoading { get { return _isLoading; } set { SetProperty(ref _isLoading, value); } } public ShellViewModel() { MessengerInstance.Subscribe<bool>(Messages.IS_LOADING, (isLoading) => { IsLoading = isLoading; RaisePropertyChanged("IsLoading"); }); } }

Utilisation d’un messenger

pour être notifié

13

13

VisualStates

<Window x:Class="VisualStateManagerDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid x:Name="_layout"> <VisualStateManager.VisualStateGroups> <VisualStateGroup> <VisualState x:Name="Normal"></VisualState> <VisualState x:Name="Fade"> <Storyboard> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="rectangle" Storyboard.TargetProperty="(UIElement.Opacity)"> <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1" /> <SplineDoubleKeyFrame KeyTime="00:00:00.700" Value="0" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <StackPanel> <Button Content="Go" Click="Button_Click"/> <Rectangle x:Name="rectangle" Fill="#FF3232DE" Height="300"/> </StackPanel> </Grid> </Window> Code-behind (sur le click du bouton)

VisualStateManager.GoToElementState(this._layout, "Fade", true);

Avec un UserControl ajouté dans la fenêtre dans l’instance se nommerait « uc » et avec les mêmes

VisualStates

VisualStateManager.GoToState(uc, "Fade", true);

On pourrait également utiliser le comportement blend « GoToStateAction » également.

14

14

2. ViewModel Ensemble des propriétés que la vue aura besoin d’afficher + déclencheurs (commandes)

... utilise les services … méthodes pour aller chercher les données pour remplir les propriétés

(chargement de la page souvent).

Plusieurs « types » de viewmodels : liste, détails affichant le détail d’un élément/ élément courant

Navigation : entre vues, avec passage de paramètre, historique de navigation

Messenger sert à échanger des messages. L’emeteur envoie un message, les abonnés recoivent une

notification

a. Classes de base de ViewModel

ViewModelBase

public class ViewModelBase : ObservableBase, IWpfNavigation { public static IEasyMessenger MessengerInstance { get { return EasyMessenger.Default; } } public virtual void OnNavigatedTo(WpfNavigationEventArgs e) { } public virtual void OnNavigatedFrom(WpfNavigationCancelEventArgs e) { } } ListViewModelBase : classe de base pour les ViewModels affichant une liste

public abstract class ListViewModelBase<T> : ViewModelBase where T : class { private ObservableCollection<T> _items; public ObservableCollection<T> Items { get { return _items; } set { SetProperty(ref _items, value); } } public ListCollectionView DefaultView { get { return (ListCollectionView)CollectionViewSource.GetDefaultView(Items); } } public T CurrentItem { get { return (Items != null) ? DefaultView.CurrentItem as T : null; } set { DefaultView.MoveCurrentTo(value); RaisePropertyChanged(); CurrentItemChanged(); } } protected virtual void CurrentItemChanged() { } }

Liste de « Model » ou liste de

« ViewModel »

Il peut être bon d’ajouter un

messenger de base et la

navigation

Classe iméplement

INotifyPropertyChanged (voir Models)

Pour pouvoir filtrer, trier,

grouper

Elément courant et méthode

permettant d’être notifié du

changement

15

15

b. ViewModel Liste et détails

1ère possibilité : On fait deux vues / ViewModels indépendants et on informe le ViewModel détails du

changement d’élément courant

public class PeopleViewModel : ViewModelBase { private IPeopleService _peopleService; public ObservableCollection<Person> People { get; set; } private Person _currentPerson; public Person CurrentPerson { get { return _currentPerson; } set { SetProperty(ref _currentPerson, value); MessengerInstance.Publish(Messages.CURRENT_PERSON_CHANGED, _currentPerson); } } public PeopleViewModel(IPeopleService peopleService) { _peopleService = peopleService; LoadPeopleAsync(); } public async void LoadPeopleAsync() { MessengerInstance.Publish(Messages.IS_LOADING, true); var peopleList = await _peopleService.getAllAsync(); People = new ObservableCollection<Person>(peopleList); RaisePropertyChanged("People"); MessengerInstance.Publish(Messages.IS_LOADING, false); } } Dans la vue Liste on binde « SelectedItem » à l’élément courant du « ViewModel Liste »

<ListBox ItemsSource="{Binding People}" SelectedItem="{Binding CurrentPerson}"/>

On notifie les abonnés du

changement d’élément courant

avec un Messenger

ViewModel liste

Et datacontext

ViewModel détails

Et datacontext Message current

changed

16

16

ViewModel Détails

public class PersonDetailsViewModel : ViewModelBase { private Person _currentPerson; public Person CurrentPerson { get { return _currentPerson; } set { SetProperty(ref _currentPerson, value); } } public PersonDetailsViewModel() { MessengerInstance.Subscribe<Person>(Messages.CURRENT_PERSON_CHANGED, (person) => { CurrentPerson = person; RaisePropertyChanged("Person"); }); } } La vue Détails, les éléments sont bindés sur l’élément courant du « ViewModel details »

<UserControl … > <StackPanel> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Label>Nom</Label> <TextBlock Grid.Column="1" Text="{Binding CurrentPerson.FirstName}" /> <Label Grid.Row="1">Prénom</Label> <TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding CurrentPerson.LastName}" /> <!-- etc.--> </Grid> </StackPanel> </UserControl>

On définit le DataContext pour chaque Vue

<Views:PeopleView DataContext="{Binding PeopleViewModel, Source={StaticResource ViewModelLocator}}" /> <Views:PersonDetailsView DataContext="{Binding PersonDetailsViewModel, Source={StaticResource ViewModelLocator}}"/>

Abonnement à l’évènement

changement d’élément courant

avec un Messenger

17

17

2ème possibilité

Soit on crée une vue « master details » avec un ViewModel MasterDetails, la vue détail ayant son

DataContext branché sur l’élément courant

ViewModel « MasterDetails »

public class MasterDetailsViewModel : ListViewModelBase<Person> { private IPeopleService _peopleService; public MasterDetailsViewModel(IPeopleService peopleService) { _peopleService = peopleService; LoadPeopleAsync(); } public async void LoadPeopleAsync() { MessengerInstance.Publish(Messages.IS_LOADING, true); var peopleList = await _peopleService.getAllAsync(); Items = new ObservableCollection<Person>(peopleList); RaisePropertyChanged("Items"); MessengerInstance.Publish(Messages.IS_LOADING, false); } } Pas de ViewModel « Details » … la vue est bindée sur CurrentItem du ViewModel « MasterDetails »

Vue « MasterDetails »

<UserControl … DataContext="{Binding MasterDetailsViewModel, Source={StaticResource ViewModelLocator}}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <local:PeopleView /> <local:PersonDetailsView Grid.Column="1" DataContext="{Binding CurrentItem}"/> </Grid> </UserControl>

Adaptation du binding, vue « Liste »

<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding CurrentItem}"/> Et pour la vue d étails on se binde directement aux propriéts de CurrentItem

<Label>Nom</Label> <TextBlock Grid.Column="1" Text="{Binding FirstName}" />

Vue MasterDetails » avec pour DataContext le

ViewModel « MasterDetails »

Vue liste, éléments bindés au DataContext du « parent »

Vue Details bindée sur le CurrentItem du

ViewModel « MasterDetails »

18

18

c. Filtrer, trier, grouper (CollectionViewSource)

Pour l’exemple on a une liste de personnes avec nom (name), email et ville (city). On trie par ordre

alphabétique, on filtre et et groupe par ville.

Dans le ViewModel

public class MainWindowViewModel : ObservableBase { public ObservableCollection<Person> People { get; set; } public ListCollectionView DefaultView { get { return (ListCollectionView)CollectionViewSource.GetDefaultView(People); } } // etc. }

On peut binder la ListBox sur la CollectionViewSource ou sur la liste des éléments

<ListBox ItemsSource="{Binding People}"> <ListBox ItemsSource="{Binding DefaultView}" />

On définit le DataContext de la vue sur le ViewModel.

(On pourrait aussi définir le DataContext de la vue avec une CollectionViewSource) <Window.DataContext> <vm:MainWindowViewModel /> </Window.DataContext> <Window.Resources> <CollectionViewSource x:Key="ViewSource" Source="{Binding People}"/> </Window.Resources> … <ListBox ItemsSource="{Binding Source={StaticResource ViewSource}}" />

Filtrer

public ICommand FilterCommand { get; set; } FilterCommand = new RelayCommand(() => { DefaultView.Filter = (item) => { var person = item as Person; return person.City == "Londres" ? true : false; }; }); Trier

public ICommand SortCommand { get; set; } SortCommand = new RelayCommand(() => { DefaultView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending)); });

19

19

Grouper

public ICommand GroupCommand { get; set; } GroupCommand = new RelayCommand(() => { DefaultView.GroupDescriptions.Add(new PropertyGroupDescription("City")); }); On adapte la ListBox afin d’afficher l’en-tête du groupe

<ListBox ItemsSource="{Binding People}"> <ListBox.GroupStyle> <GroupStyle /> </ListBox.GroupStyle> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding Name}" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox>

Les personnes sont groupées par ville dans la ListBox

20

20

d. Commandes

public class RelayCommandBase : ICommand { protected readonly Delegate _executeMethod; protected readonly Func<Object, bool> _canExecuteMethod; public RelayCommandBase(Delegate executeMethod) : this(executeMethod, (args) => true) { } public RelayCommandBase(Delegate executeMethod, Func<Object, bool> canExecuteMethod) { if (executeMethod == null) throw new ArgumentNullException("execute"); _executeMethod = executeMethod; _canExecuteMethod = canExecuteMethod; } public bool CanExecute(object parameter) { return _canExecuteMethod == null || _canExecuteMethod(parameter); } public virtual void Execute(object parameter) { _executeMethod.DynamicInvoke(parameter); } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } } RelayCommand

public class RelayCommand : RelayCommandBase { public RelayCommand(Action executeMethod): this(executeMethod, () => true) { } public RelayCommand(Action executeMethod, Func<bool> canExecuteMethod) : base(executeMethod, (args) => canExecuteMethod()) { } public bool CanExecute() { return CanExecute(null); } public void Execute() { Execute(string.Empty); } public override void Execute(object parameter) { _executeMethod.DynamicInvoke(); } } RelayCommand « Generic »

public class RelayCommand<T> : RelayCommandBase { public RelayCommand(Action<T> execute): this(execute, (args) => true) { } public RelayCommand(Action<T> executeMethod, Func<Object, bool> canExecuteMethod): base(executeMethod, (args) => canExecuteMethod((T)args)) { } public virtual bool CanExecute(T parameter) { return base.CanExecute(parameter); } }

21

21

CompositeCommand

public class EasyCompositeCommand : ICommand { private readonly IList<ICommand> _commands = new List<ICommand>(); public IList<ICommand> Commands { get { return _commands; } } public virtual void RegisterCommand(ICommand command) { if (command == null) throw new ArgumentNullException("command"); lock (_commands) { if (_commands.Contains(command)) throw new InvalidOperationException(); _commands.Add(command); } } public virtual void UnregisterCommand(ICommand command) { if (command == null) throw new ArgumentNullException("command"); lock (_commands) { _commands.Remove(command); } } public virtual void Execute(object parameter) { foreach (var command in _commands) { command.Execute(parameter); } } public virtual bool CanExecute(object parameter) { bool result = false; ICommand[] commandList; lock (_commands) { commandList = _commands.ToArray(); } foreach (ICommand command in commandList) { if (!command.CanExecute(parameter)) { return false; } result = true; } return result; } public event EventHandler CanExecuteChanged; public void RaiseCanExecuteChanged() { var handler = CanExecuteChanged; if (handler != null) { handler(this, EventArgs.Empty); } } }

22

22

e. Injection de dépendance

Un conteneur marche ainsi :

1. On enregistre dans le conteneur les services, instances, viewmodels, etc.

2. Le conteneur se charge de créer une instance (en prenant en compte les paramètres injectés

dans le constructeur) et la retourner

En simplifiant, un conteneur c’est un dictionnaire, un object builder et une variable static permettant

d’avoir accès depuis n’ importe où dans l’application aux mêmes éléments enregistrés. Il peut y avoir

en plus un cache (permettant une stratégie singleton par exemple).

Unity

NuGet

PM> Install-Package Unity PM> Install-Package CommonServiceLocator

Dans « app » « startup » par exemple

IUnityContainer container = new UnityContainer(); container.RegisterType<IPeopleService, PeopleService>(); container.RegisterType<PeopleViewModel>();

Pour pouvoir retrouver une instance depuis une autre vue, viewmodel, … (puisque l’instance du

conteneur est définie localement on ne retrouverait pas les éléments enregistrés) on utilise

ServiceLocator (Microsoft.Practices.ServiceLocation). (ServiceLocator n’est pas compris avec Unity, il

faut donc l’installer séparément avec les packages NuGet)

IUnityContainer container = new UnityContainer(); ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(container)); container.RegisterType<IPeopleService, PeopleService>(); container.RegisterType<PeopleViewModel>();

Retrouver une instance

var vm = ServiceLocator.Current.GetInstance<PeopleViewModel>();

Exemple de ViewModel

public class PeopleViewModel : ViewModelBase { private IPeopleService _peopleService; public PeopleViewModel(IPeopleService peopleService) { _peopleService = peopleService; } }

Injection dans le constructeur

du service

23

23

f. « ViewModel First »

« App »

Enregistrement des vues, viewmodels et services avec un conteneur

public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); IUnityContainer container = new UnityContainer(); var unity = new UnityServiceLocator(container); ServiceLocator.SetLocatorProvider(() => unity); container.RegisterType<IPeopleView, PeopleView>(); container.RegisterType<PeopleViewModel>(); } }

Public IView1 view1

Public IView2 view2

… retournent la vue

du viewmodel

(conteneur)

Affectation du

datacontext de la vue à

ce viewmodel (this)

IView1

View1

(UserControl par ex

implémentant

IView1)

Fenêtre principale

avec conteneurs

ContentControl , contenu

bindé à une vue de

« ShellViewModel »

ShellViewModel

Enregistrement

vues,

viewmodels,

services, dans

conteneur

ViewModel avec

injection d’une IView1

App

Views ViewModels

24

24

ViewModel

public class PeopleViewModel :ObservableBase { public IPeopleView View { get; private set; } public ObservableCollection<Person> People { get; set; } public ICommand AddPersonCommand { get; set; } public PeopleViewModel(IPeopleView view) { View = view; View.DataContext = this; AddPersonCommand = new RelayCommand(() =>{ }); LoadPeople(); } public void LoadPeople() { var peopleList = new List<Person>(); peopleList.Add(new Person("Marie", "[email protected]")); peopleList.Add(new Person("Joey", "[email protected]")); People = new ObservableCollection<Person>(peopleList); RaisePropertyChanged("People"); } } Vues

Interface

public interface IPeopleView { object DataContext { get; set; } } UserControl

public partial class PeopleView : UserControl, IPeopleView { public PeopleView() { InitializeComponent(); } } Xaml

<UserControl x:Class="ViewModelFirstDemo.Views.PeopleView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="25"/> <RowDefinition /> </Grid.RowDefinitions> <Button Content="Ajouter" Command="{Binding AddPersonCommand}" /> <ListBox ItemsSource="{Binding People}" Grid.Row="1"/> </Grid> </UserControl>

Implémente l’interface

Vue injectée

25

25

Fenêtre principale

<Window xmlns:Views="clr-namespace:ViewModelFirstDemo.Views" x:Class="ViewModelFirstDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <ContentControl Content="{Binding Main}"></ContentControl> </Grid> </Window> Le ViewModel de la fenêtre principale

public class ShellViewModel { public IPeopleView Main { get { var vm = ServiceLocator.Current.GetInstance<PeopleViewModel>(); return vm.View; } } }

Contenu des conteneurs

bindé aux vues du

ShellViewModel

26

26

g. Navigation

Navigation possible

- de vue à vue

- de vue à viewmodel ou de ViewModel à vue

- de viewmodel à viewmodel : Les ViewModels héritent de ViewModelBase. Les vues

doivent hériter d’une vue spéciale (WpfView) permettant le lien entre le service de

navigation et les viewmodels

La classe WpfView public class WpfView : ContentControl,IWpfNavigation { public virtual void OnNavigatedFrom(WpfNavigationCancelEventArgs e) { var vm = DataContext as ViewModelBase; vm.OnNavigatedFrom(e); } public virtual void OnNavigatedTo(WpfNavigationEventArgs e) { var vm = DataContext as ViewModelBase; vm.OnNavigatedTo(e); } } Exemple de vue permettant Héritant de « WpfView » qui permettra la navigation vers le ViewModel

défini en DataContext (avec passage de paramètre par exemple)

Récupère le ViewModel

défini en DataContext et

appelle ses méthodes

« OnNavigated… »

27

27

<wlw:WpfView x:Class="MvvmDemo.Views.PersonEditView" … xmlns:wlw="clr-namespace:EasyMvvm.Navigation;assembly=EasyMvvm" DataContext="{Binding PersonEditViewModel,Source={StaticResource ViewModelLocator}}"> <Grid> </Grid> </wlw:WpfView>

Le service au plus simple se contente de créer instance de la vue, puis remplace le contenu du

conteneur (Application.Current.MainForm) et si la vue implémente IWpfNavigation alors appelle la

méthode OnNavigated… soit c’est juste une vue qui implémente IWpfNavigation et la navigation

s’arrête à la vue, soit c’est une vue qui hérite de WpfView (classe implémentant IWpfNavigation)

alors la navigation est faite vers le viewmodel de la vue.

Exemple simplifié de Service de Navigation

public class WpfNavigationService { private static WpfNavigationService _default; public static WpfNavigationService Default { get { return _default ?? (_default = new WpfNavigationService()); } } public void Navigate(Type page) { Navigate(page, null); } public void Navigate(Type page, object parameter) { // création d'instance de la page var pageToGo = Activator.CreateInstance(page); // remplacement du contenu de la fenêtre principale de l'application Application.Current.MainWindow.Content = pageToGo; // passer le paramètre à la nouvelle vue, vm DoNavigatedTo(pageToGo, parameter); } private void DoNavigatedTo(object pageToGo, object parameterToGo = null) { var context = new WpfNavigationEventArgs(pageToGo.GetType(), parameterToGo); if (typeof(IWpfNavigation).IsAssignableFrom(pageToGo.GetType())) { ((IWpfNavigation)pageToGo).OnNavigatedTo(context); } } } A cela on pourrait ajouter un historique de navigation (Journal) pour permettre la navigation retour

par exemple

28

28

h. Messenger

Messenger sert à échanger des messages. L’emeteur envoie un message, les abonnés recoivent une

notification. Un « bon » Messenger utilise également les « WeakReference » pour savoir si les

abonnés n’ont pas été collectés par le GC et éviter les fuites de mêmoire.

Exemple de Messenger en version simplifiée

public class Messenger : IMessenger { private static Dictionary<string, List<Delegate>> _container = new Dictionary<string, List<Delegate>>(); // pour être juste notifié sans paramètre public void Subscribe(string message, Action callback) { if (!_container.ContainsKey(message)) { _container[message] = new List<Delegate>(); } _container[message].Add(callback); } // notifié et passage de paramètre public void Subscribe<T>(string message, Action<T> callback) { if (!_container.ContainsKey(message)) { _container[message] = new List<Delegate>(); } _container[message].Add(callback); } public void Publish(string message) { if (_container.ContainsKey(message)) { foreach (Delegate execute in _container[message]) { execute.DynamicInvoke(); } } } public void Publish(string message, object parameter) { if (_container.ContainsKey(message)) { foreach (Delegate execute in _container[message]) { execute.DynamicInvoke(parameter); } } } } S’abonner _messenger.Subscribe<string>("message", (response) => { });

Publier _messenger.Publish("message", "Nofication …");

29

29

i. ServiceLocator

Version simplifiée

public class ServiceLocator { private Dictionary<Type, object> _container = new Dictionary<Type, object>(); private static readonly ServiceLocator instance = new ServiceLocator(); public static ServiceLocator Instance { get { return instance; } } public void Register<T>(T element) { _container.Add(typeof(T), element); } public T Retrieve<T>() { object retrieved; if (_container.TryGetValue(typeof(T), out retrieved)) return (T)retrieved; return default(T); } public void Unregister<T>() { if (_container.ContainsKey(typeof(T))) { _container.Remove(typeof(T)); } } } Utilisation

Enregistrement (Dans « OnStartup » de App.xaml) ServiceLocator.Instance.Register<IMessenger>(new Messenger()); Retrouver un service IMessenger _messenger = ServiceLocator.Instance.Retrieve<IMessenger>();

30

30

j. DialogService

Exemple de service de boite de dialogues

public interface IDialogService { bool YesNo(string title, string message); string OpenFile(string title); void ShowMessage(string title, string message); }

public class DialogService : IDialogService { public bool YesNo(string title, string message) { return MessageBoxResult.Yes == MessageBox.Show(message, title, MessageBoxButton.YesNo, MessageBoxImage.Question); } public void ShowMessage(string title, string message) { MessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Information); } public string OpenFile(string title) { OpenFileDialog dialog = new OpenFileDialog() { Title = title, Multiselect = false }; if (dialog.ShowDialog().HasValue) return dialog.FileName; else return string.Empty; } }

k. Design Time data

Dans le ViewModel

public class PeopleViewModel : ObservableBase { public ObservableCollection<Person> People { get; set; } public PeopleViewModel() { if (DesignerProperties.GetIsInDesignMode(new DependencyObject())) { var peopleList = new List<Person>(); peopleList.Add(new Person(1, "Marie", "Bellin", "[email protected]")); peopleList.Add(new Person(2, "Luc", "Blanc", "[email protected]")); People = new ObservableCollection<Person>(peopleList); } } }

On peut également le définir dans le code-behind des vues par exemple.

Affichage de données en mode

design

31

31

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:InDesign" mc:Ignorable="d" x:Class="MvvmDemo.MainWindow" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <local:PeopleViewModel /> </Window.DataContext> <Grid> <ListBox ItemsSource="{Binding People}" /> </Grid> </Window>

SampleData avec Blend

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:InDesign" mc:Ignorable="d" x:Class="InDesign.MainWindow" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <local:PeopleViewModel /> </Window.DataContext> <StackPanel> <ListBox ItemsSource="{Binding People}" d:DataContext="{Binding Source={StaticResource SampleDataSource}}" ItemTemplate="{DynamicResource PeopleItemTemplate}" /> </StackPanel> </Window>

On peut utiliser la création de classe pour binder une seule valeur, à un TextBlock par exemple. On

peut également créer sa propre classe :

<data:Person xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:data = "clr-namespace:MvvmDemo" Name="Jerome" Email="[email protected]"> </data:Person>

<TextBlock d:DataContext="{d:DesignData Source=SampleData/MyData.xaml}" Text="{Binding Name}"/>

32

32

3. Models

a. ObservableBase

Classe de base pour les « Models » et utilisée par « ViewModelBase » pour la notification de

l’interface utilisateur qu’une propriété a changé.

public abstract class ObservableBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null) { if (object.Equals(storage, value)) return false; storage = value; RaisePropertyChanged(propertyName); return true; } protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null) { var handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } } Utilisation

public class Person : ObservableBase { private string _firstName; public string FirstName { get { return _firstName; } set { SetProperty(ref _firstName, value); RaisePropertyChanged("FullName"); } } // etc. private string _email; public string Email { get { return _email; } set { SetProperty(ref _email, value); } } public string FullName { get { return string.Format("{0} {1}", FirstName, LastName); } } }

On raise avec un nom de

propriété pour notifier du

changement de FullName

Enregistrement et notification

33

33

b. Validation

1. Validation sur exceptions

1. Model : Déclencher une exception si une valeur ne correspond pas à celle attendue

private string _firstName; public string FirstName { get { return _firstName; } set { if (string.IsNullOrEmpty(value)) { throw new ArgumentException("Prénom requis."); } SetProperty(ref _firstName, value); } }

2. Vue : Ajouter « ValidatesOnException » au binding

<TextBox Grid.Column="1" Text="{Binding CurrentPerson.FirstName, ValidatesOnExceptions=True}" />

3. Désactiver l’exception

2. ValidationrRule

Spécifique à Wpf

1. On crée une classe héritant de ValidationRule avec une méthode validate retournant un

« ValidationResult »

public class TwitterValidationRule : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { var regex = new Regex("^@([A-Za-z0-9_]+)"); var match = regex.Match(value.ToString()); if (match == null || match == Match.Empty) { return new ValidationResult(false, "Twitter invalide"); } else { return ValidationResult.ValidResult; } } }

34

34

2. Vue

<Label Grid.Row="2">Twitter</Label> <TextBox Grid.Column="1" Grid.Row="2"> <TextBox.Text> <Binding Path="CurrentPerson.Twitter"> <Binding.ValidationRules> <local:TwitterValidationRule /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox>

ValidationRule réutilisable

public class RegexValidationRule : ValidationRule { public string Expression { get; set; } public string ErrorMessage { get; set; } public override ValidationResult Validate(object value, CultureInfo cultureInfo) { if (Expression == null) return ValidationResult.ValidResult; if (string.IsNullOrEmpty(ErrorMessage)) ErrorMessage = "Format invalide"; var regex = new Regex(Expression); var match = regex.Match(value.ToString()); if (match == null || match == Match.Empty) { return new ValidationResult(false, ErrorMessage); } else { return ValidationResult.ValidResult; } } }

<TextBox Grid.Column="1" Grid.Row="2"> <TextBox.Text> <Binding Path="CurrentPerson.Twitter"> <Binding.ValidationRules> <local:RegexValidationRule Expression="^@([A-Za-z0-9_]+)" ErrorMessage="Twitter invalide!"/> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox>

35

35

3. IDataErrorInfo

public class Person :INotifyPropertyChanged, IDataErrorInfo { private string _firstName; public string FirstName { get { return _firstName; } set { if (_firstName != value) { _firstName = value; RaisePropertyChanged(); } } } // etc. public string Error { get { return string.Empty; } } public string this[string propertyName] { get { return GetErrorForProperty(propertyName); } } public string GetErrorForProperty(string propertyName) { switch (propertyName) { case "FirstName": if (string.IsNullOrEmpty(FirstName)) { return "Prénom requis"; } else { return string.Empty; } default: return string.Empty; } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null) { var handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } }

Dans la vue

<TextBox Text="{Binding CurrentPerson.FirstName,ValidatesOnDataErrors=True}" />

Ajouter « ValidatesOnDataErrors »

Erreur pour l’objet ayant les propriétés

On teste la valeur de la propriété

36

36

4. INotifyDataErrorInfo

public class Person : INotifyPropertyChanged, INotifyDataErrorInfo { public int Id { get; private set; } private string _firstName; public string FirstName { get { return _firstName; } set { if (_firstName != value) { _firstName = value; RaisePropertyChanged(); GetErrorsForFirstName(FirstName).ContinueWith((errorsTask) => { lock (_propertyNameAndErrors) { if (errorsTask.Result.Count > 0) { _propertyNameAndErrors["FirstName"] = errorsTask.Result; RaiseErrorsChanged("FirstName"); } } }); } } } public Task<List<string>> GetErrorsForFirstName(string value) { return Task.Factory.StartNew<List<string>>(() => { var result = new List<string>(); if (string.IsNullOrEmpty(value)) { result.Add("Prénom requis!!!!!!"); } return result; }); } private Dictionary<string, List<string>> _propertyNameAndErrors = new Dictionary<string, List<string>>(); public IEnumerable GetErrors(string propertyName) { lock (_propertyNameAndErrors) { if (_propertyNameAndErrors.ContainsKey(propertyName)) { return _propertyNameAndErrors[propertyName]; } return null; } }

37

37

public bool HasErrors { get { return _propertyNameAndErrors.Count > 0; } } public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; private void RaiseErrorsChanged(string propertyName) { var handler = ErrorsChanged; if (handler != null) { handler(this, new DataErrorsChangedEventArgs(propertyName)); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null) { var handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } }

5. Une classe de validation réutlisable

public class ValidableBase : ObservableBase, INotifyDataErrorInfo, IEditableObject { private Dictionary<string, List<string>> _propertyNameAndErrors = new Dictionary<string, List<string>>(); public void ValidateProperty(string propertyName) { if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException("propertyName"); var propertyInfo = this.GetType().GetProperty(propertyName); if (propertyInfo == null) throw new ArgumentException("Invalid property name", propertyName); // validation var validationResults = new List<ValidationResult>(); var context = new ValidationContext(this, null, null) { MemberName = propertyInfo.Name }; var value = propertyInfo.GetValue(this, null); var propertyErrors = new List<string>(); bool isValid = Validator.TryValidateProperty(value, context, validationResults); if (validationResults.Any()) { propertyErrors.AddRange(validationResults.Select(c => c.ErrorMessage)); } // erreurs ou clear? var hasCurrentValidationResults = _propertyNameAndErrors.ContainsKey(propertyName); var hasNewValidationResults = propertyErrors != null && propertyErrors.Count() > 0; if (hasCurrentValidationResults || hasNewValidationResults) {

38

38

if (hasNewValidationResults) { _propertyNameAndErrors[propertyName] = propertyErrors; } else { _propertyNameAndErrors.Remove(propertyName); } RaiseErrorsChanged(propertyName); } } protected override bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null) { var result = base.SetProperty(ref storage, value, propertyName); if (result && !string.IsNullOrEmpty(propertyName)) { ValidateProperty(propertyName); } return result; } #region INotifyDataError implemented public IEnumerable GetErrors(string propertyName) { // retourne la ou les erreurs pour la propriété (exemple : email) if (string.IsNullOrEmpty(propertyName)) return new List<string>(); var propertyErrors = new List<string>(); if (_propertyNameAndErrors.TryGetValue(propertyName, out propertyErrors)) { return propertyErrors; } return new List<string>(); } // retourne si l'élément courant?? a des erreurs public bool HasErrors { get { return _propertyNameAndErrors.Count > 0; } } // déclenché quand une erreur est trouvée public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; private void RaiseErrorsChanged(string propertyName) { var handler = ErrorsChanged; if (handler != null) { handler(this, new DataErrorsChangedEventArgs(propertyName)); } } #endregion #region IEditableObject implemented private Dictionary<string, object> _propertyNameAndValues; public void BeginEdit()

39

39

{ _propertyNameAndValues = new Dictionary<string, object>(); var properties = this.GetType().GetProperties().Where(p => p.CanRead && p.CanWrite); foreach (var property in properties) { _propertyNameAndValues.Add(property.Name, property.GetValue(this, null)); } } public void CancelEdit() { if (_propertyNameAndValues != null) { var properties = this.GetType().GetProperties().Where(p => p.CanRead && p.CanWrite); foreach (var property in properties) property.SetValue(this, _propertyNameAndValues[property.Name], null); _propertyNameAndValues = null; } } public void EndEdit() { _propertyNameAndValues = null; RaisePropertyChanged(string.Empty); } #endregion // IEditableObject }

Utilisation ajouter des data annotations sur le modèle héritant de « ValidableBase »

Dans le ViewModel on peut vérifier que que l’élément n’a pas d’erreurs avant par exemple une

modification vers une base de données

ValidateCommand = new RelayCommand(() => { if ( !_currentPerson.HasErrors) { } });

40

40

6. Templates

Style pour afficher les erreurs en ToolTip

<Style TargetType="TextBox"> <Style.Triggers> <Trigger Property="Validation.HasError" Value="true"> <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}" /> </Trigger> </Style.Triggers> </Style>

On peut définir également un template …

<TextBox Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}" Text="{Binding CurrentPerson.Twitter,ValidatesOnDataErrors=True}" />

41

41

II. Mvvm Light http://www.galasoft.ch/mvvm/

1-Installation

Plusieurs possibilités :

Installer les Templates Mvvm Light pour Visual Studio

Ou simplement installer les libraires dans son projet via le gestionnaire de Package NuGet

(Depuis la console)

42

42

PM> install-package mvvmlight

a. Templates Mvvm Light

Pour Wpf(WPF4 et WPF45), SilverLight(SL5),Windows Phone(WP8) et Windows(Win8 et

Win81)

b. Items Mvvm Light

disponibles pour Windows , Windows Phone, Wpf et Silverlight

MvvmView : crée une vue avec le DataContext défini sur le ViewModelLocator

MvvmViewModel : crée un ViewModel qui hérite de ViewModelBase

MvvmViewModelLocator : crée un ViewModelLocator + Utilisation ServiceLocator

c. Snippets

43

43

d. Projet « from scratch »

Installer les librairies Mvvm Light puis utiliser les items proposés par Mvvm Light.

2-Model

Les modèles dérivent de « ObservableObject »

ObservableObject :

Implémente INotifyPropertyChanging et INotifyPropertyChanged

Offre des méthodes pour simplement notifier d'un changement de valeur

(RaisePropertyChanging et RaisePropertyChanged)

Offre des méthodes effectuant la mise à jour de la valeur de la propriété puis déclenchant

RaisePropertyChanged (Set<T>)

public class Person : ObservableObject { }

1ère possibilité – Notification simple RaisePropertyChanged(); RaisePropertyChanged("FullName"); RaisePropertyChanged<string>(() => FirstName);

2ème possibilité – Modification de la valeur + Notification

(Quelques-unes des écritures possibles) Set(ref _firstName, value); Set(ref _firstName, value,"FirstName"); Set(() => FirstName, ref _firstName, value); Set<string>(ref _firstName, value); Set<string>(ref _firstName, value,"FirstName"); Set<string>("FirstName", ref _firstName, value); Set<string>(() => FirstName, ref _firstName, value); Concrètement on aura tendance à utiliser

Set<string>(() => FirstName, ref _firstName, value);

Set retourne un booléen indiquant si PropertyChanged a été déclenché.

if (Set<string>(() => FirstName, ref _firstName, value)) { // IsDirty = true; }

44

44

3. ViewModel

Les ViewModels héritent de « ViewModelBase ».

ViewModelBase :

Hérite de « ObservableObject »

Offre de nouvelles signatures pour RaisePropertyChanged et Set<T>

Exemple de ViewModel simple

using GalaSoft.MvvmLight; using MvvmLightDemo.Models; using System.Collections.ObjectModel; namespace MvvmLightDemo.ViewModels { public class PeopleViewModel : ViewModelBase { private ObservableCollection<Person> _people; public ObservableCollection<Person> People { get { return _people; } set { Set<ObservableCollection<Person>>(() => People, ref _people, value); } } } }

a-Commandes

RelayCommand et RelayCommand<T> (T étant le type du paramètre passé) .Implémentent

ICommand.

+ RaiseCanExecuteChanged

b-Messenger

Communication de « messages » entre ViewModels

Exemple On a deux vues, chacune ayant un ViewModel .On s’abonne dans le ViewModel « détail »

afin d’afficher l’élément couramment sélectionné.

Register

Messenger.Default.Register<Person>(this, p => { this.Person = p; }); Send

Messenger.Default.Send<Person>(CurrentPerson);

On pourrait aussi créer une classe générique.

45

45

Register

Messenger.Default.Register<NotificationMessage<Person>>(this, n => { this.Person = n.Content; }); Send

Messenger.Default.Send<NotificationMessage<Person>>(new NotificationMessage<Person>(CurrentPerson,"Key01"));

4. SimpleIoC

Utilisation de Microsoft.Practices.ServiceLocator

Constructor Injection et Property Injection

public class ViewModelLocator { public PeopleViewModel PeopleViewModel { get { return SimpleIoc.Default.GetInstance<PeopleViewModel>(); } } public PersonViewModel PersonViewModel { get { return SimpleIoc.Default.GetInstance<PersonViewModel>(); } } public ViewModelLocator() { ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); // Services SimpleIoc.Default.Register<IDialogService, DialogService>(); SimpleIoc.Default.Register<IPeopleService, PeopleService>(); // ViewModels SimpleIoc.Default.Register<PersonViewModel>(); SimpleIoc.Default.Register<PeopleViewModel>(); } }

Injection de dépendances .Le ViewModel n’a pas de constructeur par défaut

Utilisation du ViewModelLocator pour définir le DataContext des vues

o App.xaml

46

46

<Application x:Class="MvvmLightDemo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:MvvmLightDemo.ViewModels" StartupUri="MainWindow.xaml"> <Application.Resources> <vm:ViewModelLocator x:Key="ViewModelLocator" /> </Application.Resources> </Application>

o Vue

<Window x:Class="MvvmLightDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" DataContext="{Binding PeopleViewModel, Source={StaticResource ViewModelLocator}}">

Référencement du ViewModelLocator

Puis utilisation dans les vues

47

47

III. Prism

1. Installation PM> Install-Package Prism PM> Install-Package Prism.UnityExtensions

Documentation : Prism 5.0 (Wpf/ .Net 4.5), Prism 4.1 (Silverlight 5, Windows Phone 7, et Wpf/.Net

4.0)

Lirairies portables (Prism 5.0):

« Prism.Mvvm » : commandes (Delegate et composite commands), BindableBase, IView,

etc.

« Prism.PubSubEvents » : Envoi de messages aux abonnés

2. Mémento

Shell

Une région

Une autre

région

ShellViewModel (peut

implémenter une interface)

Un module

Vues (implémentent IView), DataContext

sur le viewmodel injecté

ViewModel (peut

implémenter une interface)

Vue pour la navigation

Classe de configuration du module

(implémentant IModule). Enregistrement

des services, viewmodels dans le

conteneur. Enregistrement des vues pour

une région

Interface du

viewmodel Enregistrement, création du

shell. Enregistrement des

modules, services partagés

Un module par

« feature »

Bootstrapper

(Unity ou MEF)

Lancement de l’application

(création d’une instance du

boostrapper)

App (startup)

Les régions sont des

ContentControls,

ItemsControls ou autre

(avec adaptation de région)

Projet « Infrastructure », classes

partagées par les différents projets

Projet de services partagés par

plusieurs modules

« Main » projet

48

48

a. Shell

Shell- Création de la fenêtre principale de l’application

Supprimer « MainWindow » et créer une nouvelle fenêtre nomée « Shell ». Supprimer « StartupUri »

du fichier « App »

b. Boostrapper

Pour utiliser un boostrapper avec Unity installer …

PM> Install-Package Prism.UnityExtensions

public class Bootstrapper : UnityBootstrapper { protected override DependencyObject CreateShell() { return Container.TryResolve<Shell>(); } protected override void InitializeShell() { base.InitializeShell(); App.Current.MainWindow = (Window)Shell; App.Current.MainWindow.Show(); } }

c. Lancement de l’application

public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var bootstrapper = new Bootstrapper(); bootstrapper.Run(); } } Processus du bootstrapper :

LoggerFacade Module CatalogContainerRegion Adapter mappingsRegion

BehaviorsException TypesCreate ShellInitialize ShellInitialize Modules

d. Régions

Définir les regions du Shell

<Window … xmlns:prism="http://www.codeplex.com/prism"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition/> </Grid.RowDefinitions> <ContentControl prism:RegionManager.RegionName="ToolbarRegion" /> <ContentControl prism:RegionManager.RegionName="ContentRegion" Grid.Row="1" /> </Grid> </Window>

Ajouter le namespace

Exemple enregistrement de 2 régions

Définition et affichage de la

fenêtre principale

49

49

Pour éviter les erreurs de saisie on peut créer une classe de variables static dans le projet

« Infrastructure »

public class RegionNames { public static string ToolbarRegion = "ToolbarRegion"; public static string ContentRegion = "ContentRegion"; } … Adaptation du code du Shell

<Window … xmlns:prism="http://www.codeplex.com/prism" xmlns:inf ="clr-namespace:PrismDemo.Infrastructure;assembly=PrismDemo.Infrastructure" > <Grid> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition/> </Grid.RowDefinitions> <ContentControl prism:RegionManager.RegionName="{x:Static inf:RegionNames.ToolbarRegion}" /> <ContentControl prism:RegionManager.RegionName="{x:Static inf:RegionNames.ContentRegion}" Grid.Row="1" /> </Grid> </Window>

QuickStart : UI Composition

Toolbar et navigation

Exemple de région

<ItemsControl prism:RegionManager.RegionName="{x:Static inf:RegionNames.ToolbarRegion}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl>

Utiliser un ItemsControl ou créer une adaptation de régions

pour que les différentes vues de navigation s’ajoutent dans

une région (« ToolbarRegion » par exemple)

50

50

Adaptation de région

Pour les autres conteneurs que « ContentControl » et « ItemsControl », il faut faire une adaptation

de région pour qu’ils sachent comment ajouter les vues à la région.

- Dérive de RegionAdapter<T> T étant le type de « conteneur » à adapter)

- Implemente les méthodes « Adapt » et « CreateRegion »

Exemple avec StackPanel

public class StackPanelRegionAdapter : RegionAdapterBase<StackPanel> { public StackPanelRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory) : base(regionBehaviorFactory) { } protected override void Adapt(IRegion region, StackPanel regionTarget) { region.Views.CollectionChanged += (s, e) => { if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add) { foreach (FrameworkElement element in e.NewItems) { regionTarget.Children.Add(element); } } }; } protected override IRegion CreateRegion() { return new AllActiveRegion(); } }

Shell

<StackPanel Orientation="Horizontal" prism:RegionManager.RegionName="{x:Static inf:RegionNames.ToolbarRegion}"/>

Bootstrapper

protected override RegionAdapterMappings ConfigureRegionAdapterMappings() { RegionAdapterMappings mappings = base.ConfigureRegionAdapterMappings(); mappings.RegisterMapping(typeof(StackPanel), Container.Resolve<StackPanelRegionAdapter>()); return mappings; }

c. ShellViewModel

public interface IShellViewModel : IViewModel { }

public class ShellViewModel :ViewModelBase , IShellViewModel { private readonly IRegionManager _regionManager; public ICommand NavigateCommand { get; private set; }

51

51

public ShellViewModel(IRegionManager regionManager) { _regionManager = regionManager; NavigateCommand = new DelegateCommand<object>(Navigate); GlobalCommands.NavigateCommand.RegisterCommand(NavigateCommand); } private void Navigate(object navigatePath) { if (navigatePath != null) _regionManager.RequestNavigate("ContentRegion", navigatePath.ToString(), NavigationComplete); } private void NavigationComplete(NavigationResult result) { //log } } Code-behind du Shell

public partial class Shell : Window { public Shell(IShellViewModel viewModel) { InitializeComponent(); DataContext = viewModel; } } Dans le bootstrapper

protected override void ConfigureContainer() { base.ConfigureContainer(); ((UnityContainer)Container).RegisterType<IShellViewModel, ShellViewModel>(); }

52

52

d. Modules

Installation pour chaque module

PM> Install-Package Prism PM> Install-Package Prism.UnityExtensions

Et référencer le projet « Infrastructure »

Configuration du module

public class TeachersModule : IModule { private IUnityContainer _container; private IRegionManager _regionManager; public TeachersModule(IUnityContainer container, IRegionManager regionManager) { _container = container; _regionManager = regionManager; } public void Initialize() { // conteneur : viewmodels, services du module,... _container.RegisterType<IAddTeacherViewModel, AddTeacherViewModel>(); _container.RegisterType<ITeachersViewModel, TeachersViewModel>(); // vues // navigation _regionManager.RegisterViewWithRegion(RegionNames.ToolbarRegion, typeof(TeachersNavigationView)); // enregistrement de chaque vue pour une région _regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(AddTeacherView)); _regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(TeachersView)); } }

Enregistrement des

« viewmodels » et services dans le

conteneur

Enregistrement des vues pour

une région et la navigation

Injection du conteneur et de regionManager

53

53

Avoir plus de contrôle sur la vue affichée (Activate/ Deactivate)

La méthode « RegisterViewWithRegion » offre peu de contrôle sur la vue.

Exemple on a deux vues enregistrées pour la region « ContentRegion ». On choisit quelle vue afficher

public void Initialize() { // … _regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(AddTeacherView)); var view = _container.Resolve<TeachersView>(); IRegion contentRegion = _regionManager.Regions[RegionNames.ContentRegion]; contentRegion.Add(view); contentRegion.Activate(view); }

Module Lifetime

Register Modules Discover Modules Load Modules Initialize Modules

Register/Discover Modules : par code, Module Catalog,Xaml, Configuration (Xaml),Wpf

Loading Modules : Wpf, load On demand/When available

Initialize Modules : IModule.Initialize(), register types/services/views, subscribe to

services/events

On peut créer une interface base IViewModel dans le projet « Infrastructure »

public interface IViewModel { }

ViewModel

public interface ITeachersViewModel : IViewModel { }

public class TeachersViewModel : ViewModelBase, ITeachersViewModel { // etc. }

Vue

La implémente IView (Microsoft.Practices.Prism.Mvvm)

public partial class TeachersView : UserControl, IView { public TeachersView(ITeachersViewModel viewModel) { InitializeComponent(); DataContext = viewModel; } }

On pourrait avoir plusieurs « ViewModels » implémentant

« ITeachersViewModel » et décider avec le conteneur quel

viewmodel injecter dans la vue « TeachersView »

Le viewmodel implémente l’interface.

Injecter les services dont le viewmodel a

besoin

On récupère la vue.

« ViewModel First »

on récupèrerait la vue

du viewmodel

On peut activer, désativer

la vue

54

54

Adaptation du Bootstrapper

Référencer le « Module »

Dans le « Bootstrapper »

protected override IModuleCatalog CreateModuleCatalog() { var catalog = new ModuleCatalog(); catalog.AddModule(typeof(TeachersModule)); // autres modules .. return catalog; }

« ViewModel First »

Vue

IView

ITeachersView

public interface ITeachersView : IView { }

TeachersView

public partial class TeachersView : UserControl, ITeachersView { public TeachersView() { InitializeComponent(); } }

55

55

ViewModel

IViewModel

public interface IViewModel { IView View { get; set; } }

ITeachersViewModel

public interface ITeachersViewModel : IViewModel { }

TeachersViewModel

public class TeachersViewModel : ViewModelBase, ITeachersViewModel { private ITeachersService _teachersService; public ObservableCollection<Teacher> Teachers { get; set; } public ITeachersView View { get; set; } public TeachersViewModel(ITeachersView view, ITeachersService teachersService) { _teachersService = teachersService; View = view; View.DataContext = this; var list = _teachersService.GetAll(); Teachers = new ObservableCollection<Teacher>(list); OnPropertyChanged("Teachers"); } }

Enregistrement ( configuration du module)

public void Initialize() { _container.RegisterType<ITeachersView, TeachersView>(); _container.RegisterType<ITeachersViewModel, TeachersViewModel>(); var vm = _container.Resolve<ITeachersViewModel>(); IRegion contentRegion = _regionManager.Regions[RegionNames.ContentRegion]; contentRegion.Add(vm.View); contentRegion.Activate(vm.View); }

56

56

Module chargé à la demande

1. Dans le bootstrapper

protected override IModuleCatalog CreateModuleCatalog() { var catalog = new ModuleCatalog(); catalog.AddModule(typeof(TeachersModule)); // On demand var moduleType = typeof(CoursesModule); catalog.AddModule(new ModuleInfo() { ModuleName = "CoursesModule", ModuleType = moduleType.AssemblyQualifiedName, InitializationMode = InitializationMode.OnDemand }); return catalog; }

2. Pour charger le module il faut injecter « IModuleManager moduleManager » dans le

ViewModel

3. Charger le module puis naviguer vers la vue désirée du module

_moduleManager.LoadModule("CoursesModule"); _regionManager.RequestNavigate("ContentRegion", "CoursesView");

QuickStart: Modularity

e. Navigation

QuickStart : ViewSwitchingNavigation

Navigation avec le « regionManager »

Il faut injecter IRegionManager dans le ViewModel

public class TeachersViewModel : ViewModelBase, ITeachersViewModel { private IRegionManager _regionManager; public DelegateCommand AddTeacherCommand { get; private set; } public TeachersViewModel(IRegionManager regionManager) { _regionManager = regionManager; AddTeacherCommand = new DelegateCommand(() => { regionManager.RequestNavigate(RegionNames.ContentRegion, "AddTeacherView"); }); } }

Utilisation de la méthode

« RequestNavigate » du

RegionManager à laquelle on

passe le nom de la vue (ou

l’uri) vers laquelle naviguer

57

57

Avec passage de paramètre

var uri = string.Format("AddTeacherView?title={0}", "Ajout d'un nouvel enseignant"); _regionManager.RequestNavigate(RegionNames.ContentRegion,uri);

Pour récupèrer le paramètre, le viewmodel « récepteur » doit implémenter INavigationAware

public class AddTeacherViewModel : ViewModelBase, IAddTeacherViewModel, INavigationAware { public string Title { get; set; } // etc. public bool IsNavigationTarget(NavigationContext navigationContext) { return true; } public void OnNavigatedFrom(NavigationContext navigationContext) { } public void OnNavigatedTo(NavigationContext navigationContext) { var parameters = navigationContext.Parameters; if (parameters != null) { Title = parameters["title"].ToString(); OnPropertyChanged("Title"); } } }

Processus de navigation avec INavigationAware :

RequestNavigateOnNavigatedFromIsNavigationTargetResolveViewOnNavigatedToNaviga

tionComplete

Avec NavigationParameters

var parameters = new NavigationParameters(); parameters.Add("title", "Ajout d'un nouvel enseignant!"); _regionManager.RequestNavigate("ContentRegion", new Uri("AddTeacherView", UriKind.Relative), parameters);

Dans le ViewModel Récepteur

public void OnNavigatedTo(NavigationContext navigationContext) { var parameters = navigationContext.Parameters as NavigationParameters; if (parameters != null && parameters["title"] != null) { Title = parameters["title"].ToString(); OnPropertyChanged("Title"); } }

Avec Prism 4.1 on peut utiliser UriQuery

58

58

Création d’une commande de navigation globale

public class GlobalCommands { public static CompositeCommand NavigateCommand = new CompositeCommand(); }

Dans ShellViewModel

public class ShellViewModel :ViewModelBase , IShellViewModel { private readonly IRegionManager _regionManager; public ICommand NavigateCommand { get; private set; } public ShellViewModel(IRegionManager regionManager) { _regionManager = regionManager; // NavigateCommand = new DelegateCommand<object>(Navigate); GlobalCommands.NavigateCommand.RegisterCommand(NavigateCommand); } private void Navigate(object navigatePath) { if (navigatePath != null) _regionManager.RequestNavigate("ContentRegion", navigatePath.ToString(), NavigationComplete); } private void NavigationComplete(NavigationResult result) { //log } }

Utilisation dans Xaml (depuis une vue de navigation)

<UserControl … xmlns:inf="clr-namespace:PrismDemo.Infrastructure;assembly=PrismDemo.Infrastructure" xmlns:views="clr-namespace:PrismDemo.Teachers.Views"> <StackPanel Orientation="Horizontal"> <Button Content="Liste" Command="{x:Static inf:GlobalCommands.NavigateCommand}" CommandParameter="{x:Type views:TeachersView}" /> <Button Content="Ajouter" Command="{x:Static inf:GlobalCommands.NavigateCommand}" CommandParameter="{x:Type views:AddTeacherView}" /> <!-- ect. --> </StackPanel> </UserControl>

59

59

Navigation Journal

On récupère le journal de navigation depuis « navigationContext »

private IRegionNavigationJournal _journal; // etc. public void OnNavigatedTo(NavigationContext navigationContext) { _journal = navigationContext.NavigationService.Journal; }

On peut utiliser le journal pour revenir en arrière par exemple.

if (_journal.CanGoBack) _journal.GoBack();

SI le journal est vide, c’est parce qu’il faut appeler la commande pour naviguer vers la page

« d’accueil » au lancement de l’application depuis l’initialisation du module

GlobalCommands.NavigateCommand.Execute("PeopleView");

Region Context

<ListBox x:Name="lsPeople" ItemsSource="{Binding People}" /> <ContentControl Grid.Row="1" prism:RegionManager.RegionName="PersonDetailsRegion" prism:RegionManager.RegionContext="{Binding SelectedItem, ElementName=lsPeople}"/>

Affichage du détail de la

personne sélectionnée

dans la liste avec

RegionContext.

Créer une région

« PersonDetailsRegion »

60

60

Dans la vue « PersonDetailsView »

public partial class PersonDetailsView : UserControl, IView { public PersonDetailsView(IPersonDetailsViewModel viewModel) { InitializeComponent(); ViewModel = viewModel; RegionContext.GetObservableContext(this).PropertyChanged += (s, e) => { var context = (ObservableObject<object>)s; var currentPerson = (Person)context.Value; (ViewModel as PersonDetailsViewModel).Person = currentPerson; }; } public IViewModel ViewModel { get { return (IViewModel)DataContext; } set { DataContext = value; } } }

Confirmer, Annuler la navigation (IConfirmNavigationRequest)

IConfirmNavigationRequest Process

RequestNavigateConfirmNavigationRequestOnNavigatedFromcontine navigation process

Utile par exemple pour demander à l’utilisateur s’il désire sauvegarder des changements avant de

quitter une page.

public class PersonDetailsViewModel : ViewModelBase, IPersonDetailsViewModel, INavigationAware, IConfirmNavigationRequest { // }

public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback) { bool result = true; if (MessageBox.Show("Navigation…", "Désirez-vous vraiment quitter la page?", MessageBoxButton.YesNo) == MessageBoxResult.No) { result = false; } continuationCallback(result); }

61

61

RegionMemberLifetime

La View/ViewModel sont supprimés de la région si KeepAlive réglé à false, à chaque navigation vers

une autre page.

public class PersonDetailsViewModel : ViewModelBase, IPersonDetailsViewModel, INavigationAware, IRegionMemberLifetime { public bool KeepAlive { get { return false; } } }

Navigation grâce à VisualStateManager

QuickStart: Stated-Based Navigation

A utiliser pour afficher les mêmes données avec un style différent.

Exemple un affichage « liste » et un affichage « avatars » pour la même View.

On change la vue par l’intermédiaire du VisualStateManager (exemple ici avec un ToggleButton)

62

62

3. Mvvm (Prism.Mvvm)

a. DelegateCommand

Sans paramètre

public ICommand AddTeacherCommand { get; private set; } AddTeacherCommand= new DelegateCommand(() => { });

Avec paramètre public ICommand AddTeacherCommand { get; private set; } AddTeacherCommand= new DelegateCommand<Teacher>((teacher) => { }); Avec condition d’exécution

public DelegateCommand AddTeacherCommand { get; private set; }

AddTeacherCommand = new DelegateCommand(() => { }, () => { return !CurrentTeacher.HasErrors; }); Déclencher l’éxécution de « CanExecute »

AddTeacherCommand.RaiseCanExecuteChanged();

b. CompositeCommand

Commande « globale », on peut y enregistrer une ou plusieurs commandes

« simples »(DelegateCommand)

1. Création de commandes et enregistrement dans la commande globale

SaveCommand1 = new DelegateCommand(Save, CanSave); SaveCommand2 = new DelegateCommand(Save, CanSave); GlobalCommands.SaveAllCommand.RegisterCommand(SaveCommand1); GlobalCommands.SaveAllCommand.RegisterCommand(SaveCommand2);

2. La commande composite ne peut s’exécuter que si toutes les commandes enregistrées peuvent s’exécuter.

3. En exécutant la commande globale, toutes les commandes enregistrées sont exécutées

GlobalCommands.SaveAllCommand.Execute(); Depuis le Xaml (inf étant le namespace du projet)

<Button Command="{x:Static inf:ApplicationCommands.SaveAllCommand}" Content="Enregistrer tout" />

QuickStart : Commanding

On déclare avec DelegateCommand afin de pouvoir déclencher

RaiseCanExecuteChanged

63

63

c. BindableBase

Les Models et ViewModels peuvent utiliser BindableBase (INotifyPropertyChanged)

public class Teacher : BindableBase { } Mise à jour de la valeur et notification de changement avec la méthode « SetProperty »

private string _name; public string Name { get { return _name; } set { SetProperty(ref _name, value); } }

Notification de changement

OnPropertyChanged("Teachers");

Ou avec expression

OnPropertyChanged(() => Teachers);

d. ViewModelLocator

Prism utilise la convention par défaut pour retrouver les correspondances entre View/ViewModel.

Cette convention est plus basée sur les namespaces que les « dossiers » :

- Vues dans dossier « Views » (namespace « *.Views »)

- ViewModels dans dossier « ViewModels » (namespace « *.ViewModels »)

- Nom du ViewModel = nom de la View + « ViewModel »

(Exemple pour « Shell », son ViewModel se nomme « ShellViewModel »)

« AutoWireViewModel »

En indiquant ViewModelLocator.AutoWireViewModel="True" sur la fenêtre principale, le

ViewModelLocator va utiliser sa convention pour essayer de retrouver le « ViewModel » de la vue

sans qu’on ne l’ait enregistré. Les vues doivent implémenter IView

<Window … xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Mvvm;assembly=Microsoft.Practices.Prism.Mvvm.Desktop" xmlns:p="http://www.codeplex.com/prism" prism:ViewModelLocator.AutoWireViewModel="True"> <Grid> <ContentControl p:RegionManager.RegionName="ContentRegion" /> </Grid> </Window>

Exemple dans la configuration d’un module, on enregistre la vue pour une région mais pas de

ViewModel dans le conteneur. (Note pour l’exemple le ViewModel n’implémente pas d’interface

sinon le conteneur serait dans l’impossibilité de résoudre la correspondance)

public void Initialize() { _regionManager.RegisterViewWithRegion("ContentRegion", typeof(TeachersView)); }

64

64

Changer la convention avec le ViewModelLocatorProvider

(Toujours sans enregistrer les ViewModels dans le conteneur)

Exemple ViewModels et Views rangés dans un même dossier et namespace « *.Views »

protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) => { var viewName = viewType.FullName; var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName; // PrismViewModelLocatorDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null var viewModelName = String.Format(CultureInfo.InvariantCulture, "{0}ViewModel, {1}", viewName, viewAssemblyName); // PrismViewModelLocatorDemo.Views.ShellViewModel, PrismViewModelLocatorDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null return Type.GetType(viewModelName); }); var bootstrapper = new Bootstrapper(); bootstrapper.Run(); }

Création d’instance des « ViewModels »… (Dans « OnStartup »)

ViewModelLocationProvider.SetDefaultViewModelFactory((type) => { return Activator.CreateInstance(type); });

Méthode appelée à chaque fois que le

ViewModelLocator essaie de résoudre le

ViewModel d’une vue

Type de la vue .. exemple …Name : « Shell », FullName

« PrismViewModelLocatorDemo.Views.Shell »

Retourne le type du ViewModel retrouvé

par son « chemin »

65

65

Problème ne peut résoudre les paramètres injectés …

Création d’instance avec un conteneur (exemple Unity)

public partial class App : Application { IUnityContainer _container = new UnityContainer(); protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); _container.RegisterType<IPeopleService,PeopleService>(); ViewModelLocationProvider.SetDefaultViewModelFactory((type) => { return _container.Resolve(type); }); } }

4. PubSubEvents (Prism.PubSubEvents)

QuickStart: EventAggregation

Permet l’échange de messages entre les composants de l’application

Dans le projet Infrastructure on crée les « Events ». Exemple

public class MessageEvent : PubSubEvent<string> { }

Injecter « IEventAggregator » pour les abonnés et publieurs

private IEventAggregator _eventAggregator; public PersonDetailsViewModel(IEventAggregator eventAggregator) { _eventAggregator = eventAggregator; }

Abonnement à l’event

_eventAggregator.GetEvent<MessageEvent>().Subscribe((message) => { // });

Publication d’un message

_eventAggregator.GetEvent<MessageEvent>().Publish("Un message ...");

66

66

5. Services

a. Service de module

« Couple interface et service »

public interface ITeachersService { IEnumerable<Teacher> GetAll(); void Add(Teacher teacher) ; }

public class TeachersService : ITeachersService { private static List<Teacher> teachers = new List<Teacher>() { new Teacher("Ellen Poe"), new Teacher("Marie Bellin") }; public IEnumerable<Teacher> GetAll() { return teachers; } public void Add(Teacher teacher) { teachers.Add(teacher); } }

On enregistre les services dans la classe de configuration du module

_container.RegisterType<ITeachersService, TeachersService>();

On injecte ensuite le service dans les ViewModels l’utilisant

public class TeachersViewModel : ViewModelBase, ITeachersViewModel { private IRegionManager _regionManager; private ITeachersService _teachersService; public ObservableCollection<Teacher> Teachers { get; set; } public TeachersViewModel(IRegionManager regionManager, ITeachersService teachersService) { _regionManager = regionManager; _teachersService = teachersService; var list = _teachersService.GetAll(); Teachers = new ObservableCollection<Teacher>(list); OnPropertyChanged("Teachers"); } } }

67

67

b. Services partagés

On crée un projet pour les services partagés par plusieurs modules.

Configuration du module

public class TeachersServiceModule : IModule { private IUnityContainer _container; public TeachersServiceModule(IUnityContainer container) { _container = container; } public void Initialize() { _container.RegisterType<ITeachersService, TeachersService>(new ContainerControlledLifetimeManager()); } }

Création d’un projet avec les « Models » partagés

Interface pour le service dans le projet

« Infrastructure »

Module avec le service

« Singleton »

68

68

Référencer le module dans le boostrapper

protected override IModuleCatalog CreateModuleCatalog() { var catalog = new ModuleCatalog(); catalog.AddModule(typeof(TeachersServiceModule)); catalog.AddModule(typeof(TeachersModule)); catalog.AddModule(typeof(CoursesModule)); return catalog; }

Les modules utilisant le service n’ont pas de référence directe au service mais seulement au projet

« Business » et « Infrastructure ». On injecte le service dans les ViewModels.

69

69

6. Projet Infrastructure

IViewModel : interface de base pour les ViewModels

public interface IViewModel { }

ViewModelBase : classe de base pour les ViewModels

public class ViewModelBase : BindableBase, IViewModel{ }

ModuleBase : classe de base pour la configuration des modules

public abstract class ModuleBase : IModule { protected IRegionManager RegionManager { get; private set; } protected IUnityContainer Container { get; private set; } public ModuleBase(IUnityContainer container, IRegionManager regionManager) { Container = container; RegionManager = regionManager; } public void Initialize() { RegisterTypes(); InitializeModule(); } protected abstract void InitializeModule(); protected abstract void RegisterTypes(); } Events : Events pour EventAggregator

public class MessageEvent : PubSubEvent<string> { }

70

70

GlobalCommands : les commandes composites de l’application

public class GlobalCommands { public static CompositeCommand NavigateCommand = new CompositeCommand(); }

RegionNames : pour éviter les erreurs de saisies dans le nom des régions.

public class RegionNames { public static string ToolbarRegion = "ToolbarRegion"; public static string ContentRegion = "ContentRegion"; }

Etc.

7. Interactivity (Prism.Interactivity) PM> Install-Package Prism.Interactivity

Ajouter une référence à « System.Windows.Interactivity »

QuickStart: Interactivity

a. Notification

Avec « InteractionRequest »

<Window … xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:prism="http://www.codeplex.com/prism"> <i:Interaction.Triggers> <prism:InteractionRequestTrigger SourceObject="{Binding NotificationRequest}"> <prism:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True" /> </prism:InteractionRequestTrigger> </i:Interaction.Triggers> <StackPanel> <Button Content="Notification" Command="{Binding NotificationCommand}"></Button> </StackPanel> </Window>

public class MainWindowViewModel { public InteractionRequest<INotification> NotificationRequest { get; set; } public ICommand NotificationCommand { get; set; } public MainWindowViewModel() { NotificationRequest = new InteractionRequest<INotification>(); NotificationCommand = new DelegateCommand(() =>

71

71

{ NotificationRequest.Raise(new Notification() { Title = "Notification", Content = "Message ...", }, (c) => { // callback }); }); } }

Avec « DefaultNotificationWindow »

NotificationCommand = new DelegateCommand(() => { var window = new DefaultNotificationWindow() { Width = 200, Height = 150d, WindowStartupLocation = WindowStartupLocation.CenterScreen }; window.Notification = new Notification() { Title = "Notification", Content = "Message..." }; window.ShowDialog(); });

b. Confirmation

Avec « InteractionRequest »

<Window … xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:prism="http://www.codeplex.com/prism"> <i:Interaction.Triggers> <prism:InteractionRequestTrigger SourceObject="{Binding ConfirmationRequest}"> <prism:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True" /> </prism:InteractionRequestTrigger> </i:Interaction.Triggers> <StackPanel> <Button Content="Confirmation" Command="{Binding ConfirmationCommand}"></Button> </StackPanel> </Window>

public class MainWindowViewModel {

Calllback en 3ème

paramètre

Seule la source change par

rapport à l’exemple précédent

72

72

public InteractionRequest<IConfirmation> ConfirmationRequest { get; set; } public ICommand ConfirmationCommand { get; set; } public MainWindowViewModel() { ConfirmationRequest = new InteractionRequest<IConfirmation>(); ConfirmationCommand = new DelegateCommand(() => { ConfirmationRequest.Raise(new Confirmation { Title = "Confirmation", Content = "Confirmez-vous? ..." }, (c) => { if (c.Confirmed) { // } }); }); } }

Avec « DefaultConfirmationWindow »

ConfirmationCommand = new DelegateCommand(() => { var window = new DefaultConfirmationWindow() { Width = 200, Height = 150d, WindowStartupLocation = WindowStartupLocation.CenterScreen }; window.Confirmation = new Confirmation() { Content = "Confirm ?", Title = "Confirmez-vous ? …" }; window.ShowDialog(); var dialogResult = window.Confirmation.Confirmed; if (dialogResult) { } });

73

73

c. Custom

Custom popup

<Window … xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:prism="http://www.codeplex.com/prism"> <i:Interaction.Triggers> <prism:InteractionRequestTrigger SourceObject="{Binding CustomPopupRequest}"> <prism:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True"> <prism:PopupWindowAction.WindowContent> <local:MyUserControl /> </prism:PopupWindowAction.WindowContent> </prism:PopupWindowAction> </prism:InteractionRequestTrigger> </i:Interaction.Triggers> <StackPanel> <Button Content="Custom" Command="{Binding CustomPopupCommand}"></Button> </StackPanel> </Window>

public class MainWindowViewModel { public InteractionRequest<INotification> CustomPopupRequest { get; set; } public ICommand CustomPopupCommand { get; set; } public MainWindowViewModel() { CustomPopupRequest = new InteractionRequest<INotification>(); CustomPopupCommand = new DelegateCommand(() => { CustomPopupRequest.Raise(new Notification { Title = "Custom!", Content = "Message... " }, (c) => { MessageBox.Show("Intéraction finie!"); }); }); } }

Le « userControl » peut implémenter « IInteractionRequestAware »

public partial class MyUserControl : UserControl, IInteractionRequestAware { public MyUserControl() { InitializeComponent(); } public Action FinishInteraction { get; set; } public INotification Notification { get; set; } private void Button_Click(object sender, RoutedEventArgs e) { if (FinishInteraction != null) FinishInteraction(); } }

On crée un « UserControl » que

l’on ajoute

Le callback sera déclenché

74

74

Custom Notification

Dans le Shell ou vue désirée

<i:Interaction.Triggers> <prism:InteractionRequestTrigger SourceObject="{Binding ShowMessageRequest}"> <inf:ShowNotificationAction TargetName="NotificationList" /> </prism:InteractionRequestTrigger> </i:Interaction.Triggers>

La zone affichée

<Grid x:Name="NotificationList" HorizontalAlignment="Right" VerticalAlignment="Bottom" Visibility="{Binding Count, Converter={StaticResource SizeToVisibilityConverter}, Mode=OneWay, FallbackValue=Collapsed}"> <ItemsControl ItemTemplate="{StaticResource MessageNotificationTemplate}" ItemsSource="{Binding}" /> </Grid>

ShellViewModel

public InteractionRequest<INotification> ShowMessageRequest { get; set; }

ShowMessageRequest = new InteractionRequest<INotification>(); Notification notification = new Notification(); notification.Title = "PrismOverview :"; notification.Content = message; ShowMessageRequest.Raise(notification);

(template)

<DataTemplate x:Key="MessageNotificationTemplate"> <Border BorderThickness="2" BorderBrush="SkyBlue" Background="White" CornerRadius="5" IsHitTestVisible="False" Opacity="0.6"> <StackPanel Orientation="Vertical" Width="200"> <TextBlock Padding="5"><Run Text="{Binding Content, Mode=OneWay}" FontStyle="Italic" FontSize="14"/></TextBlock> </StackPanel> </Border> </DataTemplate>

75

75

(et converter)

public class SizeToVisibilityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (value is int) { if ((int)value > 0) { return Visibility.Visible; } } return Visibility.Collapsed; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }

76

76

(Trigger)

public class ShowNotificationAction : TargetedTriggerAction<FrameworkElement> { public static readonly DependencyProperty NotificationTimeoutProperty = DependencyProperty.Register("NotificationTimeout", typeof(TimeSpan), typeof(ShowNotificationAction), new PropertyMetadata(new TimeSpan(0, 0, 5))); private ObservableCollection<object> notifications; public ShowNotificationAction() { this.notifications = new ObservableCollection<object>(); } public TimeSpan NotificationTimeout { get { return (TimeSpan)GetValue(NotificationTimeoutProperty); } set { SetValue(NotificationTimeoutProperty, value); } } protected override void OnTargetChanged(FrameworkElement oldTarget, FrameworkElement newTarget) { base.OnTargetChanged(oldTarget, newTarget); if (oldTarget != null) { this.Target.ClearValue(FrameworkElement.DataContextProperty); } if (newTarget != null) { this.Target.DataContext = this.notifications; } } protected override void Invoke(object parameter) { var args = parameter as InteractionRequestedEventArgs; if (args == null) { return; } var notification = args.Context; this.notifications.Insert(0, notification); var timer = new DispatcherTimer { Interval = this.NotificationTimeout }; EventHandler timerCallback = null; timerCallback = (o, e) => { timer.Stop(); timer.Tick -= timerCallback; this.notifications.Remove(notification); }; timer.Tick += timerCallback; timer.Start(); args.Callback(); } }

77

77

d. BusyIndicator

Création d’un « UserControl »

<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <Rectangle Fill="LightGray" Opacity="0.5"/> <Border BorderBrush="Black" BorderThickness="2" HorizontalAlignment="Center" VerticalAlignment="Center"> <StackPanel Background="White" > <TextBlock Margin="20,20,20,10" Name="Message">Loading...</TextBlock> <ProgressBar Margin="20,0,20,20" IsIndeterminate="True" Height="15" Width="200" /> </StackPanel> </Border> </Grid>

Avec VisualStateManager (Shell)

<VisualStateManager.VisualStateGroups> <VisualStateGroup> <VisualState x:Name="Normal"/> <VisualState x:Name="Loading"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="busyIndicator"> <DiscreteObjectKeyFrame KeyTime="0:0:0"> <DiscreteObjectKeyFrame.Value> <Visibility>Visible</Visibility> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups>

Le behavior

<i:Interaction.Behaviors> <ei:DataStateBehavior Binding="{Binding IsBusy}" Value="True" TrueState="Loading" FalseState="Normal"/> </i:Interaction.Behaviors>

Ajout du busyIndicator.. En bas de la page

<inf:BusyIndicator x:Name="busyIndicator" Visibility="Collapsed" />

78

78

Avec un converter « BooleanToVisibilityConverter »

public sealed class BooleanToVisibilityConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return (value is bool && (bool)value) ? Visibility.Visible : Visibility.Collapsed; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return value is Visibility && (Visibility)value == Visibility.Visible; } }

<views:BusyIndicator Visibility="{Binding IsBusy,Converter={StaticResource BooleanToVisibilityConverter}}"/>

Ajouter une propriété « IsBusy » par exemple dans le ViewModel

public bool IsBusy { get; set; } … Changer l’état

public async void Refresh() { IsBusy = true; await Task.Delay(2000); // var result = _peopleRepository.GetAll(); People = new ObservableCollection<Person>(result); OnPropertyChanged("People"); IsBusy = false; OnPropertyChanged("IsBusy"); }

79

79

e. InvokeCommandAction

Invoque une ICommand sur un évenement

Exemple :

<ListBox ItemsSource="{Binding People}"> <i:Interaction.Triggers> <i:EventTrigger EventName="SelectionChanged"> <prism:InvokeCommandAction Command="{Binding SelectPersonCommand}" TriggerParameterPath="AddedItems" /> </i:EventTrigger> </i:Interaction.Triggers> </ListBox> <TextBlock Text="{Binding CurrentPerson}" />

Dans le ViewModel de la View

public ICommand SelectPersonCommand { get; set; }

SelectPersonCommand = new DelegateCommand<object[]>((items) => { CurrentPerson = items.FirstOrDefault() as Person; });

Ici la commande récupère un tableau d’objets en parameter avec TriggerParameterPath. On peut definir le mode de selection de la ListBox sur single SelectionSelectionMode="Single"

Invoque la commande SelectPersonCommand quand la sélection change

dans la ListBox