.
Anmeldung | Registrieren | Hilfe |
Suchen
Home Foren News Member Offers Termine Developer Blogs Knowledge Base

Navigation

Skip Navigation Links.
Collapse Knowledge BaseKnowledge Base
Collapse TutorialsTutorials
Webentwicklung
Cliententwicklung
Datenbankentwicklung
IT Professional
Sharepoint
Collapse SprachspezifischSprachspezifisch
C#
Visual Basic
C++
XAML
SQL
JavaScript
Collapse ErfahrungsberichteErfahrungsberichte
Entwicklersoftware
Bücher
FAQ Grundlagen

Verknüpfungen

  • Knowledge Base durchsuchen
  • Hilfe zur Knowledge Base
  • RSS Feed
  • Twitter

Anhand Framework Version WPF oder WinForms Anwendung starten

Einleitung und Zielgruppe:
(Schwierigkeit: mittel)
Das Thema WPF oder WinForms? ist aktuell in jedem Projekt, das .NET Oberfläche bietet eine interessante Frage. Für die Projekte die es sich leisten können beides zu implementieren und anhand der installierten .NET Version dann zu entscheiden, ob WPF verwendet wird oder nicht, für die könnte dieser Artikel von Interesse sein.
Der Artikel erklärt sich anhand der von mir entwickelten Beispiel-Solution. Mit dem Erklären der jeweiligen Projekte und Codeabschnitte wird stückchenweise an den Gesamtkontext herangeführt.
Hier könnt Ihr die Beispielsolution runterladen.


Voraussetzungen:
Das Beispielprojekt ist mit VisualStudio 2008 (im Folgenden VS2008) entwickelt worden, und es sollte auf dem Entwicklerrechner dementsprechend ebenfalls dieses VS2008 und das .NET Framework 3.5 oder höher installiert sein. Des weiteren können Kenntnisse in Reflection, Microkernel und dem Starten von Anwendungen nicht schaden, sind aber nicht zwingend erforderlich.


Beschreibung der Beispiel-Solution:
Die Beispielsolution beinhaltet 4 Projekte und ist wie folgt aufgebaut:
Article_WpfOrWinForm_Solution Die Projekte sind in der Verzeichnisstruktur in jeweilige Unterordner aufgeteilt. Jedes Projekt kompiliert die Ausgabedateien in ein Verzeichnis "Bin", das mit im Hauptverzeichnis angelegt wurde.

Gesamtbeschreibung (Workflow):
Der Programmablauf bis zur Entscheidung, ob eine WPF Anwendung oder eine WinForms Anwendung gestartet wird ist hier illustriert:


Article_WpfOrWinForm_Workflow 

Zunächst wird in dem Haupteinstiegspunkt die .NET Assembly geladen, die für das installierte Framework geeignet ist. In der Solution gibt es jeweils ein separates Projekt hierfür (siehe weiter unten). Als Entscheidungskriterium wird die installierte Framework Version (basierend auf dem Registry Eintrag unter HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP) herangezogen. Sollte das 3.5 Framework installiert worden sein, so wird die Assembly in der die WPF Anwendung vorhanden ist geladen, ansonsten wird die WinForms Assembly geladen.

In der geladenen Assembly wird dann mittels Reflection alle Klassen durchsucht. Die erste Klasse die ein definiertes Interface implementiert hat, die wird dann instanziiert und die Interfacemethode aufgerufen. Eine genauere Beschreibung mit Codeerklärung befindet sich weiter unten unter dem Kapitel Projekt Executable.
Die Assemblies (sowohl WPF als auch WinForms) beinhalten also jeweils ein Startobjekt, das dynamisch geladen und ausgeführt wird. Das Prinzip nennt sich Microkernel, und der in der Solution enthaltene Code ist die minimalste Fassung davon. Damit das ein "ausgewachsener" Microkernel wird fehlen noch ein paar Features wie Konfigurierbarkeit, eine Factory und eventuell andere Features. In dem Bild ist der Microkernel Bereich dunkel hinterlegt.


Das Projekt Executable:
Eigenschaften:

  • WinForms Assembly, bei der die Main-Methode verändert wurde
  • Target-Framework 2.0
  • Output Dir: ..\bin
Aus dem Projekt Executable entsteht die Applikations-Exe. Darin enthalten ist lediglich eine Klasse Program, die den Haupteinstiegspunkt enthält.
   1: /// <summary>
   2: /// Der Haupteinstiegspunkt der Applikation.
   3: /// </summary>
   4: [STAThread]
   5: static void Main(string[] args)
   6: {
   7:     Assembly myStartUpAssembly = GetStartupAssembly();
   8:     RunStartProcess(myStartUpAssembly, args);
   9: }

