Tutoriel : Introduction à M-V-VM avec Silverlight en utilisant MVVM Light Toolkit
Date de publication : 02/05/2010 , Date de mise à jour : 02/05/2010
		Par
		Nico-pyright(c)  (Page d'accueil)
 
			Cet article constitue une introduction au développement d'applications Silverlight en utilisant le design pattern M-V-VM grâce au MVVM Light Toolkit de Laurent Bugnion.
			
	Commentez cet article : 
Commentez  
		 
		1.Introduction	
			
			
		
		2.Présentation M-V-VM	
			
			2.1.Le design Pattern
				
					
			
			2.2.MVVM Toolkit	
				
					
					
		
		3.Exemple d'utilisation de M-V-VM	
			
				
			3.1.Création du projet	
				
					
					
					
					
			
			3.2.Vers M-V-VM	
				
					
				3.2.1.Installer MVVM Toolkit	
					
					
					
					
				
				3.2.2.Référencer MVVM Toolkit	
					
					
					
				
				3.2.3.Créer le modèle	
					
					
					
					
					
					
					
					
					
					
					
					
				
				3.2.4.Créer un viewmodel et utiliser le pattern Service Locator	
					
					
					
					
					
				
				3.2.5.Lier le viewmodel à la vue	
					
					
					
					
					
					
					
					
					
					
					
					
					
					
					
					
					
					
					
					
					
					
				
				3.2.6.Lier les événements à des commandes	
					
					
					
					
					
					
					
					
					
					
				
				3.2.7.Communiquer grâce au Mediator	
					
					
					
					
					
					
					
					
					
					
					
					
					
					
					
					
					
					
				
				3.2.8.Lier les animations	
					
					
					
					
					
					
					
					
					
					
					
					
					
					
					
					
				
			
		
		4.Un peu plus loin dans les commandes
			
			
			4.1.Une simple commande sur un bouton
				
				
				
				
				
				
				
				
				
				
				
			
			4.2.Une simple commande sur un bouton avec un paramètre
				
				
				
				
				
				
				
				
				
			
		
		5.Utiliser les templates pour créer un nouveau projet M-V-VM
			
			
			
			
			
			
			
			
		
		6.Téléchargement et démo
			
			
			
				
		7.Conclusion
			
			
		
		Remerciements
			
			
		
		Contact
			
			
				
	
		1.Introduction
	
			
			Très à la mode, le pattern M-V-VM se présente comme la meilleure solution pour développer des applications avec Silverlight et WPF. Sans rentrer dans ses avantages, il permet notamment de séparer les responsabilités de l'IHM des traitements métiers.
			Nous allons voir dans cet article une introduction à son adoption en utilisant le MVVM Light Toolkit de Laurent Bugnion.
			Tout au long de ce tutoriel, j'utiliserai Visual Studio 2010 et la version 3 de Silverlight.
Notons que Silverlight 4 apporte quelques évolutions qui permettent de développer plus facilement avec un pattern comme M-V-VM, mais qui ne sont pas nécessaire lorsqu'on utilise MVVM Light Toolkit.
		
		2.Présentation M-V-VM
	
			
			2.1.Le design Pattern
				
					M-V-VM signifie Model View ViewModel. Ce design pattern a été présenté de nombreuses fois, je vais donc le présenter très brièvement et tenter plutôt une métaphore pour l'expliquer. Pour plus d'informations, consulter par exemple 
 WPF Apps With The Model-View-ViewModel Design Pattern
 WPF Apps With The Model-View-ViewModel Design Pattern, 
 Wikipédia
 Wikipédia, ... 
					- M : Modèle => grosso modo les données, les services qui mettent à disposition des objets de données. (Une classe "Client" par exemple)
					- V : Vue => ce que l'on voit, il s'agit en l'occurrence du xaml.
					- VM : View Model => la colle entre les deux. Le view model est une classe C# et ne peut fonctionner que grâce à la puissance du binding xaml et à l'interface 
INotifyPropertyChanged.
Pour l'expliquer, essayons de voir ca comme un jeu, disons une machine à sous d'un casino.
- Mon modèle => les différentes valeurs des images de la machine à sous, dont le fameux 7 qui fait gagner le gros lot.
- Ma vue => la carcasse de la boite à sous, ce qu'on voit.
- Mon ViewModel : les engrenages qui relient les images à la machine à sous.
Sans ces engrenages, mes images ne peuvent pas s'accrocher au cadre de la machine à sous et tout se casse la figure, on ne voit rien sur la vue.
Lorsque ces engrenages sont présents, on peut voir les données liées à la vue (grâce au binding). Et je peux agir sur mon modèle par l'intermédiaire de commandes, en l'occurrence le levier de la machine à sous.
Je tire sur le levier, une commande du view model est activée, les images tournent, le modèle se met à jour (les images ont changé) et la vue est mise à jour automatiquement. Je peux voir que les trois sept sont alignés. JACKPOT.
Je suis bien conscient que la métaphore n'est surement pas la meilleure mais j'espère qu'avec ça, vous aurez plus de facilité à appréhender ce design pattern.
					
MVVM Toolkit est un ensemble de bibliothèques, créé par Laurent Bugnion. Elles fournissent des classes qui facilitent l'adoption de M-V-VM. Vous pouvez télécharger ce toolkit sur le site 
 de Galasoft
 de Galasoft.
					MVVM Toolkit n'est absolument pas obligatoire pour développer une application en utilisant le pattern M-V-VM, mais ce toolkit facilite grandement son utilisation. J'apprécie particulièrement sa simplicité et malgré sa dénomination de "light", ce toolkit dispose de toutes les bases pour développer une application utilisant ce pattern.
					
3.Exemple d'utilisation de M-V-VM
	
			
				Pour illustrer l'utilisation du toolkit, je vais m'attacher à transformer le projet qui est généré lors de la création d'un nouveau projet de type Silverlight Navigation Application pour qu'il respecte le pattern M-V-VM.
				
			3.1.Création du projet
	
				
					Commençons par créer le nouveau projet : New Project -> Silverlight -> Silverlight Navigation Application, et nommons notre application : DemoMvvmLight.
					
					
Figure 1: Création du nouveau projet.
Première chose, compilons cette application et lançons là pour vérifier que tout va bien.
					
					
Figure 2: Première exécution du projet.
3.2.Vers M-V-VM
	
				
					Certes, l'application créée n'est pas très aboutie. Maintenant, transformons celle-ci pour qu'elle respecte le pattern M-V-VM, et pour ce faire, on va utiliser le MVVM Light Toolkit de Laurent Bugnion.
					
					
					
						L'installation est très simple, rendez vous 
 sur la page dédiée
 sur la page dédiée.
						Récupérer ensuite les binaires et les templates pour la version de votre choix. 
						Pour les binaires, Le zip proposé encourage à positionner les dll dans Program Files, ce qui n'est pas obligatoire.
						Pour les templates, il faudra :
						- récupérer la racine du path utilisé pour les templates (Tools --> Options --> Project and Solutions )
					

Figure 3: Récupérer le path des templates.
						- extraire le contenu du zip dans le répertoire
						
						Tout à été fait par Laurent pour que ce soit simple.
						Il ne restera plus qu'à fermer Visual Studio pour voir apparaitre les nouveaux templates, comme on le verra 
dans le chapitre 5.
					
3.2.2.Référencer MVVM Toolkit
	
					
					
						Récupérer les trois dll suivantes depuis les binaires : 
						- GalaSoft.MvvmLight.dll
						- GalaSoft.MvvmLight.Extras.dll
						- System.Windows.Interactivity.dll
						et les référencer dans le projet :
					
					
Figure 4: Référencement des DLL du toolkit.
3.2.3.Créer le modèle
	
					
					Dans un premier temps, ouvrons le fichier Views/Home.xaml, nous pouvons voir notamment :
					
					| Home.xaml | 
| 
<Grid x:Name="LayoutRoot"><ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}"><StackPanel x:Name="ContentStackPanel"><TextBlock x:Name="HeaderText" Style="{StaticResource HeaderTextStyle}"                               Text="Home"/><TextBlock x:Name="ContentText" Style="{StaticResource ContentTextStyle}"                               Text="Home page content"/></StackPanel></ScrollViewer></Grid>
 | 
