|
UIManager pour MVVMLight : gestionnaire de contrôles light
Richard Clark
15/10/09
(Vu
1348)
Dans la plupart de nos applications, on a besoin de charger des contrôles utilisateurs. Le problème est que si l'applcation devient importante, il n'est pas question au démarrage de l'application de charger l'ensemble de ces contrôles. Il faut donc impérativement les charger dynamiquement. Cette problématique existe aussi bien pour les applications Windows Forms classiques, que pour les applications WPF et Silverlight. La solution exposée ici fonctionne pour WPF et Silverlight, mais peut être aisément applicable aux Windows Forms (quoique il y a un problème sans doute de libération de mémoire, les UserControls étant IDisposable et référencés par leur Component). L'idée s'inspire de la notion de Region exposée par le Framework MVVM Prism de Microsoft mais repose sur l'excellent Framework de Galasoft, MVVM Light Framework. UIManager La première étape consiste a créer une classe UIManager qui va se charger d'enregistrer les différentes views de notre application. Le but est stocker la définition de la View, pas une instance de la view (ce qui permet d'être "light"). Notre classe (qui sera un singleton) UIManager gère donc un dictionnaire de ces définitions de view. Ces définitions de View sont représentées par une classe ViewDefinition : public class ViewDefinition
{
public ViewDefinition(string regionKey, Type viewType)
{
if (viewType == null)
throw new ArgumentNullException("viewType", "Le type ne peut être null");
if (!viewType.IsSubclassOf(typeof(UIElement)))
throw new ArgumentException("Le type doit être un UIElement", "viewType");
this._controlType = viewType;
this._regionKey = regionKey;
}
private UIElement _instance;
private Type _controlType;
private string _regionKey;
public Type ControlType
{
get { return _controlType; }
}
public string RegionKey
{
get { return _regionKey; }
}
public UIElement GetInstance()
{
if (_instance == null)
_instance = Activator.CreateInstance(this.ControlType) as UIElement;
return _instance;
}
}
On stocke le Type de la View et on ne crée son instance que quand on en a besoin, grâce à la méthode GetInstance. Maintenant dans notre UIManager, on va stocker ces ViewDefinition dans un dictionnaire : public class UIManager
{
private static readonly UIManager Instance = new UIManager();
private readonly Dictionary<string, ViewDefinition> _uiElementDefinitions = new Dictionary<string, ViewDefinition>();
public static void RegisterView(string viewKey, string regionKey, string typeName)
{
RegisterView(viewKey, regionKey, Type.GetType(typeName));
}
public static void RegisterView(string viewKey, string regionKey, Type viewType)
{
if (viewKey == null)
throw new ArgumentNullException("viewKey", "La clé ne peut être null");
if (viewType == null)
throw new ArgumentNullException("viewType", "Le type de la view ne peut être null");
Instance._uiElementDefinitions.Add(viewKey, new ViewDefinition(regionKey, viewType));
}
public static UIElement GetView(string viewKey)
{
return Instance._uiElementDefinitions[viewKey].GetInstance();
}
public static ViewDefinition GetViewDefinition(string viewKey)
{
return Instance._uiElementDefinitions[viewKey];
}
}
Pour enregistrer un contrôle, une view dans notre UIManager, on utilisera ses méthodes RegisterView. Pour obtenir une ViewDefinition ou une instance de la View, on utilisera ses méthodes GetView ou GetViewDefinition. Les Regions Notre Form principale est décomposée en Region. Une Region est en réalité un contrôle container. Pour mon application, j'ai décidé qu'elle se décomposerait en 3 régions :
J'ai donc les regions Left, Middle et Right. Dans le XAML de ma Windows principale, ce sont en réalité 3 Border : <Image Source="Images/logo.png" Height="90" HorizontalAlignment="Left"/> <Border x:Name="bdLeft" Grid.Row="1" /> <Border x:Name="bdMiddle" Grid.Column="2" Grid.Row="1"/> <Border x:Name="bdRight" Grid.Column="4" Grid.Row="1"/> Vous voyez dans la capture que chaque région contient déjà un UserControl : La liste des Tags, des Discussions et la Discussion. Dans le Loaded de ma Windows principale, j'ai donc enregistrer ces 3 contrôles avec l'assignation de la région : UIManager.RegisterView("ListeMessages", MainPage.MiddleRegion, typeof(View.ListeMessages));
UIManager.RegisterView("ListeTags", MainPage.LeftRegion, typeof(View.ListeTags));
UIManager.RegisterView("Message", MainPage.RightRegion, typeof(View.MessageView));
NB : MainPage.MiddleRegion & co sont des constantes de type string. Activation des contrôles Reste maintenant à pouvoir activer une View. On va cette fois utiliser le Messenger du framework MVVMLight pour cela. L'idée est d'envoyer un message, que ce message soit réceptionné par la Windows principale qui utilise le UIManager pour placer le bon contrôle au bon endroit. Donc on a besoin de créer une première classe qui va typer notre message : public class ShowViewMessage : MessageBase
{
public ShowViewMessage() : base(null, null)
{}
public ShowViewMessage(object sender) : base(sender, null)
{}
public ShowViewMessage(object sender, object target) : base(sender, target)
{}
public string ViewKey { get; set; }
public object Parameter { get; set; }
}
Quand on va broadcaster un message de ce type, on va juste indiquer que l'on veut activer la View avec la clé ViewKey. Par exemple, ou que je sois dans mon application, si j'envoi un message de type ShowViewMessage avec comme ViewKey "ListeMessages", j'indique en réalité que je veux que le contrôle View.ListeMessages soit affiché. : Messenger.Default.Send(new ShowViewMessage(this) { ViewKey = "ListeMessages" });
Et c'est de la responsabilité de la Windows principale de s'abonner à ce type de message : Messenger.Default.Register(this, delegate(ShowViewMessage message) {ShowViewMessage_Received(message);});
et de réagir en fonction : private void ShowViewMessage_Received(ShowViewMessage message)
{
// récupère l'instance
ViewDefinition newViewDefinition = UIManager.GetViewDefinition(message.ViewKey);
// place l'instance dans la form
UIElement instance = newViewDefinition.GetInstance();
Border targetBorder = null;
switch (newViewDefinition.RegionKey)
{
case MainPage.LeftRegion:
targetBorder = this.bdLeft;
break;
case MainPage.MiddleRegion:
targetBorder = this.bdMiddle;
break;
case MainPage.RightRegion:
targetBorder = this.bdRight;
break;
default:
throw new ArgumentException("La region cible est inconnue : " + newViewDefinition.RegionKey, "message");
}
targetBorder.Child = newViewDefinition.GetInstance();
// averti que l'instance est activée
Messenger.Default.Send(new ViewActivatedMessage(message.Sender, message.Target) {Parameter = message.Parameter});
}
Remarquez la dernière ligne : je renvoi un nouveau message de type ViewActivatedMessage. Cela me permet de passer des paramètres à la View que je veux activer. A elle d'écouter ce type de message pour en faire ce qu'elle veut. Exemple : imaginer une première View avec la liste des Discussions. Quand je clique sur un bouton, j'envoi un message de type ShowViewMessage avec comme Parameter la discussion concernée. La Windows principale récupère ce message, place l'instance de la View : DetailDiscussion et envoi le message de type ViewActivatedMessage. Ce dernier est écouté par le ViewModel de la View DetailDiscussion et affiche ainsi la discussion sans toucher à ses autres propriétés. Vous trouverez le code complet de cet exemple ici. Je suis bien entendu à l'écoute de vos critiques "constructives" pour me dire ce que vous en pensez. Richard Clark
Vos commentaires
Vous devez être identifié pour pouvoir commenter l'article. |