Die Methode GetStartupAssembly() ermittelt welches Framework installiert ist und läd entsprechend entweder MyWpfWindows.dll oder MyWindows.dll. Die geladene Assembly wird als Rückgabewert an den Aufrufer zurückgegeben.
Die Überprüfung welches Framework installiert ist wird mittels den Registry-SubKeys unter dem Eintrag HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP ermittelt. Die Methode die diese Überprüfung macht heißt CheckIfTypeImplementsInterface und gibt True zurück, falls die Framework Version 3.5 installiert wurde, ansonsten False.
   1: /// <summary>
   2: /// Methode, die über die Registry die installierten .NET Frameworks ermittelt
   3: /// und übreprüft, ob Version 3.5 installiert ist.
   4: /// </summary>
   5: /// <returns>True, falls .NET Version 3.5 installiert wurde, andernfalls False.</returns>
   6: private static bool CheckIfFx35Installed()
   7: {
   8:     bool retVal = false;
   9:  
  10:     // Anhand der Registry die installierte .NET Version ermitteln
  11:     RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP");
  12:     if (key != null)
  13:     {
  14:         foreach (string version in key.GetSubKeyNames())
  15:         {
  16:             if (version == "v3.5")
  17:             {
  18:                 retVal = true;
  19:                 break;
  20:             }
  21:         }
  22:     }
  23:  
  24:     return retVal;
  25: }

Die Methode RunStartProcess setzt das Microkernel Prinzip um. Es durchläuft per Reflection alle darin definierten Typen der übergebenen Assembly und sucht nach den Klassen (andere Typen werden übersprungen), die das Interface IStartable (Bechreibung unter Projekt BusinessLogic) implementiert haben. Die erste gefundene Klasse wird instanziiert und es wird die Interfacemethode Start aufgerufen.
Das Prinzip sieht Codetechnisch so aus:
   1: /// <summary>
   2: /// Diese Methode sucht die erste Klasse, die in der übergebenen Assembly das
   3: /// Interface <see cref="T:BusinessLogic.IStartable"/> implementiert hat, und
   4: /// ruft dann die Start Methode auf.
   5: /// </summary>
   6: /// <param name="assemblyToStart">Assembly, die eine Klasse mit o.g. Interface beinhaltet.</param>
   7: /// <param name="args">Parameter, die der Startmethode übergeben werden sollen.</param>
   8: private static void RunStartProcess(Assembly assemblyToStart, string[] args)
   9: {
  10:     Type tInterface = typeof(BusinessLogic.IStartable);
  11:  
  12:     // Jede Klasse durchgehen, und die finden, die IStartable implementiert
  13:     foreach (Type currentType in assemblyToStart.GetTypes())
  14:     {
  15:         // Ist es eine Klasse und hat diese das Interface implementiert?
  16:         if ((currentType.IsClass) && (CheckIfTypeImplementsInterface(currentType, tInterface.FullName)))
  17:         {
  18:             // Instanz per Reflection erzeugen und dann Start-Methode aufrufen
  19:             BusinessLogic.IStartable myStartObject = (BusinessLogic.IStartable) assemblyToStart.CreateInstance(
  20:                     currentType.FullName,
  21:                     false,
  22:                     BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.Public,
  23:                     null,
  24:                     null,
  25:                     null,
  26:                     null);
  27:             myStartObject.Start(args);
  28:         }
  29:     }
  30: }

Knackpunkt ist die Instanziierung, die über CreateInstance und der Angabe der richtigen BindingFlags funktioniert.
Die Feststellung, ob eine Klasse das Interface implementiert erfolgt dadurch, dass versucht wird das Interface über die Typdefinition zu erlangen:
   1: /// <summary>
   2: /// Methode, die überprüft, ob ein Typ das übergebene Interface implementiert.
   3: /// </summary>
   4: /// <param name="typeToCheck">Der Typ, der zu überprüfen ist.</param>
   5: /// <param name="interfaceName">Der voll qualifizierte Name des zu überprüfenden Interfaces.</param>
   6: /// <returns>True, wenn der Typ das Interface implementiert, andernfalls False.</returns>
   7: private static bool CheckIfTypeImplementsInterface(Type typeToCheck, string interfaceName)
   8: {
   9:     return (typeToCheck.GetInterface(interfaceName) != null);
  10: }