On trouve deux TextBlock à qui ont a affecté deux valeurs statiques. Dans une vraie application, on devra charger ces valeurs à partir de la base de données et les rendre disponibles grâce au modèle.
					Profitons-en pour créer un modèle minimal, ce qui correspondra à l'objet échangé et au service qui permet de le récupérer. Dans une vraie application, on ferait appel à un web service pour aller charger les données de son modèle ; simulons ce fonctionnement et créons un nouveau projet de type Sylverlight Class Library qu'on appelle DemoMvvm.NavigationService.
					
					
Figure 5: Création du projet NavigationService.
						Et rajoutons une classe NavigationService :
					
					| NavigationService.cs | 
| 
publicclassNavigationService{publicdelegatevoidGetNavigationNodeHanlder(GetNavigationNodeCompletedEventArgs args);publiceventGetNavigationNodeHanlder OnGetNavigationNodeCompleted;publicvoidGetNavigationNodeAsync(stringnodeName){NavigationNode node=GetNodeFrom(nodeName);if(OnGetNavigationNodeCompleted!=null)OnGetNavigationNodeCompleted(newGetNavigationNodeCompletedEventArgs{Result=node});}privateNavigationNodeGetNodeFrom(stringnodeName){switch(nodeName){case"Home":returnnewNavigationNode{Title="Accueil",Content="Bienvenue sur la page d'accueil"};case"About":returnnewNavigationNode{Title="A propos",Content="Ici on parle de nous"};default:thrownewNotImplementedException();}}}
 | 
