At BASTA Austria (http://www.basta-austria.at) I did a workshop about WPF and Prism. This is my slide deck. It summarizes the most important take-aways from the workshop. Additionally it contains sample code snippets.
10. Setup Dependency Injection (MEF)
• Optional
– Override CreateContainer and ConfigureContainer
– Make sure to call base class' implementation to get standard
services
protected override void ConfigureContainer()
{
base.ConfigureContainer();
// Publish container using MEF
this.Container.ComposeExportedValue<CompositionContainer>(this.Container);
}
• Override ConfigureAggregateCatalog
– Example:
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(Shell).Assembly));
}
11. Create Shell (MEF)
• Create and initialize the shell
protected override DependencyObject CreateShell()
{
return this.Container.GetExportedValue<Shell>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow = this.Shell as Window;
Application.Current.MainWindow.Show();
}
12. Sample: Bootstrapper
using Microsoft.Practices.Prism.MefExtensions;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;
using System.Reflection;
using System.Windows;
namespace Prism41Sample.UI
{
public class Bootstrapper : MefBootstrapper
{
/// <summary>
/// Sets up MEF catalogs
/// </summary>
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
var executingAssembly = Assembly.GetExecutingAssembly();
// Use current assembly when looking for MEF exports
this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(executingAssembly));
// Look into "Modules" directory to dynamically load additional exports
this.AggregateCatalog.Catalogs.Add(new DirectoryCatalog(
Path.Combine(Path.GetDirectoryName(executingAssembly.Location),
"RuntimeModules")));
}
13. Sample: Bootstrapper
/// <summary>
/// Exports MEF container in service locator
/// </summary>
protected override CompositionContainer CreateContainer()
{
var container = base.CreateContainer();
container.ComposeExportedValue(container);
return container;
}
protected override DependencyObject CreateShell()
{
// Create a new instance of the applications "MainWindow"
return this.Container.GetExportedValue<Window>("MainWindow");
}
/// <summary>
/// Makes shell the main window of the application.
/// </summary>
protected override void InitializeShell()
{
base.InitializeShell();
Application.Current.MainWindow = this.Shell as Window;
Application.Current.MainWindow.Show();
}
}
}
14. Sample: Bootstrapper
using Prism41Sample.UI.ViewModel;
using System.ComponentModel.Composition;
using System.Windows;
namespace Prism41Sample.UI.View
{
[Export("MainWindow", typeof(Window))]
public partial class MainWindow : Window
{
[ImportingConstructor]
public MainWindow(MainWindowViewModel viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
}
}
16. Module Creation (1/2)
• Implement IModule
• Register named modules in XAML, app.config, or
code (see above)
• Declarative metadata attributes for modules
[ModuleExport(typeof(DataManagementModule), InitializationMode = InitializationMode.WhenAvailable)]
public class DataManagementModule : IModule
{
public void Initialize()
{
[…]
}
[…]
}
• Dependency management (incl. cycle detection)
[ModuleExport(typeof(ModuleA), DependsOnModuleNames = new string[] { "ModuleD" })]
public class ModuleA : IModule
{
[…]
}
17. Module Creation (2/2)
• Load modules from directory with
DirectoryModuleCatalog
protected override IModuleCatalog CreateModuleCatalog()
{
return new DirectoryModuleCatalog() {ModulePath = @".Modules"};
}
• Load modules from directory with MEF
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
DirectoryCatalog catalog = new DirectoryCatalog("DirectoryModules");
this.AggregateCatalog.Catalogs.Add(catalog);
}
18. Sample: Repository Module
using Microsoft.Practices.Prism.MefExtensions.Modularity;
using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.ServiceLocation;
using Prism41Sample.Infrastructure;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
namespace Prism41Sample.Repository
{
[ModuleExport("RepositoryModule", typeof(RepositoryModule))]
public class RepositoryModule : IModule
{
[Import]
private IServiceLocator serviceLocator = null;
public void Initialize()
{
// Publish repository service using MEF
var container = this.serviceLocator.GetInstance<CompositionContainer>();
container.ComposeExportedValue<RepositoryBase>(new CustomerRepository());
}
}
}
21. Regions and Views
Region Adapters
• ContentControlRegionAdapter
• SelectorRegionAdapter
– E.g. TabControl
• ItemsControlRegionAdapter
– E.g. ListBox
22. Defining Regions
• In XAML
<UserControl […]
xmlns:prism="http://www.codeplex.com/prism">
[…]
<Controls:AnimatedTabControl
x:Name="PositionBuySellTab"
prism:RegionManager.RegionName="{x:Static inf:RegionNames.MainRegion}"/>
[…]
</UserControl>
• In Code
IRegionManager regionManager = ServiceLocator.Current.GetInstance<IRegionManager>();
RegionManager.SetRegionManager(this.ActionContent, regionManager);
RegionManager.SetRegionName(this.ActionContent, "ActionRegion");
23. Defining Regions
• In XAML
<UserControl […]
xmlns:prism="http://www.codeplex.com/prism">
[…]
<Controls:AnimatedTabControl
x:Name="PositionBuySellTab"
prism:RegionManager.RegionName="{x:Static inf:RegionNames.MainRegion}"/>
[…]
</UserControl>
• In Code
IRegionManager regionManager = ServiceLocator.Current.GetInstance<IRegionManager>();
RegionManager.SetRegionManager(this.ActionContent, regionManager);
RegionManager.SetRegionName(this.ActionContent, "ActionRegion");
24. Loading Content Into Regions
// View discovery using composition container
this.regionManager.RegisterViewWithRegion("MainRegion", typeof(EmployeeView));
// …or delegate
this.regionManager.RegisterViewWithRegion("MainRegion", () => this.container.Resolve<EmployeeView>());
// Add view in code
IRegion region = regionManager.Regions["MainRegion"];
var ordersView = container.Resolve<OrdersView>();
region.Add(ordersView, "OrdersView");
25. Tips For Views and Regions
• Ordering of views in a region
[Export]
[ViewSortHint("01")]
public partial class EmailNavigationItemView
[Export]
[ViewSortHint("02")]
public partial class CalendarNavigationItemView
• Sharing data between regions
– Attached property RegionContext
<TabControl AutomationProperties.AutomationId="DetailsTabControl"
cal:RegionManager.RegionName="{x:Static local:RegionNames.TabRegion}"
cal:RegionManager.RegionContext="{Binding Path=SelectedEmployee.EmployeeId}"
...>
// Read value of RegionContext
private void GetRegionContext()
{
this.Model.EmployeeId = (int)RegionContext.GetObservableContext(this).Value;
}
31. Sample: Event Aggregator
using Microsoft.Practices.Prism.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Prism41Sample.CustomerMaintenance
{
public class ActivateCustomerEvent : CompositePresentationEvent<int>
{
}
}
33. Navigation
• Basic navigation
IRegion mainRegion = ...;
mainRegion.RequestNavigate(new Uri("InboxView", UriKind.Relative));
// or
IRegionManager regionManager = ...;
regionManager.RequestNavigate("MainRegion",
new Uri("InboxView", UriKind.Relative));
• Prerequisite (for MEF): Export of the view
[Export("InboxView")]
public partial class InboxView : UserControl
• Parameters for navigation
Employee employee = Employees.CurrentItem as Employee;
if (employee != null)
{
UriQuery query = new UriQuery();
query.Add("ID", employee.Id);
_regionManager.RequestNavigate(RegionNames.TabRegion,
new Uri("EmployeeDetailsView" + query.ToString(), UriKind.Relative));
}
34. Sample: Event Aggregator
[Export]
public class MainWindowViewModel : NotificationObject
{
private IEventAggregator eventAggregator = null;
[Import]
private IRegionManager regionManager = null;
[ImportingConstructor]
public MainWindowViewModel(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
this.eventAggregator.GetEvent<ActivateCustomerEvent>().Subscribe(this.OnActivateCustomer);
}
#region Main window controller
// You should implement the controller in a separate class if navigation is more complex.
private void OnActivateCustomer(int customerNumber)
{
var q = new UriQuery();
q.Add("CustomerNumber", customerNumber.ToString());
this.regionManager.RequestNavigate(
RegionAndModuleStringConstants.DetailRegionName,
new Uri("CustomerDetail" + q.ToString(), UriKind.Relative));
}
#endregion
36. Command Objects
• Implement ICommand yourself
• Use Prism's DelegateCommand<T>
public class DelegateCommand<T> : DelegateCommandBase
{
public DelegateCommand(
Action<T> executeMethod,
Func<T,bool> canExecuteMethod )
: base((o) => executeMethod((T)o), (o) => canExecuteMethod((T)o))
{
...
}
}
37. Sample: Delegate Command
public class MainWindowViewModel : NotificationObject
{
[ImportingConstructor]
public MainWindowViewModel(IEventAggregator eventAggregator)
{
this.CloseAllTabsCommand = new DelegateCommand(this.OnCloseAllTabs);
…
}
public ICommand CloseAllTabsCommand { get; private set; }
private void OnCloseAllTabs()
{
var detailRegion = this.regionManager.Regions[
RegionAndModuleStringConstants.DetailRegionName];
foreach (var view in detailRegion.Views)
{
// if the view implements IDisposable, call Dispose on it
var disposableView = view as IDisposable;
if (disposableView != null)
{
disposableView.Dispose();
}
detailRegion.Remove(view);
}
}
…
}
38. Command Objects With
Interaction Triggers
• Use commands with controls that does not support commands out of
the box
• Defined in Expression Blend
– Tip: Reference assemblies come with Prism
• Example:
<UserControl […]
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">
[…]
<TextBlock Margin="5" Text="Hello World!">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding Path=MyCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
[…]
</UserControl>
• Use CallMethodAction to call methods without ICommand
– No support for parameters
40. Composite Commands (2/2)
• Implement IActiveAware on View or ViewModel class
• Implement IActiveAware on your commands
– DelegateCommand and CompositeCommand already implement
IActiveAware
• When View/ViewModel becomes inactive, deactivate child commands
• Specify true in monitorCommandActivity of constructor of
CompositeCommand
41. Interaction Services (WPF)
• ViewModel communicates with View using an
interaction service
var result =
interactionService.ShowMessageBox(
"Are you sure you want to cancel this operation?",
"Confirm",
MessageBoxButton.OK );
if (result == MessageBoxResult.Yes)
{
CancelRequest();
}
• Note: Silverlight not covered here
42. Sample: Composite Commands
namespace Prism41Sample.UI.ViewModel
{
[Export(typeof(IGlobalCommands))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class GlobalCommands : IGlobalCommands
{
public GlobalCommands()
{
this.SaveAllCommand = new CompositeCommand();
this.PrintActiveItem = new CompositeCommand(true);
}
public CompositeCommand SaveAllCommand { get; private set; }
public CompositeCommand PrintActiveItem { get; private set; }
43. Sample: Composite Commands
#region IGlobalCommands implementation
public void RegisterSaveAllCommand(ICommand subCommand)
{
this.SaveAllCommand.RegisterCommand(subCommand);
}
public void RegisterPrintActiveItemCommand(ICommand subCommand)
{
this.PrintActiveItem.RegisterCommand(subCommand);
}
public void UnregisterSaveAllCommand(ICommand subCommand)
{
this.SaveAllCommand.UnregisterCommand(subCommand);
}
public void UnregisterPrintActiveItemCommand(
ICommand subCommand)
{
this.PrintActiveItem.UnregisterCommand(subCommand);
}
#endregion
}
}
49. Q&A
Thank you for your attention!
Rainer Stropek
software architects
rainer@timecockpit.com
http://www.timecockpit.com
50. is the leading time tracking solution for knowledge
workers. Graphical time tracking calendar, automatic tracking of
your work using signal trackers, high level of extensibility and
customizability, full support to work offline, and SaaS
deployment model make it the optimal choice especially in the
IT consulting business.
Try for free and without any risk. You can get your
trial account at http://www.timecockpit.com. After the trial
period you can use for only 0,20€ per user and
month without a minimal subscription time and without a
minimal number of users.
51. ist die führende Projektzeiterfassung für
Knowledge Worker. Grafischer Zeitbuchungskalender,
automatische Tätigkeitsaufzeichnung über Signal Tracker,
umfassende Erweiterbarkeit und Anpassbarkeit, volle
Offlinefähigkeit und einfachste Verwendung durch SaaS machen
es zur Optimalen Lösung auch speziell im IT-Umfeld.
Probieren Sie kostenlos und ohne Risko einfach
aus. Einen Testzugang erhalten Sie unter http://www.timecockpit.com.
Danach nutzen Sie um nur 0,20€ pro Benutzer und
Tag ohne Mindestdauer und ohne Mindestbenutzeranzahl.