Das Projekt BusinessLogic:
Eigenschaften:
  • Klassenbibliothek Assembly
  • Target-Framework 2.0
  • Output Dir: ..\bin
Die BusinessLogic enthält eine Klasse namens Core und eine Interfacedefinition IStartable. Die Klassenbibliothek simuliert hier den Teil der Applikation, der nichts mit der grafischen Oberfläche zu tun hat. Der Teil wird auch von den BEIDEN Assemblies MyWindows und MyWpfWindows (Siehe Beschreibungen weiter unten) genutzt. Die Klasse Core enthält lediglich eine statische Methode, die einen festen String zurückliefert. In der Beispielsolutions wird die Methode für das Beschriften eines Labels aufgerufen (siehe weiter unten).
In einer echten Applikation sind hier natürlich viel mehr Klassen und Methoden/Eigenschaften derfiniert, die die komplette Programmlogik abbilden.
Das Interface IStartable bietet die Möglichkeit eine Einstiegsquelle zu definieren. Über das oben genannte Microkernel Prinzip wird nach Klassen gesucht, die dieses Interface implementieren. Es definiert nur eine Methode, die zu implementieren ist:
   1: /// <summary>
   2: /// Ein Interface, das für den dynamischen Zugriff auf die Assembly
   3: /// benötigt wird, und Zugriffsmethoden definiert um einen Startvorgang
   4: /// einzuleiten.
   5: /// </summary>
   6: public interface IStartable
   7: {
   8:     /// <summary>
   9:     /// Die Start Methode startet einen Vorgang.
  10:     /// </summary>
  11:     /// <param name="args">Argumente, die dem Startvorgang übergeben werden.</param>
  12:     void Start(string[] args);
  13: }


So gesehen definiert es eine eigens zu implementierende Main-Methode (sinnbildlich).


Das Projekt MyWpfWindows:
Eigenschaften:

  • WPF ControlLibrary Assembly
  • Output Dir: ..\bin

Das Projekt ist als ControlLibrary geführt, damit keine Exe sondern eine Dll beim Buildvorgang erstellt wird. Die ControlLibrary kann auch WpfFenster enthalten (so wie im Beispiel), und muss nicht zwangsläufig aus Controls bestehen. Beim Anlegen dieses Projektes wird VS2008 schon automatisch ein Control erzeugen, das aber gleich wieder gelöscht werden darf.
Das Projekt enthält somit ein WPF Fenster namens MainWindow. Wie das nun erstellt wird ist bitte in einem WPF Buch oder Tutorial nachzulesen. Das Fenster sieht so aus:
Article_WpfOrWinForm_WpfWindow

Es gibt ein Label und 2 Buttons. Das Label hat zunächst die Beschriftung "Uninitialized". Drückt man den Button "Initialize" so wird codetechnisch die statische Methode GetHelloText aus der Klasse Core aus der BusinessLogic aufgerufen und dessen Ergebnistext dem Label zugeordnet. Der Button Exit beendet die Applikation.
Weiterhin ist eine Klasse StartApplication in dem Projekt integriert. Sie dient dem Zweck, dass das Microkernel Prinzip diese Klasse findet und mit Hilfe dessen die WPF Anwendung starten wird. Ein Blick im Code verrät uns, wie eine WPF Anwendung gestartet werden kann:

   1: /// <summary>
   2: /// Bietet Methoden und Eigenschaften, die die Anwendung starten lassen.
   3: /// </summary>
   4: public class StartApplication : System.Windows.Application, IStartable
   5: {
   6:     #region IStartable Members
   7:  
   8:     /// <summary>
   9:     /// Die Start Methode startet einen Vorgang.
  10:     /// </summary>
  11:     /// <param name="args">Argumente, die dem Startvorgang übergeben werden.</param>
  12:     public void Start(string[] args)
  13:     {
  14:         this.StartupUri = new System.Uri("pack://application:,,,/MyWPFWindows;component/WpfMainWindow.xaml", System.UriKind.Absolute);
  15:         this.Run();
  16:     }
  17:     #endregion
  18: }


Eine WPF Anwendung erfordert eine Angabe einer URL, wo sie das Startfenster (konkreter die xaml-Datei) finden kann. Die xaml-Datei ist in in diesem Falle in der ControlLibrary zu finden, weshalb hier System.UriKind.Absolute verwendet wurde. Der Verweis pack://application:,,,/MyWPFWindows;component/WpfMainWindow.xaml ist WPF spezifisch. Sollte das Prinzip nicht geläufig sein, so sollte man sich in einem WPF Buch das Prinzip hierzu aneignen.