Avec
					
					| GetNavigationNodeCompletedEventArgs.cs | 
| 
publicclassGetNavigationNodeCompletedEventArgs:EventArgs{publicNavigationNode Result{get;set;}}
 | 
et :
					
					| NavigationNode.cs | 
| 
publicclassNavigationNode{publicstringTitle{get;set;}publicstringContent{get;set;}}
 | 
Notre simulation de modèle est terminée.
					
				
				3.2.4.Créer un viewmodel et utiliser le pattern Service Locator
	
					
					Nous allons créer notre ViewModel qui va nous permettre de charger le modèle. Nommons-le 
HomeViewModel et plaçons le dans le répertoire ViewModel.
					Pour fonctionner avec MVVM Light Toolkit, le ViewModel doit dériver de la classe 
ViewModelBase. 
					Une fois que le ViewModel aura chargé les données, il pourra les mettre à disposition de la vue. La vue devra donc être liée au ViewModel, grâce au DataContext.
					Pour ce faire et pour éviter le couplage fort, on va utiliser 
 le pattern Service Locator
 le pattern Service Locator en créant une nouvelle classe :
					
| ViewModelLocator.cs | 
| 
publicclassViewModelLocator{privatestaticHomeViewModel _homeViewModel;publicstaticHomeViewModel HomeViewModelStatic{get{if(HomeViewModel==null)
                _homeViewModel=newHomeViewModel();return_homeViewModel;}}publicHomeViewModel HomeViewModel{get{returnHomeViewModelStatic;}}}
 | 
On déclarera ce Locator dans les ressources de l'application, ainsi il sera accessible depuis n'importe quelle vue :
					
					| app.xaml | 
| 
<Application    x:Class="DemoMvvmLight.App"  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  xmlns:vm="clr-namespace:DemoMvvmLight"  mc:Ignorable="d"><Application.Resources><ResourceDictionary><ResourceDictionary.MergedDictionaries><ResourceDictionary Source="Assets/Styles.xaml"/></ResourceDictionary.MergedDictionaries></ResourceDictionary><vm:ViewModelLocator x:Key="Locator"                             d:IsDataSource="True" /></Application.Resources></Application>
 | 
3.2.5.Lier le viewmodel à la vue
	
					
					Afin de lier le viewmodel à la vue, on déclarera le viewmodel dans le xaml de la vue :
					
					| Home.xaml | 
| 
DataContext="{Binding HomeViewModelStatic, Source={StaticResource Locator}}"
					
 | 
Il ne restera plus qu'à binder les deux textbox au ViewModel :
					
					| Home.xaml | 
| 
<Grid x:Name="LayoutRoot"><ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}"><StackPanel x:Name="ContentStackPanel"><TextBlock x:Name="HeaderText" Style="{StaticResource HeaderTextStyle}"							   Text="{Binding NavigationNode.Title}"/><TextBlock x:Name="ContentText" Style="{StaticResource ContentTextStyle}"							   Text="{Binding NavigationNode.Content}"/></StackPanel></ScrollViewer></Grid>
 | 
Notez que Visual Studio 2010 arrive à charger les valeurs dans son designer :
					
					
Figure 6: Aperçu du ViewModel dans le designer de Visual Studio 2010.
						Faisons pareil maintenant pour la Vue About et créons son viewmodel :
					
					| AboutViewModel.cs | 
| 
publicclassAboutViewModel:ViewModelBase{publicAboutViewModel(){NavigationService navigationService=newNavigationService();navigationService.OnGetNavigationNodeCompleted+=navigationService_OnGetNavigationNodeCompleted;navigationService.GetNavigationNodeAsync("About");}voidnavigationService_OnGetNavigationNodeCompleted(GetNavigationNodeCompletedEventArgs args){NavigationNode=args.Result;}publicconststringNavigationNodePropertyName="NavigationNode";privateNavigationNode _navigationNode=null;publicNavigationNode NavigationNode{get{return_navigationNode;}privateset{if(_navigationNode==value)return;_navigationNode=value;RaisePropertyChanged(NavigationNodePropertyName);}}}
 | 
On n'oubliera bien sûr pas de le déclarer dans le ViewModelLocator :
					
					| ViewModelLocator.cs | 
