« Prism 6 » : différence entre les versions
(5 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 385 : | Ligne 385 : | ||
class ViewAViewModel : BindableBase | class ViewAViewModel : BindableBase | ||
{ | { | ||
private string | private string myProperty; | ||
public string MyProperty | public string MyProperty | ||
{ | { | ||
get => _myProperty; | get => _myProperty; | ||
set => SetProperty(ref | set => SetProperty(ref myProperty, value); | ||
set { | set { | ||
myProperty = value; | |||
RaisePropertyChanged( | RaisePropertyChanged(nameof(MyProperty)); | ||
} | } | ||
</kode> | </kode> | ||
Ligne 400 : | Ligne 400 : | ||
<filebox fn='Modules/A/Views/ViewA.xaml'> | <filebox fn='Modules/A/Views/ViewA.xaml'> | ||
<UserControl x:Class="A.Views.ViewA" | <UserControl x:Class="A.Views.ViewA" | ||
prism:ViewModelLocator.AutoWireViewModel="True"> | prism:ViewModelLocator.AutoWireViewModel="True" | ||
d:DataContext="{d:DesignInstance local:ViewAViewModel}"> <!-- auto-completion during design --> | |||
</filebox> | </filebox> | ||
= DelegateCommand = | = [https://prismlibrary.com/docs/commanding.html DelegateCommand] = | ||
<filebox fn='MyView.xaml'> | <filebox fn='MyView.xaml'> | ||
<Button Command="{Binding AddCmd}">Add</Button> | <Button Command="{Binding AddCmd}">Add</Button> | ||
Ligne 410 : | Ligne 411 : | ||
<filebox fn='MyViewModel.cs'> | <filebox fn='MyViewModel.cs'> | ||
private DelegateCommand | public ICommand AddCmd { get; set; } | ||
public DelegateCommand AddCmd => | // in the ctor | ||
AddCmd = new DelegateCommand(Add, CanAdd); | |||
// other solution | |||
private DelegateCommand addCmd; | |||
public DelegateCommand AddCmd => addCmd ?? (addCmd = new DelegateCommand(Add, CanAdd)); | |||
private void Add() {} | private void Add() {} | ||
Ligne 419 : | Ligne 424 : | ||
// Command avec paramètres | // Command avec paramètres | ||
// le parametre doit être un type nullable | // le parametre doit être un type nullable | ||
private DelegateCommand<int?> | private DelegateCommand<int?> addCmd2; | ||
public DelegateCommand<int?> AddCmd2 => | public DelegateCommand<int?> AddCmd2 => addCmd2 ?? (addCmd2 = new DelegateCommand<int?>(Add2, CanAdd2)); | ||
private void Add2(int? parameter) {} | private void Add2(int? parameter) {} | ||
private bool CanAdd2(int? parameter) { return true; } | private bool CanAdd2(int? parameter) { return true; } | ||
// Task-Based DelegateCommand | |||
AddCmd = new DelegateCommand(AddAsync); | |||
async void AddAsync() | |||
{ | |||
await SomeAsyncMethod(); | |||
} | |||
// force la réévaluation de CanExecute | // force la réévaluation de CanExecute |
Dernière version du 7 août 2020 à 14:42
Introduction
Prism est un framework qui permet de développer des applications composites.
- SoC - Separation of Concerns: principe de conception visant à séparer un programme informatique en blocs, chaque bloc correspondant à une fonctionnalité.
- Loose coupling: les blocs du programme n'ont pas connaissance des autres. (Exemple Dependency injection)
Design de l'application
- 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
- Prism.Wpf Dependencies: Prism.Core CommonServiceLocator
Prism Template Pack
Ajoute des templates Prism dans les nouveaux projets Visual Studio Visual C# → Prism.
À la création d'un BlankApp, choix du conteneur IoC:
- DryIoc
- Autofac
- Unity
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 |
// classe de type PrismApplication qui hérite de PrismApplicationBase 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>(); // return Container.Resolve<MainWindow>(); } protected override void InitializeShell() { base.InitializeShell(); App.Current.MainWindow = (Window)Shell; App.Current.MainWindow.Show(); // Application.Current.MainWindow.Show(); } protected override IModuleCatalog CreateModuleCatalog() { var catalog = new ModuleCatalog(); catalog.AddModule(typeof(ModuleAModule)); return catalog; } /*protected override void ConfigureModuleCatalog() { var moduleCatalog = (ModuleCatalog)ModuleCatalog; //moduleCatalog.AddModule(typeof(YOUR_MODULE)); }*/ 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)); // autre méthode d'ajout 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 |
// Dans les cas où le module n'est pas chargé via le cas // il peut définir lui même son ModuleName, son InitializationMode (par défaut WhenAvailable) et ses dépendances [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)); // autre méthode d'ajout 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() { // enregistrer les types de vue _container.RegisterTypeForNavigation<ViewA>(); // composition des vues // View Discovery _regionManager.RegisterViewWithRegion("ContentRegion", typeof(ViewA)); // View Injection var viewA = _container.Resolve<ViewA>(); IRegion region = _regionManager.Regions["ContentRegion"]; region.Add(viewA); // remplacer une vue par une autre 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)); } |
ViewModelLocator
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}"> <!-- auto-completion during design --> |
DelegateCommand
MyView.xaml |
<Button Command="{Binding AddCmd}">Add</Button> <Button Command="{Binding AddCmd2}" CommandParameter="10">Add</Button> |
MyViewModel.cs |
public ICommand AddCmd { get; set; } // in the ctor AddCmd = new DelegateCommand(Add, CanAdd); // other solution private DelegateCommand addCmd; public DelegateCommand AddCmd => addCmd ?? (addCmd = new DelegateCommand(Add, CanAdd)); private void Add() {} private bool CanAdd() { return true; } // Command avec paramètres // le parametre doit être un type nullable 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; } // Task-Based DelegateCommand AddCmd = new DelegateCommand(AddAsync); async void AddAsync() { await SomeAsyncMethod(); } // force la réévaluation de CanExecute AddCmd.RaiseCanExecuteChanged(); |
CompositeCommand
Une commande qui regroupe plusieurs autres commandes.
public static class GlobalCommands { public static CompositeCommand SaveAllCommand = new CompositeCommand(); } // ajout de MyCommand dans la CompositeCommand SaveAllCommand GlobalCommands.SaveAllCommand.RegisterCommand(MyCommand); |
<UserControl xmlns:myns="clr-namespace:MyNamespace;assembly=MyAssembly"> <Button Command="{x:Static myns:GlobalCommands.SaveAllCommand}">Save All</Button> |
Event Aggregator
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() { // publie l'évenement TestEvent avec "MyValue" comme payload 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); // recevoir l'évenement sur le thread UI pour pouvoir modifier l'UI myEvent.Subscribe(MessageReceived, ThreadOption.UIThread); // filter myEvent.Subscribe(MessageReceived, ThreadOption.PublisherThread, false, message => message.StartsWith("x")); // se désincrire myEvent.Unsubscribe(MessageReceived); } private void MessageReceived(string message) { // reception des évenements TestEvent et de leur payload } |
Permet de faire de l'injection de dépendance (implémentation de l'inversion de contrôle)
Common/IDataService.cs |
// interface commune public interface IDataService { IList<string> GetData(); } |
Modules/Services/DataService.cs |
// une implémentation du service 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) { // enregistrement de DataService comme implémentation de IDataService containerRegistry.Register<IDataService, DataService>(); } |
Modules/A/ViewModels/MyViewModel.cs |
// injection de dépendance private readonly IDataService dataService; public MyViewModel(IDataService dataService) { this.dataService = dataService; } |
Region Context
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; } // suivre les changements dans le context 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; } } |