Das Projekt MyWindows:
Eigenschaften:

  • WinForms ControlLibrary Assembly
  • Target-Framework 2.0
  • Output Dir: ..\bin

Das Projekt ist als ControlLibrary geführt, damit keine Exe sondern eine Dll beim Buildvorgang erstellt wird. Die ControlLibrary kann auch WinForms Fenster enthalten (so wie im Beispiel), und muss nicht zwangsläufig aus Controls bestehen. Beim Anlegen dieses Projektes wird VS2008 schon automatisch ein Control erzeugen, das aber gleich wieder gelöscht werden darf.
Das Projekt enthält ein WinForms Fenster analog zum WPF und namens MainWindow. Das Fenster sieht so aus:


Article_WpfOrWinForm_WinFormsWindow

Es gibt ein Label und 2 Buttons. Das Label hat zunächst die Beschriftung "Uninitialized". Drückt man den Button "Initialize" so wird codetechnisch die statische Methode GetHelloText aus der Klasse Core aus der BusinessLogic aufgerufen und dessen Ergebnistext dem Label zugeordnet. Der Button Exit beendet die Applikation.
Weiterhin ist eine Klasse StartApplication in dem Projekt integriert. Sie dient dem Zweck, dass das Microkernel Prinzip diese Klasse findet und mit Hilfe dessen die WinForms Anwendung starten wird. Ein Blick im Code verrät uns, wie eine WinForms Anwendung gestartet werden kann:

   1: #region IStartable Members
   2:  
   3: /// <summary>
   4: /// Die Start Methode startet einen Vorgang.
   5: /// </summary>
   6: /// <param name="args">Argumente, die dem Startvorgang übergeben werden.</param>
   7: public void Start(string[] args)
   8: {
   9:     Application.EnableVisualStyles();
  10:     Application.SetCompatibleTextRenderingDefault(false);
  11:     Application.Run(new MainWindow());
  12: }
  13:  
  14: #endregion


Der Code sollte jedem bekannt sein, der schon einmal ein neues WinForms Projekt erstellt hat. Es ist der Teil, der in einer Main-Methode in der Klasse Program enthalten ist, das von VS2008 erzeugt wurde.


Fazit:
Mit der Beispiel-Solution sollte ein kurzer Einblick in das Microkernel Prinzip gelungen sein. Weiterhin kann nun mit dieser Grundlage eine Applikation erschaffen werden, die dynamisch entweder eine WPF Anwendung oder eine WinForms Anwendung startet.

Abschließende Bemerkungen:

(Schwierigkeit: schwer)
- Das Interface sollte nicht in der BusinessLogic angegliedert sein. In dem Beispiel habe ich das darin platziert, weil es von allen anderen Projekten refernziert werden kann. Besser wäre es eine eigene .NET Klassenbibliothek zu erstellen, in der nur Interfaces definiert werden. Das Prinzip wäre im Ansatz dann eine Code-Contract Vereinbarungsschnittstelle. Im Framework 4.0 wird es hier aber viel bessere Möglichkeiten dazu geben.
- Weiterhin ist die BusinessLogic hart auf das Framework 2.0 eingestellt. Das würde bedeuten, dass man in der BusinessLogic auf Linq und Extensions etc. verzichten müsste. Das kann man aber leicht ändern, indem man hier die Framework 3.5 Logiken in einer separaten Klassenbibliothek ausgliedert und für das Verwenden der entsprechenden BusinessLogic (2.0 oder 3.5) dort wieder einen kleinen Microkernel und mit Hilfe einer Factory implementiert.
Sollte man mit diesen Dingen nicht vertraut sein, so kann man auch (vorläufig) eine Beschränkung auf .NET 2.0 akzeptieren.


Ich hoffe, dass Euch der Artikel gefallen hat und würde mich über Kommentare freuen.
Euer Timo


Hier könnt Ihr die Beispielsolution runterladen.

von Timo Rehl, 28.01.2010 zugeordnet zu Tutorials .

Kommentare

Es sind noch keine Kommentare vorhanden.

Eigener Kommentar

Sie müssen angemeldet sein, um ein Kommentar zu erstellen.
  • Schwierigkeit: Fortgeschrittene
  • Views: 733
  • Zur Druckversion
  • Artikel von Timo Rehl

Kick it on dotnet-kicks.de

Artikel

Autor

Kick it!

Wenn ihnen dieser Artikel gefällt, bitte "kicken" sie ihn.
Das Team | Regeln | Impressum