| 
privatestaticAboutViewModel _aboutViewModel;publicstaticAboutViewModel AboutViewModelStatic{get{if(_aboutViewModel==null)
            _aboutViewModel=newAboutViewModel();return_aboutViewModel;}}publicAboutViewModel AboutViewModel{get{returnAboutViewModelStatic;}}
 | 
Notez que pour être propre, cette classe devrait permettre de libérer ses ressources, créons donc une méthode de libération de ressources :
					
					| ViewModelLocator.cs | 
| 
publicstaticvoidCleanup(){Cleanup(ref_homeViewModel);Cleanup(ref_aboutViewModel);}privatestaticvoidCleanup<T>(refT viewModelBase) where T:ViewModelBase{if(viewModelBase!=null){viewModelBase.Cleanup();viewModelBase=null;}}
 | 
Que l'on appellera dans l'événement Exit de l'application :
					
					| app.xaml.cs | 
| 
publicpartialclassApp:Application{publicApp(){this.Startup+=this.Application_Startup;this.UnhandledException+=this.Application_UnhandledException;Exit+=App_Exit;InitializeComponent();}privatevoidApp_Exit(objectsender,EventArgs e){ViewModelLocator.Cleanup();}[...]}
 | 
Il ne reste plus qu'à lier le viewmodel à la vue :
					
					| About.xaml | 
| 
DataContext="{Binding AboutViewModelStatic, Source={StaticResource Locator}}"
					
 | 
et à faire le binding des valeurs :
					
					| About.xaml | 
| 
<Grid x:Name="LayoutRoot"><ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}"><StackPanel x:Name="ContentStackPanel"><TextBlock x:Name="HeaderText" Style="{StaticResource HeaderTextStyle}"                       Text="{Binding NavigationNode.Title}"/><TextBlock x:Name="ContentText" Style="{StaticResource ContentTextStyle}"                       Text="{Binding NavigationNode.Content}"/></StackPanel></ScrollViewer></Grid>
 | 
Si on compile et qu'on exécute l'application, on voit bien les valeurs issues de notre modèle :
					
					
Figure 7: Les vues Home et About utilisent M-V-VM.
						Voilà pour ces deux vues, cela s'avère plutôt simple.
					
				
				3.2.6.Lier les événements à des commandes
	
					
					Maintenant, ouvrons MainPage.xaml et modifions la propriété Source du contrôle navigation:Frame et remplaçons "/Home" par "/Home2".
					Lorsqu'on relance l'application, on obtient la fenêtre suivante :
					
					
Figure 8: Erreur de chargement de la vue Home2.
Il s'agit de la vue ErrorWindow.xaml ; ouvrons son code behind, que voyons-nous :
					En fonction du constructeur de la classe ErrorWindow, on remplit un Textbox avec un message. C'est bien beau, mais pas très M-V-VM. On aimerait pouvoir exploiter la puissance du binding xaml et associer un viewmodel à cette vue.
					Créons donc un ViewModel pour cette vue :
					
					| ErrorWindowViewModel.cs | 
| 
publicclassErrorWindowViewModel:ViewModelBase{publicErrorWindowViewModel(){}publicconststringErrorMessagePropertyName="ErrorMessage";privatestring_errorMessage;publicstringErrorMessage{get{return_errorMessage;}set{if(_errorMessage==value)return;_errorMessage=value;RaisePropertyChanged(ErrorMessagePropertyName);}}}
 | 
Vous êtes habitués maintenant, on doit rajouter ce viewmodel dans le localisateur de service et modifions le xaml pour changer le textbox ErrorTextBox pour avoir :
					
					| ErrorWindow.xaml | 
| 
<TextBox x:Name="ErrorTextBox" Height="90" TextWrapping="Wrap" IsReadOnly="True"    VerticalScrollBarVisibility="Auto" Text="{Binding ErrorMessage}"/>
 | 
(Laissons les autres chaines en dur dans le xaml)
					Maintenant, voyons comment est intanciée cette ErrorWindow. Dans le code behind de MainPage.xaml, on voit :
					
					| ErrorWindow.xaml.cs | 
| 
privatevoidContentFrame_NavigationFailed(objectsender,NavigationFailedEventArgs e){e.Handled=true;ChildWindow errorWin=newErrorWindow(e.Uri);errorWin.Show();}
 | 
