Introduction
Prism est un framework qui permet de développer des applications composites.
Design de l'application
Erreur lors de la création de la vignette : /bin/bash: /usr/bin/convert: No such file or directory Error code: 127
Shell: contient les regions
Regions: les vues sont injectées dans les regions au runtime
Modules: fonctionnalités majeures de l'application
Bootstrapper: initialise Prism
Install
NuGet
Prism.Wpf Dependencies: Prism.Core CommonServiceLocator
Ajoute des templates Prism dans les nouveaux projets Visual Studio Visual C# → Prism .
À la création d'un BlankApp, choix du conteneur IoC:
Ajoute aussi des snippets: propp cmd cmdfull cmdg cmdgfull
Hierarchie des fichiers
App.xaml
<prism:PrismApplication x:Class ="Prism1.App"
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism ="http://prismlibrary.com/"
xmlns:local ="clr-namespace:Prism1" >
<Application.Resources />
</prism:PrismApplication >
App.xaml.cs
protected override Window CreateShell ()
{
return Container.Resolve <MainWindow >();
}
protected override void RegisterTypes (IContainerRegistry containerRegistry )
{ }
public override void Initialize ()
{
base .Initialize ();
IRegionManager regionManager = ServiceLocator.Current.GetInstance <IRegionManager >();
var viewA = Container.Resolve <ViewA >();
IRegion region = regionManager.Regions["ContentRegion" ];
region.Add (viewA );
}
Views/MainWindow.xaml
<Window x:Class ="Prism1.Views.MainWindow"
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism ="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel ="True"
Title ="{Binding Title}" Height ="350" Width ="525" >
<Grid >
<ContentControl prism:RegionManager.RegionName ="ContentRegion" />
</Grid >
</Window >
ViewModels/MainWindowViewModel.cs
public class MainWindowViewModel : BindableBase
{
private string _title = "Prism Application" ;
public string Title
{
get => _ title;
set => SetProperty (ref _ title , value );
}
public MainWindowViewModel ()
{ }
}
Hierarchie des fichiers - OLD
Classe responsable de l'initialisation:
Core Services (ModuleManager, ModuleCatalog, ModuleInitializer, RegionManager, EventAggregator, LoggerFacade, ServiceLocator)
Application specific services
App.xaml.cs
protected override void OnStartup (StartupEventArgs e )
{
base .OnStartup (e );
var bootstrapper = new Bootstrapper ();
bootstrapper.Run ();
}
Bootstrapper.cs
public class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell ()
{
return Container.Resolve <Shell >();
}
protected override void InitializeShell ()
{
base .InitializeShell ();
App.Current.MainWindow = (Window)Shell;
App.Current.MainWindow.Show ();
}
protected override IModuleCatalog CreateModuleCatalog ()
{
var catalog = new ModuleCatalog ();
catalog.AddModule (typeof (ModuleAModule ));
return catalog;
}
protected override RegionAdapterMappings ConfigureRegionAdapterMappings ()
{
RegionAdapterMappings mappings = base .ConfigureRegionAdapterMappings ();
mappings.RegisterMapping (typeof (StackPanel ), Container .Resolve <StackPanelRegionAdapter >());
return mappings;
}
}
Shell
Représente la fenêtre principale (MainWindow) qui contiendra le contenu de l'application.
Équivalent de la Master Page ASP.NET, représente le template de vue de l'application.
Le Shell contient des regions dans lesquelles les vues seront injectées.
Region
C'est un espace réservé (placeholder) pour du contenu dynamique. Permet de nommer un espace où placer une vue.
Une région n'a aucune connaissance de la vue qu'elle va contenir.
Une région n'est pas un Control mais une Attached Property qui s'applique à un Control.
Implémente IRegion .
Shell.xaml
<Window xmlns:prism ="http://prismlibrary.com/" >
<DockPanel >
<ContentControl prism:RegionManager.RegionName ="ToolbarRegion" DoclkPanel.Dock ="Top" />
<ContentControl prism:RegionManager.RegionName ="ContentRegion" />
Region Adapter
Permet d'adapter un UI Control pour qu'il devienne une région. Il faut un adapter pour chaque type de UI Control.
ContentControlRegionAdapter
ItemsControlRegionAdapter
SelectorRegionAdapter
TabControl
Custom Region Adapter
StackPanelRegionAdapter.cs
class StackPanelRegionAdapter : RegionAdapterBase <StackPanel >
{
public StackPanelRegionAdapter (IRegionBehaviorFactory regionBehaviorFactory ) : base (regionBehaviorFactory )
{
}
protected override void Adapt (IRegion region , StackPanel regionTarget )
{
region.Views.CollectionChanged += (sender, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (FrameworkElement element in e.NewItems)
{
regionTarget.Children.Add (element );
}
}
};
}
protected override IRegion CreateRegion ()
{
return new AllActiveRegion ();
}
}
Bootstrapper.cs
protected override RegionAdapterMappings ConfigureRegionAdapterMappings ()
{
var mappings = base .ConfigureRegionAdapterMappings ();
mappings.RegisterMapping (typeof (StackPanel ), Container .Resolve <StackPanelRegionAdapter >());
return mappings;
}
RegionContext
Attached Property permettant de propager les données de la région à la vue.
Module
Package regroupant toutes les fonctionnalités d'une partie de l'application. Un module correspond à une fonctionnalité majeure de l'application.
Les modules sont dans des projets séparés de type Prism Module ou WPF User Control Library.
Modules/Test/TestModule.cs
public class TestModule : IModule
{
public void OnInitialized (IContainerProvider containerProvider )
{
IRegionManager regionManager = ServiceLocator.Current.GetInstance <IRegionManager >();
regionManager.RegisterViewWithRegion ("ContentRegion" , typeof (ViewA ));
}
public void RegisterTypes (IContainerRegistry containerRegistry )
{ }
Ajout du module
Le projet principal (Shell) doit avoir une référence vers les modules.
App.xaml.cs
protected override void ConfigureModuleCatalog (IModuleCatalog moduleCatalog )
{
moduleCatalog.AddModule (typeof (TestModule ));
Type testModuleType = typeof (TestModule);
moduleCatalog.AddModule (new ModuleInfo ()
{
ModuleName = testModuleType .Name ,
ModuleType = testModuleType .AssemblyQualifiedName ,
InitializationMode = InitializationMode .WhenAvailable
});
Module - OLD
Package regroupant toutes les fonctionnalités d'une partie de l'application. Un module correspond à une fonctionnalité majeure de l'application.
Les modules sont dans des projets séparés de type Prism Module ou WPF User Control Library.
Modules/Test/TestModule.cs
[Module (ModuleName = "Test" , OnDemand = true)]
[ModuleDependency ("" )]
public class TestModule : IModule
{
public TestModule (IUnityContainer container , IRegionManager regionManager )
{
_ container = container;
_ regionManager = regionManager;
}
public void Initialize ()
{
_ container.RegisterTypeForNavigation <ViewA >();
_ regionManager.RegisterViewWithRegion ("ContentRegion" , typeof (ViewA ));
Chargement depuis le code
Le projet principal (Shell) doit avoir une référence vers les modules.
Bootstrapper.cs
protected override void ConfigureModuleCatalog ()
{
var moduleCatalog = (ModuleCatalog)ModuleCatalog;
moduleCatalog.AddModule (typeof (TestModule ));
Type testModuleType = typeof (TestModule);
ModuleCatalog.AddModule (new ModuleInfo ()
{
ModuleName = testModuleType .Name ,
ModuleType = testModuleType .AssemblyQualifiedName ,
InitializationMode = InitializationMode .WhenAvailable
});
Chargement depuis un chemin
Un dossier Modules doit être présent au même niveau que MyApplication.exe . Ce dossier doit contenir les dll des modules (TestModule.dll ).
Bootstrapper.cs
protected override IModuleCatalog CreateModuleCatalog ()
{
return new DirectoryModuleCatalog ()
{
ModulePath = @".\Modules"
};
Chargement depuis un fichier XAML
Ajouter au projet principal un dictionnaire de ressources.
Passer la Build Action à Resource
Les dll des modules doivent être présents au même niveau que MyApplication.exe .
XamlCatalog.xaml
<Modularity:ModuleCatalog xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Modularity ="clr-namespace:Prism.Modularity;assembly=Prism.Wpf" >
<Modularity:ModuleInfo Ref ="file://TestModule.dll"
ModuleName ="Test"
ModuleType ="TestModule.TestModuleModule, TestModule, Version=1.0.0.0"
InitializationMode ="WhenAvailable" />
</Modularity:ModuleCatalog >
Bootstrapper.cs
protected override IModuleCatalog CreateModuleCatalog ()
{
return Prism.Modularity.ModuleCatalog.CreateFromXaml (new Uri ("/MyApplication;component/XamlCatalog.xaml" , UriKind .Relative ));
}
Chargement depuis un fichier App.config
Ajouter au projet principal un Application Configuration File .
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration >
<configSections >
<section name ="modules" type ="Prism.Modularity.ModulesConfigurationSection, Prism.Wpf" />
</configSections >
<modules >
<module assemblyFile ="Modules/ModuleTest.dll"
moduleType ="ModuleTest.ModuleTestModule, ModuleTest, Version=1.0.0.0"
moduleName ="ModuleTest"
startupLoaded ="true" />
</modules >
</configuration >
Bootstrapper.cs
protected override IModuleCatalog CreateModuleCatalog ()
{
return new ConfigurationModuleCatalog ();
}
Chargement avec MEF
Ajouter l'assembly System.ComponentModel.Composition au projet ModuleTest
Prism.MefExtension ?
View
Pour créer une nouvelle vue, ajouter un UserControl dans un Module .
TestModule.cs
public class TestModule : IModule
{
public void Initialize ()
{
_ container.RegisterTypeForNavigation <ViewA >();
_ regionManager.RegisterViewWithRegion ("ContentRegion" , typeof (ViewA ));
var viewA = _ container.Resolve <ViewA >();
IRegion region = _ regionManager.Regions["ContentRegion" ];
region.Add (viewA );
region.Deactivate (viewA );
var viewB = _ container.Resolve <ViewB >();
region.Add (viewB );
ViewModel
BindableBase
Permet aux vue-modèles d'appeler RaisePropertyChanged .
class ViewAViewModel : BindableBase
{
private string myProperty ;
public string MyProperty
{
get => _ myProperty;
set => SetProperty (ref myProperty , value );
set {
myProperty = value ;
RaisePropertyChanged (nameof (MyProperty ));
}
Associe à ViewA le VM ViewAViewModel .
Modules/A/Views/ViewA.xaml
<UserControl x:Class ="A.Views.ViewA"
prism:ViewModelLocator.AutoWireViewModel ="True"
d:DataContext ="{d:DesignInstance local:ViewAViewModel}" >
MyView.xaml
<Button Command ="{Binding AddCmd}" > Add</Button >
<Button Command ="{Binding AddCmd2}" CommandParameter ="10" > Add</Button >
MyViewModel.cs
public ICommand AddCmd { get ; set ; }
AddCmd = new DelegateCommand (Add , CanAdd );
private DelegateCommand addCmd ;
public DelegateCommand AddCmd => addCmd ?? (addCmd = new DelegateCommand (Add , CanAdd ));
private void Add () {}
private bool CanAdd () { return true ; }
private DelegateCommand <int ?> addCmd2 ;
public DelegateCommand<int ?> AddCmd2 => addCmd2 ?? (addCmd2 = new DelegateCommand <int ?>(Add2 , CanAdd2 ));
private void Add2 (int ? parameter ) {}
private bool CanAdd2 (int ? parameter ) { return true ; }
AddCmd = new DelegateCommand (AddAsync );
async void AddAsync ()
{
await SomeAsyncMethod ();
}
AddCmd.RaiseCanExecuteChanged ();
CompositeCommand
Une commande qui regroupe plusieurs autres commandes.
public static class GlobalCommands
{
public static CompositeCommand SaveAllCommand = new CompositeCommand ();
}
GlobalCommands.SaveAllCommand.RegisterCommand (MyCommand );
<UserControl xmlns:myns ="clr-namespace:MyNamespace;assembly=MyAssembly" >
<Button Command ="{x:Static myns:GlobalCommands.SaveAllCommand}" > Save All</Button >
Système de distribution de message (event bus) sans couplage entre le destinataire et l'expéditeur.
un objet envoie un message, sans savoir qui le recevra
un ou des objets s'inscrivent pour recevoir ces messages, sans savoir qui les envoie
Ce mécanisme permet:
d'appeler une méthode de la vue depuis le Vue-Modèle.
de communiquer entre Vue-Modèle.
Modules/A/ViewModels/View1ViewModel.cs
private readonly IEventAggregator eventAggregator ;
public View1ViewModel (IEventAggregator eventAggregator )
{
this .eventAggregator = eventAggregator;
}
private void MyAction ()
{
eventAggregator.GetEvent <PubSubEvent <string >>().Publish ("MyValue" );
}
Modules/B/ViewModels/View2ViewModel.cs
private readonly IEventAggregator eventAggregator ;
public View2ViewModel (IEventAggregator eventAggregator )
{
this .eventAggregator = eventAggregator;
var myEvent = this .eventAggregator.GetEvent <PubSubEvent <string >>();
myEvent.Subscribe (MessageReceived );
myEvent.Subscribe (MessageReceived , ThreadOption .UIThread );
myEvent.Subscribe (MessageReceived , ThreadOption .PublisherThread , false ,
message => message .StartsWith ("x" ));
myEvent.Unsubscribe (MessageReceived );
}
private void MessageReceived (string message )
{
}
Shared Services
Permet de faire de l'injection de dépendance (implémentation de l'inversion de contrôle)
Common/IDataService.cs
public interface IDataService
{
IList <string > GetData ();
}
Modules/Services/DataService.cs
public class DataService : IDataService
{
public IList <string > GetData ()
{
return new List <string > { "un" , "deux" , "trois" };
}
}
Modules/Services/ServicesModule.cs
public class ServicesModule : IModule
{
public void RegisterTypes (IContainerRegistry containerRegistry )
{
containerRegistry.Register <IDataService , DataService >();
}
Modules/A/ViewModels/MyViewModel.cs
private readonly IDataService dataService ;
public MyViewModel (IDataService dataService )
{
this .dataService = dataService;
}
Permet aux vues de partager des données (context) avec ses vues filles.
<UserControl prism:RegionManager.RegionContext ="{Binding SelectedElement.Id}" >
private void GetRegionContext ()
{
var id = (int )RegionContext.GetObservableContext (this ).Value ;
}
ObservableObject <object > viewRegionContext = RegionContext.GetObservableContext (this );
viewRegionContext.PropertyChanged += this .ViewRegionContext_OnPropertyChangedEvent;
private void ViewRegionContext_OnPropertyChangedEvent (object sender , PropertyChangedEventArgs args )
{
if (args.PropertyName == "Value" )
{
var context = (ObservableObject<object >)sender;
int newValue = (int )context.Value;
}
}