Schrittweises Entwickeln einer einfachen modularen Anwendungen mit dem “Composite Application Guidance for WPF” Framework Einführung In diesem Dokument soll das schrittweise Erstellen einer modularen Anwendung mit dem “Composite Application Guidance for WPF” Framework beschrieben werden. Dieses Framework wurde unter dem Codenamen PRISM entwickelt. Dieser Codename wird der Einfachheit halber im Dokument verwendet werden. Die Hauptseite des PRISM Communityprojekte ist http://www.codeplex.com/CompositeWPF. Das PRISM Framework ist unter Microsoft public license veröffentlicht. Aus meiner Sicht wichtige Aspekte dieser Lizenzvereinbarung: das PRISM Framework kann kostenfrei genutzt werden und der unter Verwendung von PRISM erstellte Code muss nicht offengelegt werden. Supportanfragen und Featurerequest kann man nur auf der Codeplex Communityseite stellen, und können dort von der Community beantwortet werden. Die Motivation für dieses Dokument ist, dass viele Geschäftsanwendungen Anforderungen haben, die mit dem PRISM Framework sehr gut abgedeckt werden können. Das Typische für viele Geschäftsanwendungen ist, dass diese Anwendungen zur Abwicklung und Durchführung von bestimmten geschäftlichen Abläufen entwickelt wurden. Diese Anwendungen lassen sich häufig in verschiedene fachliche Module, wie z.B. „Human Resources“, „Custom Relationship Management“, u.v.a.m. unterteilen. Die Anforderungen an Modularität kann aus verschiedenen Gründen für den Entwurf einer Anwendung gewünscht sein: Modularisierte Entwicklung für/durch fachliche Module Modularisierte Softwareverteilung (z.B. der „HR Arbeitsplatz“, der „CRM Arbeitsplatz“, ... ) Anwendungsmodule für den Zugriff auf verschiedene (Web)-Services bzw. BackendModule Modularisierte Updates U.v.a.m. Diese Anforderungen für modularisierte Anwendungen unterstützt PRISM als integriertes Basiskonzept und es soll in diesem Dokument gezeigt werden, wie einfach die Verwendung von PRISM in Hinblick auf die Umsetzung dieser Anforderungen ist. Die nächsten Seiten beschrieben also die Erstellung einer modularen Rahmenanwendung mit PRISM, um die Grundfunktionen zu demonstrieren und die ersten Schritte zu einer generischen modularen Anwendung zu demonstrieren. Diese Anleitung soll nicht die Dokumentation zum PRISM Framework ersetzen. Es empfiehlt sich einen kurzen Blick auf die PRISM Dokumenten zu werfen, die umfassend sehr viele wichtige Aspekte von PRSIM beschreibt. Dieses Dokument soll nur einfach nachzuvollziehender Wegweiser für die wichtigsten Arbeitsschritte zum Anlegen einer einfachen modularen Anwendung sein. Zu diesem Guide gibt es ein fertiges Demoprojekt, dass die komplette Lösung enthält. Diese Anleitung wendet sich an Entwickler und technisch versierte Leser. Technische Voraussetzungen .NET Framework 3.5 (SP1) Visual Studio (Express, Standard, …) – (Anmerkung – bei Visual Studio Express gibt es den Projekttyp “WPF User Control Library” nicht; aber eine „Class library“ mit den entsprechenden WPF Referenzen kann natürlich einfach erzeugt werden.) Umsetzungsschritte Anwendungsshell 1. Bereitstellen des PRISM Frameworks und der Enterprise Library 4.0 (ETL optional) Wenn man PRISM zur lokalen Entwicklung nutzen will, sollte man zuerst die Enterprise Library 4.0 installieren. Zwar ist das PRISM Framework auch ohne den ETL Download nutzbar, da aber PRISM auf Funktionen der ETL 4.0 aufsetzt und wenn man Dokumentation und ggf. Sourcecode nachschlagen möchte, ist die Installation der ETL empfehlenswert. Die Enterprise Library bekommt man von http://www.codeplex.com/entlib/Release/ProjectReleases.aspx?ReleaseId=13498. Danach muss man das PRISM Framework herunterladen http://www.microsoft.com/downloads/details.aspx?FamilyId=6DD3D0C1-D5B4-453BB827-98E162E1BD8D&displaylang=en. Als Ergebnis des Downloads hat man ein Exe-File auf der lokalen Festplatte, das man durch Ausführen in ein beliebiges Verzeichnis extrahieren kann. Hierzu sollte man ein Verzeichnis wählen, in dem man auch bei der täglichen Arbeit Schreiben bzw. Verändern darf, zum Beispiel um die Quickstarts auszuprobieren .... Im Verzeichnis „<Extract Directory>Source\CAL“ befindet sich der Sourcecode des PRISM Framework mit Unittests. Um das Framework zu kompilieren, muss man das File „CompositeApplicationLibrary.sln“ öffnen. Nach dem Build findet man im Verzeichnis „<Extract Directory>Source\CAL\Composite.UnityExtensions\bin\Debug“ die PRISM Assemblies, die man zur Benutzung von PRISM benötigt. Die markierten Assemblies und Files: „Microsoft.Practices.Composite.*“, „Microsoft.Practices.Composite.UnityExtensions.*“ und „Microsoft.Practices.Composite.Wpf.*“ sollte man in ein separates Verzeichnis kopieren. Dann kann man diese notwendigen PRISM Assemblies binär in der eigenen Visual Studio Lösung referenzieren. Eine Integration von PRISM als Sourcecode in eine eigene Visual Studio Lösung ist auch möglich, aber den kompiliert man für jeden Test etc. Immer das PRISM Framework mit. Abbildung 1: Kopieren der notwendigen PRISM Assemblies 2. Anlegen der Solution In Visual Studio eine leere („blank“) Solution erstellen. File->New->Project hier dann unter „Other Project Types“ den Eintrag „Visual Studio Solutions“ auswählen im großen Fenster dann die „Blank Solution“ anklicken und den Namen der Solution eintragen. In unserem Beispiel ist der Name der Solution PrismHowTo. Danach müssen die eigentlichen Projekte angelegt werden. Dazu braucht man mindestens 3 Projekte: 1. Die Anwendungsshell = das WPF Projekt aus dem das Anwendungsfenster gestartet wird 2. Das Module 1 Projekt, um die Modularität zu demonstrieren 3. Das Module 2 Projekt, um die Modularität zu demonstrieren Im Hauptfenster unserer Anwendungsshell werden dann die fachlichen Module ihr UI visualisieren. Abbildung 2: Anlegen der Solution 3. Anlegen des Projektes für die Anwendungsshell Das Hauptprojekt wird als WPF Application Projekt mit dem Namen „ApplicationShell“ angelegt. „File – Add New Project – Visual C# - Windows – WPF Application“. Abbildung 3: 2. Anlegen des Projektes für die Anwendungsshell 4. Hinzufügen der notwendigen Referenzen zu den Prism und Enterprise Library Assemblies. Die drei zu referenzierenden Assemblies von Prism sind: Microsoft.Practices.Composite, Microsoft.Practices.Composite.UnityExtensions und Microsoft.Practices.Composite,Wpf. Aus der Enterprise Lib 4.0 sind zwei Assemblies zu referenzieren: Microsoft.Practices.Unity und Microsoft.Practices.ObjectBuilder2. Dazu auf das Project ApplicationShell rechts klicken und im Contextmenu „Add Reference“ auswählen. Und dann über den Browse Button zu den Prism Libs navigieren und hinzufügen. Dann die Enterprise Lib Assemblies aus dem GAC referenzieren. Danach sollte das Projekt folgende Referenzen aufweisen. Abbildung 4: Referenzen zu Prism und Enterprise Lib Assemblies 5. Sinnvolle Klassenstruktur im ApplicationShell Projekt erstellen Um den Startvorgang der Anwendung und das Einstellen der Laufzeitumgebung nach den eigenen Bedürfnissen zu gestalten, fügt man eine Klasse Program.cs in das Projekt ein. In der Klasse Program wird es die Main Methode der Anwendung geben. Weiterhin benötigt man eine neue Klasse Bootstrapper.cs, welche ebenfalls hinzugefügt wird, für das Anpassen des PRISM Frameworks. Mit rechter Maustaste auf das Project ApplicationShell klicken und dann mit Add->Class die Program Klasse in Program.cs hinzufügen. Die Klasse Program bekommt vorerst nur eine Methode: [STAThread()] public static int Main(string[] args) { return 0; } Danach kam man in den Properties des Projektes das Startobjekt auf die Program Klasse einstellen. Rechtsklick auf das Project „ApplicationShell“ Properties anklicken. Unter Application dann die Klasse ApplicatioShell.Program als Startup Klasse auswählen. Abbildung 5: Setzen des Startup Objektes Auf die gleiche Art und Weise wie vorher jetzt die Klasse Bootsstrapper zum Projekt hinzufügen. Die Bootstrapper Klasse leitet von der Klasse UnityBootstrapper aus dem PRISM Framework ab. In dieser Klasse kann man die PRISM Funktionalität beeinflussen, um das Framework nach seinen Wünschen zu initialisieren. In der Bootstrapper Klasse soll das ShellWindow Klasse als Hautpfenster der Anwendung gesetzt werden. Weiterhin muss das IModuleEnumerator Interface auf die Klasse ConfigurationModuleEnumerator setzen. Diese Einstellung bewirkt, dass das App.Config File für das Laden der Module genutzt wird. Der Code sieht jetzt so aus: using System.Windows; using Microsoft.Practices.Composite.Modularity; using Microsoft.Practices.Composite.UnityExtensions; namespace ApplicationShell { public class Bootstrapper : UnityBootstrapper { // Fields protected ShellWindow mainWnd; // Methods protected override DependencyObject CreateShell() { this.mainWnd = new ShellWindow(); return this.mainWnd; } protected override IModuleEnumerator GetModuleEnumerator() { return new ConfigurationModuleEnumerator(); } // Properties public ShellWindow ApplicationShellWindow { get { return this.mainWnd; } } } } Die Klasse ShellWindow wird jetzt noch nicht erkannt, da wir diese erst im XAML File anlegen müssen. Die Program Klasse hat als wichtigste Methode die Main Methode. Hier wird der Anwendungsstart mit: Initialisierung der Fehlerbehandlung, Initialisierung der DefaultCulture, Initialisierung der Authentication Policy, Initialisierung des PRISM Framworks und dem Start des WPF Applicationobjektes durchgeführt. #define TRACE using System; using System.Diagnostics; using System.Globalization; namespace ApplicationShell { class Program { private static App applicationInstance; private static Bootstrapper bootStrapper; [STAThread()] public static int Main(string[] args) { int iRetVal = 0; try { // set the unhandled execption handler to find unhandled exceptions AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); // set the prefered principal policy for your up domain AppDomain.CurrentDomain.SetPrincipalPolicy(System.Security.Principal.Pri ncipalPolicy.WindowsPrincipal); // set the UI culture for your application System.Threading.Thread.CurrentThread.CurrentCulture = System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US"); applicationInstance = new App(); // in RELEASE mode - capture unhandled exceptions #if !DEBUG applicationInstance.DispatcherUnhandledException += new System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(applic ationInstance_DispatcherUnhandledException); #endif // initialize the PRISM framework bootStrapper = new Bootstrapper(); bootStrapper.Run(); // start the WPF application with the Shell window generated in the bootstrapper applicationInstance.Run(bootStrapper.ApplicationShellWindow); } catch (Exception ex) { Trace.WriteLine(ex.ToString()); iRetVal = -1; } return iRetVal; } static void applicationInstance_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) { Trace.WriteLine(e.Exception.ToString()); e.Handled = true; } static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { Trace.WriteLine(((Exception)e.ExceptionObject).ToString()); } } } 6. Visuelle Gestaltung des Anwendungshauptfensters In dem Anwendungshauptfenster müssen jetzt explizit die Bereiche festgelegt werden, in denen ein Modul visuelle Veränderungen vornehmen kann. Die typische visuelle Veränderung, die ein Modul im Anwendungsfenster vornimmt, ist das Hinzufügen und Entfernen von Controls. Zum Beispiel im Menu, Toolbar, Arbeitsbereich, ... . Das PRISM Framework stellt für die Darstellungsbereiche „Regions“ eine Verwaltungsklasse bereit, die den Zugriff der Module auf die Regionen erlaubt. Damit diese Verwaltungsklasse im Hauptfenster die Bereiche erkennt muss man im WPF XAML Code solche Vermerke eintragen: <StatusBar DockPanel.Dock="Bottom" prism:RegionManager.RegionName="MainStatusbar" Name="MainStatusbar" Height="30" VerticalAlignment="Bottom"> Anhand dieser Vermerke können bei der Initialisierung des PRISM Frameworks durch den Bootstrapper, im Hauptfenster die festgelegten Regions erkannt werden. Das Hauptfenster dieses Beispiels hat ein Menu, einen Toolbar, einen Statusbar und einen Arbeitsbereich. Im Arbeitsbereich sollen, die eigentliche „Dialoge“ zur Durchführung der fachlichen Funktionen eingeblendet werden. Zuerst wird die Datei Windows1.xaml im Solution Explorer in ShellWindow.xaml umbenannt. Nach eintragen der Regions für diese Anwendung sieht die Datei ShellWindow.xaml dann so aus: <Window x:Class="ApplicationShell.ShellWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:prism="http://www.codeplex.com/CompositeWPF" Title="ShellWindow" Height="768" Width="1024"> <DockPanel Height="Auto" Width="Auto" LastChildFill="true"> <Menu DockPanel.Dock="Top" Height="30" Width="Auto" Name="MainMenu" VerticalAlignment="Center" HorizontalContentAlignment="Center"> <MenuItem Header="File" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="1"> <MenuItem Header="Exit" Click="MenuItem_Click" ></MenuItem> </MenuItem> <MenuItem Header="Module functions" Width="Auto" prism:RegionManager.RegionName="ExtensionMenu" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="1"></MenuItem> <MenuItem Header="Help" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="1"></MenuItem> </Menu> <ToolBar DockPanel.Dock="Top" prism:RegionManager.RegionName="MainToolbar" Name="MainToolbar" Height="35" Width="Auto" VerticalAlignment="Top"> </ToolBar> <StatusBar DockPanel.Dock="Bottom" prism:RegionManager.RegionName="MainStatusbar" Name="MainStatusbar" Height="30" VerticalAlignment="Bottom"> </StatusBar> <ItemsControl Name="WorkArea" prism:RegionManager.RegionName="WorkArea" Background="LightGray"> </ItemsControl> </DockPanel> </Window> Um das Ganze jetzt vollständig zu machen müssen wir noch in der Datei ShellWindow.xaml.cs einen Handler für den Click Event im Menu auf den Eintrag „Exit“ hinzufügen. Am einfachsten geht dies, wenn man im Xaml Source (Shellwindow.xaml) mit der rechten Maustaste auf den String "MenuItem_Click" klickt und dann im Contextmenu „Navigate To Event Handler“ anwählt. Im C# File muss dann auch noch „Window1“ durch „ShellWindow“ ersetzt werden, so dass die Datei dann folgendermaßen aussieht: using System.Windows; namespace ApplicationShell { /// <summary> /// Interaction logic for ShellWindow.xaml /// </summary> public partial class ShellWindow : Window { public ShellWindow() { InitializeComponent(); } private void MenuItem_Click(object sender, RoutedEventArgs e) { Application.Current.Shutdown(); } } } 7. Konfiguration in der App.Config Jetzt müssen wir noch eine Applikations Konfigurationsdatei zum Projekt hinzufügen. Hierzu im Solution Explorer mit der rechten Maustaste auf ApplicationShell klicken und dann über Add->New Item, dann im Dialog „Application Configuration File“ auswählen und den Namen App.Config eingeben. Abbildung 6. Hinzufügen der App.config Datei In dieser App.Config können wir neben vielen anderen Einstellungen auch konfigurieren, welche Module zum Anwendungsstart von PRISM geladen werden sollen. Dazu benötigt die App.Config mindestens folgende Einträge: <?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="modules" type="Microsoft.Practices.Composite.Modularity.ModulesConfigurationSecti on, Microsoft.Practices.Composite"/> </configSections> <modules> </modules> </configuration> Die im Bootstrapper festgelegte ConfigurationModuleEnumerator Klasse liest diese Konfigurationseinträge aus. Anlegen eines Modules 8. Anlegen des Projektes für ein Modul Um ein fachliches Modul anzulegen, müssen wir in der Solution ein neues Projekt hinzufügen. Wir benötigen ein neues Projekt, da wir ja die Fachlichkeit im Modul und die Funktionen des Anwendungsrahmens explizit trennen wollen. „File – Add New Project – Windows – WPF User Control Library“ Abbildung 7: Hinzufügen des Projektes für ein Modul 9. Setzen der notwendigen Referenzen Durch den Projekt Typ „WPF User Control Library“ sind alle für WPF notwendigen Referenzen schon im neu erzeugten Projekt gesetzt. Um die für PRISM notwendigen Referenzen zu setzen, geht man genau wie im Abschnitt „4. Hinzufügen der notwendigen Referenzen zu den Prism und Enterprise Library Assemblies.“ beim Anlegen des ApplicationShell Projektes vor. 10. Anlegen der ModulInit Klasse Während des Ladevorgangs eines Moduls ruft das PRISM Framework in einem Modul eine bestimmte Initialisierungs-Methode einmalig auf. Das gibt dem Modul die Möglichkeit, notwendige Erst-Initialisierungsschritte auszuführen, z.B. das Anlegen von Menu- und Toolbareinträgen. Zu Erzeugung dieser Klasse fügt man zum Modul-Projekt eine Klasse „ModulInit“ hinzu. Diese Klasse muss das Interface IModule aus dem PRISM Framework implementieren und public sein, damit sie von Moduleloader aus dem Hauptassembly (= ApplicationShell) aufgerufen werden kann. Die Implementierung des Interfaces IModule benötigt die Implementierung einer Methode „public void Initialize()“, die während der Module Initialisierung Sequenz ausgeführt wird. 11. Der Constructor in der ModulInit Klasse Durch den verwendeten Unity Applicationblock kann man sich bei der Initialisierung eines Modules im Constructor vorhandene notwendige globale Funktionen/Variablen übergeben lassen. Ein typisches Beispiel für dieses Szenario ist es, sich den UnityContainer und den RegionManager in Constructor des Modules zuweisen zu lassen. Dieser Code hierzu sieht folgendermaßen aus: public class ModuleInit : IModule { private readonly IUnityContainer unityCnt; private readonly IRegionManager regionMgr; public ModuleInit(IUnityContainer container, IRegionManager regionManager) { unityCnt = container; regionMgr = regionManager; } … Wie schon erwähnt, wird beim Laden des Modules durch das Unity Framework hier die richtige Zuweisung von Variablen ausgeführt; - via Dependency Injection. Damit stehen im Module alle notwendigen Funktionen zur Verfügung, um z.B. die Regionen im Anwendungshauptfenster mit eigenen Controls zu benutzen. 12. ModulInitialisierung Man sollte sich die Logik, die während der Modulinitialisierung durchgeführt wird, genau überlegen. Da ein Modul nicht als alleiniges Modul in einem Hauptfenster ausgeführt wird, ist es empfehlenswert geteilte Darstellungsressourcen während der Initialisierung nicht für sich allein zu beanspruchen. Wenn ein anderes, uns nicht bekanntes Modul ebenso vorgeht, sind bestimmte UI Bereiche eventuell nicht sichtbar. Ein Beispiel wäre hier in unserem Sample die „WorkArea“, das ist ein Darstellungsbereich, in dem typischerweise nur das aktive Modul sichtbar ist und nicht mehrere Module gleichzeitig sichtbar sein sollten. Ein sinnvolles Vorgehen während der Modulinitialisierung ist es nur in zentralen Steuerungselementen, wie Toolbars und Menus, Einträge zu machen, die es dem Benutzer erlauben spezielle Modulfunktionen auf diesem Weg zu starten. Der Code eines sinnvollen ModulInit könnte also so aussehen: using using using using using using System.Diagnostics; System.Windows; System.Windows.Controls; Microsoft.Practices.Composite.Modularity; Microsoft.Practices.Composite.Regions; Microsoft.Practices.Unity; using SharedFuncs.Extensions; namespace ModuleCRM { public class ModuleInit : IModule { // central controls to trigger business functionality inside the moduel private MenuItem mniStartSearch; private Button btnStartSearch; // variables for usefull PRISM/UNITY framework classes private readonly IUnityContainer unityCnt; private readonly IRegionManager regionMgr; // the constructor which assigns the necessary framework classes public ModuleInit(IUnityContainer container, IRegionManager regionManager) { unityCnt = container; regionMgr = regionManager; } #region IModule Members // the module initialization sequence public void Initialize() { Trace.WriteLine("ModuleCRM.ModuleInit.Initialize() ..."); #region init ui // create a menu item to call the "Start search handler" IRegion r = regionMgr.Regions["ExtensionMenu"]; if (r != null) { mniStartSearch = new MenuItem() { Header = "Start search customer" }; mniStartSearch.Click += StartSearchEvtHdl; r.Add(mniStartSearch); } // create a toolbar button to call the "Start search handler" r = regionMgr.Regions["MainToolbar"]; if (r != null) { btnStartSearch = new Button() { Content = "Start search customer" }; btnStartSearch.Click += StartSearchEvtHdl; r.Add(btnStartSearch); } #endregion init ui } #endregion IModule Members // the "Start search handler" creates an search UI with module specific functionality // and shows it in the Workarea private void StartSearchEvtHdl(object sender, RoutedEventArgs e) { IRegion r = regionMgr.Regions["WorkArea"]; if (r != null) { r.RemoveAllOtherControlsFromRegion(); VwCrmSearch vw = new VwCrmSearch(); r.Add(vw); } } } } Die Modulinitialisierung ist ggf. auch ein geeigneter Zeitpunkt, um Berechtigungsüberprüfungen durchzuführen. Im Module selbst weiß man, welche Fachlichkeit geladen werden sollte und könnte hiererstmalig die Berechtigung überprüfen. Z.B. darf User XYX das Module HR benutzen? Bei den einzelnen fachlichen Methoden eines Modules müssen natürlich noch granularere Berechtigungsprüfungen stattfinden. 13. Module UI Es gibt verschiedene Weg aus einem Module in den Regionen des Hauptfensters sichtbar zu werden. Die einfachste Variante ist es, Basiscontrols aus WPF zu verwenden. Wenn man eine komplexere fachlichere Logik, zum Beispiel für eine Suchemaske mit Ergebnisanzeige, darstellen muss, empfiehlt es sich WPF User Controls zu verwenden. Ein WPF User Control kann in den Regionen des Hauptfensters dargestellt werden und bieten während der Anwendungsentwicklung WYSWIG Designer Unterstützung im Visual Studio. Abbildung 8: Hinzufügen eines WPF User Controls zum Modulprojekt Hier der Xaml Code des User Controls, in unserem Fall nur ein Button und ein Texteingabe feld: <UserControl x:Class="ModuleCRM.VwCrmSearch" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="768" Width="1024"> <Grid> <Button Height="22" HorizontalAlignment="Left" Margin="32,30,0,0" Name="btnStartSearch" VerticalAlignment="Top" Width="159" Click="btnStartSearch_Click">Start Search Customer</Button> <TextBox Height="20" Margin="228,30,238,0" Name="tbxSearchString" VerticalAlignment="Top" /> </Grid> </UserControl> Über den Rechtsklick auf „btnStart Search_Click“ und Navigate toHandler wir wiederum ein EventHandler für den Click Event erzeugt. In VwCrmSearch.xaml.cs: using System.Windows; using System.Windows.Controls; namespace ModuleCRM { /// <summary> /// Interaction logic for VwCrmSearch.xaml /// </summary> public partial class VwCrmSearch : UserControl { public VwCrmSearch() { InitializeComponent(); } private void btnStartSearch_Click(object sender, RoutedEventArgs e) { } } } 14. Modulübergreifende Funktionen In der Methode „StartSearchEvtHdl” wird eine Funktion „RemoveAllOtherControlsFromRegion” verwendet, die nicht Bestandteil des PRISM Frameworks ist. Diese Methode stellt bei der Anzeige eines WPF UserControls sicher, dass alle anderen Controls aus der „WorkArea“ Region entfernt werden. Diese Funktionalität ist so allgemein, dass sie modulübergreifend verwendet werden kann. Deswegen kann man ein extra Assembly erzeugen („File – Add – New Project – Windows – WPF User Control Library “ Als Namen „SharedFuncs.cs“ eintragen), dass für alle UI Klassen gemeinsame Funktionen enthält. Mit dem Anlegen des Assemblies als „WPF User Control Library“ sind alle für WPF notwendigen Referenzen gesetzt. Jetzt muss man nur noch die für PRISM notwendigen Referenzen setzen – siehe Abschnitt „Error! Reference source not found.“. In dem Module kann man jetzt eine Klasse „IRegionExtensions“ anlegen, mit der das IRegion Interface um sinnvolle Extensionsmethoden erweitert wird. Der Code dazu sieht so aus: using System.Windows; using System.Windows.Controls; using Microsoft.Practices.Composite.Regions; namespace SharedFuncs.Extensions { public static class IRegionExtension { public static void HideAllOtherControlsInRegion(this IRegion r) { if (r != null) { IEnumerator<object> o = r.ActiveViews.GetEnumerator(); while (o.MoveNext()) { if (o.Current is System.Windows.Controls.Control) { ((Control)o.Current).Visibility = Visibility.Collapsed; } } } } public static void RemoveAllOtherControlsFromRegion(this IRegion r) { if (r != null) { List<Control> lstControls = new List<Control>(); IEnumerator<object> o = r.ActiveViews.GetEnumerator(); while (o.MoveNext()) { if (o.Current is System.Windows.Controls.Control) { lstControls.Add(((Control)o.Current)); } } foreach (Control c in lstControls) { r.Remove(c); } } } } } 15. Module Deployment Das Projekt kompiliert das erzeugte Module nun schon, die Moduleinitialisierung ist vorbereitet, die Registierung in Menu und Toolbar sind vorgesehen und es könnte auf Benutzereingaben spezielle Funktionen ausgeführt werden. Was fehlt noch??? - Nun, die ApplicationShell muss das neue Modul laden, damit die Funktionalität ausführbar ist. Dazu muss man in der App.Config folgende Einträge haben: <?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="modules" type="Microsoft.Practices.Composite.Modularity.ModulesConfigurationSection, Microsoft.Practices.Composite"/> </configSections> <modules> <module assemblyFile="ModuleCRM.dll" moduleType="ModuleCRM.ModuleInit" moduleName="Module Customer Relationship Management"/> </modules> <system.diagnostics> <trace autoflush ="true"></trace> </system.diagnostics> </configuration> Dieser Eintrag in der Konfigurationsdatei wird von ConfigurationModuleLoader ausgewertet und der Ladevorgang und die Initialiiserung für das Module ausgeführt. Sehr wichtig ist der nächste Schritte Das Assembly des neuen Modules muss in das Directory des ApplicationShell (oder ein Subdirectory) kopiert werden, damit der Loader das Assemblyfile findet. Diesen Kopiervorgang kann man automatisieren, indem man auf das Post-Build Ereignis des Modules diesen Eintrag setzt: xcopy "$(TargetDir)*.*" "$(SolutionDir)ApplicationShell\$(OutDir)" /S /Y Das Post-Build Ereignis kann man in den Properties des Projektes im Tab “Build Events” setzen. Abbildung 9: Konfigurieren des Post Build Ereignis für automatisches Kopieren 16. Die Ausgabe der Bootstrapper Klasse im Output Windows bzw. Debug Console In dieser Ausgabe kann man gut nachverfolgen, welche Module geladen wurden usw. ApplicationShell.vshost.exe Information: 0 : Creating Unity container 'ApplicationShell.vshost.exe' (Managed): Loaded 'C:\martinv\scratch\VS2008\PrismHowTo\ApplicationShell\bin\Debug\Microso ft.Practices.ObjectBuilder2.dll' ApplicationShell.vshost.exe Information: 0 : Configuring container ApplicationShell.vshost.exe Information: 0 : Configuring region adapters ApplicationShell.vshost.exe Information: 0 : Creating shell 'ApplicationShell.vshost.exe' (Managed): Loaded 'C:\Windows\assembly\GAC_MSIL\PresentationFramework.Aero\3.0.0.0__31bf38 56ad364e35\PresentationFramework.Aero.dll', Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled. ApplicationShell.vshost.exe Information: 0 : Initializing modules 'ApplicationShell.vshost.exe' (Managed): Loaded 'C:\martinv\scratch\VS2008\PrismHowTo\ApplicationShell\bin\Debug\ModuleC RM.dll', Symbols loaded. ModuleCRM.ModuleInit.Initialize() ... ApplicationShell.vshost.exe Information: 0 : Bootstrapper sequence completed 17. Hinzufügen weiterer Module Auf diese Weise kann man nun beliebige andere Module erzeugen und in der ApplicationShell benutzbar machen. Zusammenfassung & nächste Schritte Mit diesen einfachen Umsetzungsschritten kann man eine generische modulare Anwendung schreiben. Sollte man die Anwendung später ausbauen, werden schnell weitere technische Anforderungen hinzukommen. Um die gewonnen Modularität beizubehalten sollte man sich mit: dem Konzept der WPFCommand Infrastructure und den Delegate/Composite Commands im Prism für globale, entkoppelte Kommandos zwischen der WPF Bedienoberfläche/-Elementen und den Modulen dem Eventkonzept in PRISM für die fachliche, entkoppelte Kommunikation zwischen Modulen vertraut machen. Eine architekturelle, konzeptionelle Frage, die PRISM explizit nicht mit dem Framework beantwortet, ist die Frage nach der logischen Organisation des UI Codes. Zum Beispiel, setzt man Model-View-Controller Pattern oder Model-View-Presenter Pattern in den Modulen ein. Um die Codestruktur in den fachlichen Modulen zu organisieren, ist es zwingend empfohlen vor dem Start eines echten Projektes mit PRISM hier architekturellen Vorgaben zu entwerfen und festzulegen. Links Beispiel Code http://code.msdn.microsoft.com/Project/Download/FileDownload.aspx?ProjectName=Prism HowTo&DownloadId=3278 PRISM Community Seite auf Codeplex http://www.codeplex.com/CompositeWPF Enterpise Library Community Seite auf Codeplex http://www.codeplex.com/entlib WPF Windows Presentation Foundation http://msdn.microsoft.com/enus/netframework/aa663326.aspx Windows Client Informationen http://windowsclient.net/
© Copyright 2024