On constate qu'on instancie la vue ChildWindow et qu'on passe un objet dans le constructeur. C'est ce constructeur qui mettait à jour le textblock, ce qu'on cherche à éviter.
					
				
					
					
					Comment indiquer alors au viewmodel que l'on souhaite mettre une valeur dans sa propriété ErrorMessage ? Comment accéder à ce viewmodel ? 
					On sait que le viewmodel est lié à la vue grâce à la propriété DataContext et on serait tenté d'utiliser cette propriété pour récupérer ce viewmodel. Mais cela nous obligerait à caster ce DataContext et créerait un couplage fort entre la vue et le viewmodel, ce qui est contraire à M-V-VM.
					Ce que nous propose le mvvm toolkit est un système de messages, sur base du pattern mediator. Dans cet exemple, nous allons plutôt utiliser 
 le mediator de Marlon Grech
  le mediator de Marlon Grech.
					Nous allons créer un nouveau projet Class Library pour importer les sources : DemoMvvm.Toolkit.
					Vous trouverez dans l'archive en téléchargement à la fin de l'article les sources du mediator où j'ai passé un petit coup de resharper.
					J'ai également créé un Singleton pour faciliter l'accès au mediator.
					Le but est de pouvoir informer le viewmodel qu'on lui passe une Uri, pour ce faire, on va utiliser le SingletonMediator et remplacer la méthode du dessus par :
					
| ErrorWindow.xaml.cs | 
| 
privatevoidContentFrame_NavigationFailed(objectsender,NavigationFailedEventArgs e){e.Handled=true;ChildWindow errorWin=newErrorWindow();SingletonMediator.Instance.NotifyColleagues(MediatorMessages.UriMessage,e.Uri);errorWin.Show();}
 | 
Une classe MediatorMessages a été créé pour l'occasion et contient les messages que l'on va utiliser pour communiquer :
					
					| MediatorMessages.cs | 
| 
publicstaticclassMediatorMessages{publicconststringExceptionObjectMessage="ExceptionObjectMessage";publicconststringUriMessage="UriMessage";}
 | 
Il faut maintenant que le viewmodel s'abonne à ce message.
Dans le constructeur, il s'enregistre auprès du mediator par :
					
					| ErrorWindowViewModel.cs | 
| 
SingletonMediator.Instance.Register(this);
 | 
et on pourra déclarer une méthode qui sera appelée lorsqu'on recevra le message
					
					| ErrorWindowViewModel.cs | 
| 
[MediatorMessageSink(MediatorMessages.UriMessage,ParameterType=typeof(Uri))]publicvoidOnErrorMessage(Uri uri){ErrorMessage="Page non trouvée : \""+uri+"\"";}
 | 
On constate l'utilisation d'un attribut pour indiquer que cette méthode sera appelée lors de la réception du message UriMessage, le paramètre envoyé étant du type Uri.
					Ainsi, on pourra mettre à jour la propriété ErrorMessage du viewmodel qui est bindée à un textbox dans le xaml.
					Cela nous permet d'épurer le fichier ErrorWindow.xaml.cs pour ne garder que le minimum :
					
					| ErrorWindow.xaml.cs | 
| 
publicpartialclassErrorWindow:ChildWindow{publicErrorWindow(){InitializeComponent();}privatevoidOKButton_Click(objectsender,RoutedEventArgs e){this.DialogResult=true;}}
 | 
Il reste à faire la même chose dans Application_UnhandledException, c'est le même principe, je ne le présenterai pas.
					En relançant l'application, on peut constater le résultat en voyant que la fenêtre affiche un "Page non trouvée", comme fait dans la méthode OnErrorMessage.
					On a pu voir que l'instanciation de la classe ChildWindow se faisait dans l'événement ContentFrame_NavigationFailed, dans le code behind de la vue. N'y aurait-il pas moyen de rendre ceci un peu plus mvvm-friendly ?
					Bien sur que si, grâce à la behavior EventToCommand.
					Rajoutons 2 nouveaux namespaces dans MainPage.xaml :
					
					| MainPage.xaml.cs | 
| 
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras"
 | 
Et transformons le contrôle navigation:Frame :
					
					| MainPage.xaml.cs | 
| 
<navigation:Frame x:Name="ContentFrame"Style="{StaticResource ContentFrameStyle}"Source="/Home2"Navigated="ContentFrame_Navigated"><i:Interaction.Triggers><i:EventTrigger EventName="NavigationFailed"><cmd:EventToCommand PassEventArgsToCommand="True"Command="{Binding NavigateFailedCommand}"/></i:EventTrigger></i:Interaction.Triggers><navigation:Frame.UriMapper><uriMapper:UriMapper><uriMapper:UriMapping Uri=""MappedUri="/Views/Home.xaml"/><uriMapper:UriMapping Uri="/{pageName}"MappedUri="/Views/{pageName}.xaml"/></uriMapper:UriMapper></navigation:Frame.UriMapper></navigation:Frame>
 | 
