Les objets mis en oeuvre sont :
Prism.UnityExtensionsPrism.UnityBootstrapper
<i:Interaction.Trigger
<prism:InteractionRequestTrigger
Prism.Interactivity.InteractionRequest
Prism.Commands.DelegateCommand
Prism.Events.EventAggregator
namespace HelloWorld
{
public class Bootstrapper : UnityBootstrapper
{
protected override void ConfigureModuleCatalog()
{
base.ConfigureModuleCatalog();
ModuleCatalog moduleCatalog = (ModuleCatalog)this.ModuleCatalog;
moduleCatalog.AddModule(typeof(HelloWorldModule.HelloWorldModule));
}
Le module HelloWorldModule utilise un projet Class Librairy "Infrastructure" qui propose une interface IRegionManagerAware et un objet PopupWindowAction qui vont permettre dans les Views du HelloWorldModule de popuper des Window de la façon suivante :
<UserControl x:Class="HelloWorldModule.Views.HelloWorldView"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:inf_int="clr-namespace:Infrastructure.InteractionRequests;assembly=Infrastructure"
xmlns:local="clr-namespace:HelloWorldModule.Views"
...>
<i:Interaction.Triggers>
<prism:InteractionRequestTrigger SourceObject="{Binding SelectClientRequest, Mode=OneWay}">
<inf_int:PopupWindowAction>
<inf_int:PopupWindowAction.WindowContent>
<local:SelectClientView />
</inf_int:PopupWindowAction.WindowContent>
</inf_int:PopupWindowAction>
</prism:InteractionRequestTrigger>
</i:Interaction.Triggers>
Des fenêtres secondaires vont permettre à l'utilisateur de choisir une série d'actions dont le résultat sera afficher dans la fenêtre principale : HelloWorldView.xaml.
Une fois les Interaction.Trigger correctement configurées, il faut encore un bouton dont la propriété Command est bindée sur RaiseSelectClient :
<Button Margin="5" Content="Raise Select Client View" Command="{Binding RaiseSelectClient}" />
Le résultat sera afficher dans TextBlock dont la propriété Text est Bindée sur la propriété Result du ViewModel :
<TextBlock Margin="5" FontWeight="Bold" Foreground="DarkRed" Text="{Binding Result}"/>
On obtient ainsi à l'exécution de la fenêtre principal :
\\PopupWindowActionSample\HelloWorldModule\Views\HelloWorldView.xaml
Et lorsque l'on clique sur le bouton "Raise Select Client View", on obtient la fenêtre cliente suivante :
En sélectionnant "John Doe" et en cliquant sur "OK" la fenêtre principale récupère le choix utilisateur et l'on obtient le résultat suivant dans la fenêtre principal :
On a donc bien un allé et retour d'une information, d'un choix utilisateur effectuer dans une PopupWindow et que l'on récupère pour l'afficher dans le Module Principal.
namespace HelloWorldModule.ViewModels
{
public InteractionRequest<Notification> SelectClientRequest { get; private set; }
public ICommand RaiseSelectClient { get; private set; }
public HelloWorldViewModel()
{
this.SelectClientRequest = new InteractionRequest<Notification>();
this.RaiseSelectClient = new DelegateCommand(this.OnRaiseSelectClient);
Avec l'utilisation de l'objet Notification, on rentre dans l'utilisation de la DLL, Microsoft.Practices.Prism.Interactivity.dll :
D'autre part, la commande du bouton "Raise Select Client View" est bindé sur la DelegateCommand RaiseSelectClient qui exécute l'Action OnRaiseSelectClient :
private void OnRaiseSelectClient()
{
this.SelectClientRequest.Raise(
new Notification { Title = "Clients" });
}
Ce même mécanisme permet aux deux fonctions OnRaiseConfirmation et OnRaiseNotification de modifier la propriété Result du HelloWorldViewModel pour indiquer le résultat obtenu dans le TexBlock dont la propriété Text est Bindée sur le Result.
Dans le cas de OnRaiseSelectItem, on récupère la propriété string SelectedItem du SelectItemViewModel pour afficher le résultat.
Dans le HelloWorldViewModel on s'enregistre à l’évènement : ClientSelectedEvent et on y inscrit l'Action ClientSelected qui sera exécutée sur réception de l'évènement :
public HelloWorldViewModel()
{
this.eventAggregator = ServiceLocator.Current.GetInstance<EventAggregator>();
this.eventAggregator.GetEvent<ClientSelectedEvent>().Subscribe(this.ClientSelected);
...
}
public void ClientSelected(ClientData client)
{
if (client != null)
{
this.Result = "The user selected the client: " + client.Name;
}
else
{
this.Result = "The user didn't select a client.";
}
}
Dans le SelectClientViewModel, on publie les données choisies par l'utilisateur :
public ClientData SelectedClient { get; set; }
protected void OkAction()
{
this.eventAggregator.GetEvent<ClientSelectedEvent>().Publish(this.SelectedClient);
if (this.HostWindow != null)
{
this.HostWindow.Close();
}
}
Et voilà comment cela fonctionne ...
Prism.Events.EventAggregator
Architecture de du projet PopupWindowAction Sample
Un projet Windows Application "HelloWorld" utilise Prism et Unity pour charger le module "HelloWorldModule" :namespace HelloWorld
{
public class Bootstrapper : UnityBootstrapper
{
protected override void ConfigureModuleCatalog()
{
base.ConfigureModuleCatalog();
ModuleCatalog moduleCatalog = (ModuleCatalog)this.ModuleCatalog;
moduleCatalog.AddModule(typeof(HelloWorldModule.HelloWorldModule));
}
Le module HelloWorldModule utilise un projet Class Librairy "Infrastructure" qui propose une interface IRegionManagerAware et un objet PopupWindowAction qui vont permettre dans les Views du HelloWorldModule de popuper des Window de la façon suivante :
<UserControl x:Class="HelloWorldModule.Views.HelloWorldView"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:inf_int="clr-namespace:Infrastructure.InteractionRequests;assembly=Infrastructure"
xmlns:local="clr-namespace:HelloWorldModule.Views"
...>
<i:Interaction.Triggers>
<prism:InteractionRequestTrigger SourceObject="{Binding SelectClientRequest, Mode=OneWay}">
<inf_int:PopupWindowAction>
<inf_int:PopupWindowAction.WindowContent>
<local:SelectClientView />
</inf_int:PopupWindowAction.WindowContent>
</inf_int:PopupWindowAction>
</prism:InteractionRequestTrigger>
</i:Interaction.Triggers>
Des fenêtres secondaires vont permettre à l'utilisateur de choisir une série d'actions dont le résultat sera afficher dans la fenêtre principale : HelloWorldView.xaml.
PopupWindowAction Sample Prism InteractionRequest |
PopupWindowAction Sample Exécution
Ici je vais focusser sur le dernier exemple de popupwindow dont la propriété SourceObject est bindée sur SelectClientRequest.Une fois les Interaction.Trigger correctement configurées, il faut encore un bouton dont la propriété Command est bindée sur RaiseSelectClient :
<Button Margin="5" Content="Raise Select Client View" Command="{Binding RaiseSelectClient}" />
Le résultat sera afficher dans TextBlock dont la propriété Text est Bindée sur la propriété Result du ViewModel :
<TextBlock Margin="5" FontWeight="Bold" Foreground="DarkRed" Text="{Binding Result}"/>
On obtient ainsi à l'exécution de la fenêtre principal :
\\PopupWindowActionSample\HelloWorldModule\Views\HelloWorldView.xaml
Fenêtre principale de PopupWindowAction Sample |
Client Window View |
PopupWindowAction Sample après un choix utilisateur |
PopupWindowAction comment ça marche ?
D'une part, la propriété SourceObject (EventTriggerBase.SourceObject) de l'InteractionRequestTrigger est bindée sur la commande SelectClientRequest dans la View et dans le ViewModel on trouve le code C# suivant :namespace HelloWorldModule.ViewModels
{
public InteractionRequest<Notification> SelectClientRequest { get; private set; }
public ICommand RaiseSelectClient { get; private set; }
public HelloWorldViewModel()
{
this.SelectClientRequest = new InteractionRequest<Notification>();
this.RaiseSelectClient = new DelegateCommand(this.OnRaiseSelectClient);
Avec l'utilisation de l'objet Notification, on rentre dans l'utilisation de la DLL, Microsoft.Practices.Prism.Interactivity.dll :
Microsoft.Practices.Prism.Interactivity, Notification et Confirmation |
private void OnRaiseSelectClient()
{
this.SelectClientRequest.Raise(
new Notification { Title = "Clients" });
}
Ce même mécanisme permet aux deux fonctions OnRaiseConfirmation et OnRaiseNotification de modifier la propriété Result du HelloWorldViewModel pour indiquer le résultat obtenu dans le TexBlock dont la propriété Text est Bindée sur le Result.
Dans le cas de OnRaiseSelectItem, on récupère la propriété string SelectedItem du SelectItemViewModel pour afficher le résultat.
Utilisation de l'EventAggregator pour récupérer les données du Modèle
Dans le cas de OnRaiseSelectClient, pour récupérée des données structurées du model (ClientData) , on utilise alors le mécanisme d'EventAggregator.Dans le HelloWorldViewModel on s'enregistre à l’évènement : ClientSelectedEvent et on y inscrit l'Action ClientSelected qui sera exécutée sur réception de l'évènement :
public HelloWorldViewModel()
{
this.eventAggregator = ServiceLocator.Current.GetInstance<EventAggregator>();
this.eventAggregator.GetEvent<ClientSelectedEvent>().Subscribe(this.ClientSelected);
...
}
public void ClientSelected(ClientData client)
{
if (client != null)
{
this.Result = "The user selected the client: " + client.Name;
}
else
{
this.Result = "The user didn't select a client.";
}
}
Dans le SelectClientViewModel, on publie les données choisies par l'utilisateur :
public ClientData SelectedClient { get; set; }
protected void OkAction()
{
this.eventAggregator.GetEvent<ClientSelectedEvent>().Publish(this.SelectedClient);
if (this.HostWindow != null)
{
this.HostWindow.Close();
}
}
Et voilà comment cela fonctionne ...
PopupWindowAction le projet Infrastructure
Si je m'arrêtais là, je passerais à côté de l'un des objets principaux de ce projet. De façon classique Damian a créé un projet dans lequel il range ce qui pourrait faire partie d'un framework de base.
Et pour afficher une PopupWindow sur InteractionRequestTrigger Damian définit sa propre PopupWindowAction qui dérive de TriggerAction :
namespace Infrastructure.InteractionRequests
{
/// <summary>
/// TODO: Update summary.
/// </summary>
public class PopupWindowAction : TriggerAction<FrameworkElement>
/// <summary>
/// The content of the child window to display as part of the popup.
/// </summary>
public static readonly DependencyProperty WindowContentProperty =
DependencyProperty.Register(
"WindowContent",
typeof(FrameworkElement),
typeof(PopupWindowAction),
new PropertyMetadata(null));
Un peu d'humour dans les commentaires cela ne fait pas de mal, j'adore la rubrique TODO !
C'est objet est utilisé dans l'InteractionRequestTrigger que l'on a vu au tout début de cet article.
Franchement, je ne trouve pas cela si simple ... Et je reviendrais sur cet exemple pour compléter mon analyse de ce projet que je trouve extrêmement intéressant. Tout ce code pour afficher une ChildWindow c'est véritablement bluffant !