On peut voir que j'ai supprimé l'événement NavigationFailed et que je l'ai remplacé par un Trigger Blend. On utilise ensuite EventToCommand pour lier l'événement à une commande. Notez l'utilisation de PassEventArgsToCommand à true pour passer les arguments à la commande.
					Il faudra donc créer un viewmodel pour MainPage comme on sait faire désormais et lui ajouter une commande NavigateFailedCommand.
					
					| MainPageViewModel.cs | 
| 
publicclassMainPageViewModel:ViewModelBase{publicMainPageViewModel(){NavigateFailedCommand=newRelayCommand<NavigationFailedEventArgs>(e=>{e.Handled=true;ChildWindow errorWin=newErrorWindow();SingletonMediator.Instance.NotifyColleagues(MediatorMessages.UriMessage,e.Uri);errorWin.Show();});}publicICommand NavigateFailedCommand{get;internalset;}}
 | 
On crée donc une commande en déclarant une propriété de type ICommand qui est un objet du Framework.net. On l'instanciera grâce à la classe RelayCommand du MVVM Toolkit et on pourra spécifier le type du paramètre attendu.
					Ici je crée une commande dans sa forme la plus simple. Le toolkit permet de préciser éventuellement une méthode permettant la détermination de l'activation d'une commande.
					On peut désormais supprimer l'événement en question dans le code behind de MainPage.xaml.cs et repositionner la propriété Source du navigation:frame à "/Home".
					
				
				3.2.8.Lier les animations
	
					
					Passons désormais à l'événement ContentFrame_Navigated. Ici ça se corse. Que fait la méthode ?
					
					| MainPage.xaml.cs | 
| 
foreach(UIElement childinLinksStackPanel.Children){HyperlinkButton hb=childasHyperlinkButton;if(hb!=null&&hb.NavigateUri!=null){if(hb.NavigateUri.ToString().Equals(e.Uri.ToString())){VisualStateManager.GoToState(hb,"ActiveLink",true);}else{VisualStateManager.GoToState(hb,"InactiveLink",true);}}}
 | 
En cas de navigation, elle regarde tous les HyperlinkButton du LinksStackPanel, et effectue une animation sur l'élément de navigation grâce au VisualStateManager pour le passer d'actif à inactif.
					Pour rendre ceci conforme à M-V-VM, on utilise dans un premier temps L'EventToCommand pour brancher l'événement Navigated sur une commande :
					
					| MainPage.xaml | 
| 
<navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}"                  Source="/Home"><i:Interaction.Triggers><i:EventTrigger EventName="Navigated"><cmd:EventToCommand PassEventArgsToCommand="True"                                Command="{Binding NavigatedCommand}" /></i:EventTrigger><i:EventTrigger EventName="NavigationFailed"><cmd:EventToCommand PassEventArgsToCommand="True"                                Command="{Binding NavigateFailedCommand}" /></i:EventTrigger></i:Interaction.Triggers><navigation:Frame.UriMapper><uriMapper:UriMapper><uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/><uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/></uriMapper:UriMapper></navigation:Frame.UriMapper></navigation:Frame>
 | 
On pourra définir la commande dans le viewmodel :
					
					| MainPageViewModel.cs | 
| 
publicICommand NavigatedCommand{get;internalset;}publicMainPageViewModel(){NavigatedCommand=newRelayCommand<NavigationEventArgs>(e=>{});}
 | 
Mais comment va-t-on pouvoir interagir depuis le viewmodel avec le VisualStateManager sur les contrôles qui nous sont inconnus ?
					La solution que je vais exposer ici n'est sans doute pas la meilleure, si vous connaissez un meilleur moyen d'y parvenir, je suis preneur.
					La première chose à faire va être de se doter d'une classe qui fourni un propriété attachée pour chaque contrôle afin de permettre la transition jusqu'à un nouvel état.
					Nous allons pour ce faire récupérer 
 la classe VisualStates d'Alex van Beek
 la classe VisualStates d'Alex van Beek.
					
| VisualStates.cs | 
| 
publicstaticclassVisualStates{publicstaticreadonlyDependencyProperty CurrentStateProperty=DependencyProperty.RegisterAttached("CurrentState",typeof(String),typeof(VisualStates),newPropertyMetadata(TransitionToState));publicstaticstringGetCurrentState(DependencyObject obj){return(string)obj.GetValue(CurrentStateProperty);}publicstaticvoidSetCurrentState(DependencyObject obj,stringvalue){obj.SetValue(CurrentStateProperty,value);}privatestaticvoidTransitionToState(objectsender,DependencyPropertyChangedEventArgs args){Control c=senderasControl;if(c!=null)
            VisualStateManager.GoToState(c,(string)args.NewValue,true);elsethrownewArgumentException("CurrentState is only supported on the Control type");}}
 | 
On pourra définir cette propriété attachée sur les HyperlinkButton :
					
					| MainPage.xaml | 
| 
<HyperlinkButton x:Name="Link1" Style="{StaticResource LinkStyle}"                 NavigateUri="/Home" TargetName="ContentFrame" Content="home"                 mvvm:VisualStates.CurrentState="{Binding HomeCurrentState}"/><Rectangle x:Name="Divider1" Style="{StaticResource DividerStyle}"/><HyperlinkButton x:Name="Link2" Style="{StaticResource LinkStyle}"                 NavigateUri="/About" TargetName="ContentFrame" Content="about"                 mvvm:VisualStates.CurrentState="{Binding AboutCurrentState}"/>
 | 
et les lier à des propriétés du viewmodel :
					
					| MainPageViewModel.cs | 
| 
publicconststringHomeCurrentStatePropertyName="HomeCurrentState";privatestring_homeCurrentState;publicstringHomeCurrentState{get{return_homeCurrentState;}set{if(_homeCurrentState==value)return;_homeCurrentState=value;RaisePropertyChanged(HomeCurrentStatePropertyName);}}publicconststringAboutCurrentStatePropertyName="AboutCurrentState";privatestring_aboutCurrentState;publicstringAboutCurrentState{get{return_aboutCurrentState;}set{if(_aboutCurrentState==value)return;_aboutCurrentState=value;RaisePropertyChanged(AboutCurrentStatePropertyName);}}
 | 
Ainsi, notre commande pourra s'écrire :
					
					| MainPageViewModel.cs | 
| 
NavigatedCommand =newRelayCommand<NavigationEventArgs>(e=>{if(e.Uri.ToString()=="/Home"){HomeCurrentState="ActiveLink";AboutCurrentState="InactiveLink";}else{HomeCurrentState="InactiveLink";AboutCurrentState="ActiveLink";}});
 | 
On pourra supprimer la méthode ContentFrame_Navigated dans le code behind de MainPage.xaml.
					Et voilà, le tour est joué.
					
				
			
		
		4.Un peu plus loin dans les commandes
			
			Nous avons eu un aperçu des commandes dans le chapitre 3 avec des commandes relativement simples où nous transformions un événement en une commande grâce à EventToCommand.
				Le MVVM Light Toolkit nous permet de créer des commandes plus évoluées, avec passage de paramètres à la commande. Voyons ceci d'un peu plus près.
			
			
				
				Créons un nouveau TextBlock qui sera lié à l'heure courante :
				
				| Home.xaml | 
| 
<TextBlock Style="{StaticResource ContentTextStyle}"                                    Text="{Binding CurrentTime}"/>
 | 
Avec dans le ViewModel :
				
				| HomeViewModel.cs | 
| 
publicstringCurrentTime{get{returnDateTime.Now.ToLongTimeString();}}
 | 
Créons maintenant un nouveau bouton sur la vue Home. Ce bouton nous permettra de forcer le rafraichissement du TextBlock. Il sera associé à une commande qui permettra de notifier à la vue qu'elle doit rafraichir le TextBlock. 
				La commande sera :
				
				| MainViewModel.cs | 
| 
publicICommand ChangeCommand{get;internalset;}publicHomeViewModel(){ChangeCommand=newRelayCommand(()=>RaisePropertyChanged("CurrentTime"),()=>true);}
 | 
Le premier paramètre de RelayCommand correspond à l'action, ici on demande au mécanisme de binding de mettre à jour la propriété liée CurrentTime. Le deuxième paramètre permet d'indiquer si la commande est active ou pas, ici on indique qu'elle est toujours active. Mais on pourrait imaginer de permettre l'activation de la commande uniquement pour les trois premiers clicks ...
				
Pour relier la commande au click du bouton, le MVVM Light Toolkit nous fournit une propriété dépendante, spécifique au bouton. On l'utilisera de cette façon :
				
				| MainViewModel.cs | 
| 
<Button cmd:ButtonBaseExtensions.Command="{Binding ChangeCommand}"Content="Mettre à jour l'heure"Width="150"Height="35"/>
 | 
Ainsi, en ré-exécutant l'application, chaque click provoquera le rafraichissement de l'heure.
				
				
Figure 09: La commande rafraichit l'heure une fois activée.
La classe RelayCommand dispose d'une version générique RelayCommand<T> qui, associée à l'attribut CommandParameter permet de passer un paramètre à la commande. On pourra s'en servir par exemple pour créer une zone de recherche (un TextBox, un Button et un TextBlock pour le résultat de la recherche) :
				
				| Home.xaml | 
| 
<TextBox x:Name="search" HorizontalAlignment="Left" Width="150"/><Button cmd:ButtonBaseExtensions.Command="{Binding SearchCommand}" 	cmd:ButtonBaseExtensions.CommandParameter="{Binding ElementName=search, Path=Text}" 	Content="Rechercher" Width="100" Height="35" /><TextBlock Style="{StaticResource ContentTextStyle}" Text="{Binding SearchResult}"/>
 | 
J'utilise ici la propriété ButtonBaseExtensions.CommandParameter pour indiquer que le paramètre à passer à la commande sera à trouver dans la propriété Text de l'élément search, le TextBox. Ceci m'évite d'avoir une propriété de mon ViewModel bindée au TextBox.
				Je crée par contre une propriété SearchResult dans le ViewModel pour afficher le résultat : 
				
				| MainViewModel.cs | 
| 
publicconststringSearchResultPropertyName="SearchResult";privatestring_searchResult=null;publicstringSearchResult{get{return_searchResult;}privateset{if(_searchResult==value)return;_searchResult=value;RaisePropertyChanged(SearchResultPropertyName);}}
 | 
Et la commande SearchCommand :
				
				| MainViewModel.cs | 
| 
publicICommand SearchCommand{get;internalset;}publicHomeViewModel(){SearchCommand=newRelayCommand<string>(textToSearch=>SearchResult="Recherche sur "+textToSearch,textToSearch=>textToSearch!="abc");}
 | 
On remarque l'utilisation de la version générique de RelayCommand. On pourra noter également que j'ai légèrement changé la condition qui rend active ou inactive la commande. Si vous tapez "abc" dans le TextBox, la commande sera inactive. On pourra observer que le bouton se grise.
				
				
Figure 10: La commande se grise lorsque la condition de désactivation est remplie.
5.Utiliser les templates pour créer un nouveau projet M-V-VM
			
			Lors de l'installation, nous avons eu l'occasion d'extraire les templates Visual Studio pour MVVM Light Toolkit.
			Après avoir redémarré Visual Studio, on peut constater les nouveaux templates disponibles :
			
			
Figure 11: Création d'un projet avec le template MVVM Light Toolkit.
Si vous créez un nouveau projet avec ce template, on retrouve une application avec l'arborescence suivante : 
			
			
Figure 12: Arborescence d'un projet créé avec le template MVVM Light Toolkit.
On retrouve notamment la classe ViewModelLocator, utilisée dans le cadre du pattern Service Locator.
			On retrouve également un ViewModel : MainViewModel.
Dans ce viewmodel, un nouvel élément dans le constructeur :
			
			| MainViewModel.cs | 
| 
publicMainViewModel(){if(IsInDesignMode){}else{}}
 | 
6.Téléchargement et démo
			
			
			
				
		7.Conclusion
			
			Ce tutoriel constitue une introduction au design pattern M-V-VM en utilisant le MVVM Toolkit. Sans revenir sur les avantages de ce design pattern, on a pu voir qu'il était relativement facile à adopter grâce aux classes fournies par le toolkit. 
Cet article a d'abord présenté rapidement le pattern, puis je me suis attaché à transformer l'application exemple d'un projet de navigation Silverlight pour respecter ce pattern. On a pu voir la création de ViewModel ainsi que le binding de ses propriétés à la vue. Le Service Locator a été abordé pour montrer comment lier le ViewModel au DataContext. On a également pu voir comment utiliser les commandes, comment transformer un événement en commande et comment utiliser le pattern Mediator pour communiquer entre les ViewModel. Une solution a également été présentée pour utiliser M-V-VM avec les transitions d'états. Nous avons enfin exploré les commandes basiques qui manquaient à notre application exemple et avons vu les templates de 
			création de projet fournis par le Toolkit.
			J'espère que ce tutoriel a pu vous être utile et vous a donné envie d'utiliser M-V-VM, à travers du MVVM Toolkit.
			
		
		Remerciements
			
			
			J'espère pouvoir remercier Laurent Bugnion d'avoir a eu la gentillesse de relire cet article pour voir si je racontais pas trop de bétises :).
			Je remercie l'équipe Dotnet pour leurs relectures attentives du document et particulièrement Skyounet et Thomas Lebrun pour leurs remarques.
			
		
		
			
			
			Si vous constatez une erreur dans le tutorial, dans le source, dans la programmation ou pour toutes informations, n'hésitez pas à me contacter 
par mail, ou 
par le forum.
			


 
		
Les sources présentées sur cette page sont libres de droits
et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation
constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ©
2010 Nico-pyright(c). Aucune reproduction, même partielle, ne peut être faite
de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation
expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et
jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.