Freigeben über


WPF-Architektur

Dieses Thema enthält eine geführte Tour durch die Windows Presentation Foundation (WPF)-Klassenhierarchie. Es behandelt die meisten wichtigen Subsysteme von WPF und beschreibt, wie sie interagieren. Außerdem werden einige der Von den Architekten von WPF getroffenen Entscheidungen beschrieben.

System.Object

Das primäre WPF-Programmiermodell wird über verwalteten Code verfügbar gemacht. In der Entwurfsphase von WPF gab es eine Reihe von Debatten darüber, wo die Linie zwischen den verwalteten Komponenten des Systems und den nicht verwalteten komponenten gezeichnet werden sollte. Die CLR bietet eine Reihe von Features, die die Entwicklung produktiver und robuster machen (einschließlich Speicherverwaltung, Fehlerbehandlung, gemeinsames Typsystem usw.), aber sie kostenaufwendig sind.

Die Hauptkomponenten von WPF sind in der folgenden Abbildung dargestellt. Die roten Abschnitte des Diagramms (PresentationFramework, PresentationCore und Milcore) sind die wichtigsten Codebereiche von WPF. Von diesen ist nur eine nicht verwaltete Komponente – Milcore. Milcore wird in nicht verwalteten Code geschrieben, um eine enge Integration in DirectX zu ermöglichen. Die gesamte Anzeige in WPF erfolgt über das DirectX-Modul und ermöglicht ein effizientes Hardware- und Softwarerendering. WPF erforderte auch eine feine Kontrolle über Arbeitsspeicher und Ausführung. Das Kompositionsmodul in Milcore ist extrem leistungsabhängig und erforderte, viele Vorteile der CLR aufzugeben, um die Leistung zu steigern.

Die Position von WPF innerhalb von .NET Framework.

Die Kommunikation zwischen verwalteten und nicht verwalteten Teilen von WPF wird weiter unten in diesem Thema erläutert. Der Rest des verwalteten Programmiermodells wird unten beschrieben.

System.Threading.DispatcherObject

Die meisten Objekte in WPF leiten sich von DispatcherObject ab, das die grundlegenden Konstrukte für den Umgang mit Parallelität und Threading bereitstellt. WPF basiert auf einem Messagingsystem, das vom Dispatcher implementiert wird. Dies funktioniert ähnlich wie die vertraute Win32-Nachrichtenpumpe. Tatsächlich verwendet der WPF-Dispatcher User32-Nachrichten zum Ausführen von Cross-Thread-Aufrufen.

Tatsächlich gibt es zwei Kernkonzepte, die wichtig sind, beim Diskutieren von Parallelität in WPF – der Dispatcher und die Threadaffinität.

Während der Entwurfsphase von WPF bestand das Ziel darin, zu einem einzelnen Ausführungsthread zu wechseln, aber ein nicht threadbezogenes "affinitisiertes" Modell. Threadaffinität geschieht, wenn eine Komponente die Identität des ausgeführten Threads verwendet, um einen bestimmten Zustandstyp zu speichern. Die häufigste Form ist die Verwendung des lokalen Threadspeichers (TLS) zum Speichern des Zustands. Threadaffinität erfordert, dass jeder logische Ausführungsthread nur einem physischen Thread im Betriebssystem gehört, der speicherintensiv werden kann. Am Ende wurde das Threadingmodell von WPF mit dem vorhandenen User32-Threadingmodell synchronisiert, das eine einzelner Thread-Ausführung mit Threadaffinität verwendet. Der Hauptgrund hierfür war interoperabilität – Systeme wie OLE 2.0, die Zwischenablage und Internet Explorer erfordern die Ausführung von Sta (Single Thread Affinity).

Da Sie über Objekte mit STA-Threading verfügen, benötigen Sie eine Möglichkeit, zwischen Threads zu kommunizieren und zu überprüfen, ob Sie sich im richtigen Thread befinden. Hier liegt die Rolle des Verteilers. Der Verteiler ist ein einfaches Nachrichtenversandsystem mit mehreren priorisierten Warteschlangen. Beispiele für Nachrichten sind unformatierte Eingabebenachrichtigungen (Maus verschoben), Frameworkfunktionen (Layout) oder Benutzerbefehle (diese Methode ausführen). Durch das Ableiten von DispatcherObject erstellen Sie ein CLR-Objekt mit STA-Verhalten, und es wird Ihnen zum Zeitpunkt der Erstellung ein Zeiger auf einen Dispatcher zugewiesen.

System.Windows.DependencyObject

Eine der wichtigsten Architekturphilosophien, die beim Erstellen von WPF verwendet werden, war eine Präferenz für Eigenschaften gegenüber Methoden oder Ereignissen. Eigenschaften sind deklarativ und ermöglichen es Ihnen, absichten anstelle von Aktionen einfacher anzugeben. Dies unterstützt auch ein modellgesteuertes oder datengesteuertes System zum Anzeigen von Benutzeroberflächeninhalten. Diese Philosophie hatte die beabsichtigte Wirkung, mehr Eigenschaften zu erstellen, an die Sie binden konnten, um das Verhalten einer Anwendung besser zu steuern.

Um mehr von Eigenschaften gesteuert zu werden, war ein umfangreicheres Eigenschaftensystem erforderlich als das von der CLR bereitgestellte. Ein einfaches Beispiel für diese Fülle sind Änderungsbenachrichtigungen. Um eine bidirektionale Bindung zu ermöglichen, benötigen Sie beide Seiten der Bindung, um die Änderungsbenachrichtigung zu unterstützen. Damit das Verhalten an Eigenschaftswerte gebunden ist, müssen Sie benachrichtigt werden, wenn sich der Eigenschaftswert ändert. Das Microsoft .NET Framework verfügt über eine Schnittstelle, INotifyPropertyChange, die es einem Objekt ermöglicht, Änderungsbenachrichtigungen zu veröffentlichen, ist jedoch optional.

WPF stellt ein umfangreicheres Eigenschaftssystem bereit, das vom DependencyObject Typ abgeleitet wird. Das Eigenschaftensystem ist wirklich ein "Abhängigkeits"-Eigenschaftssystem, in dem Abhängigkeiten zwischen Eigenschaftsausdrücken nachverfolgt und Eigenschaftswerte automatisch aktualisiert werden, wenn Abhängigkeiten geändert werden. Wenn Sie beispielsweise über eine Eigenschaft verfügen, die vererbt wird (wie FontSize), wird das System automatisch aktualisiert, wenn sich die Eigenschaft bei einem übergeordneten Element ändert, das den Wert erbt.

Die Grundlage des WPF-Eigenschaftensystems ist das Konzept eines Eigenschaftsausdrucks. In dieser ersten Version von WPF wird das Eigenschaftenausdruckssystem geschlossen, und die Ausdrücke werden alle als Teil des Frameworks bereitgestellt. Ausdrücke sind der Grund, warum das Eigenschaftensystem keine hartcodierte Datenbindung, Formatierung oder Vererbung aufweist, sondern von späteren Ebenen innerhalb des Frameworks bereitgestellt wird.

Das Eigenschaftensystem bietet auch einen geringen Speicher von Eigenschaftswerten. Da Objekte Dutzende (wenn nicht Hunderte) von Eigenschaften aufweisen können und die meisten Werte im Standardzustand (geerbt, durch Formatvorlagen festgelegt usw.) vorhanden sind, muss nicht jede Instanz eines Objekts die volle Gewichtung jeder darin definierten Eigenschaft aufweisen.

Das letzte neue Merkmal des Eigenschaftensystems ist das Konzept angefügter Eigenschaften. WPF-Elemente basieren auf dem Prinzip der Wiederverwendung von Kompositionen und Komponenten. Es ist häufig der Fall, dass einige enthaltende Elemente (z. B. ein Grid Layoutelement) zusätzliche Daten für untergeordnete Elemente benötigen, um das Verhalten (z. B. die Zeilen-/Spalteninformationen) zu steuern. Anstatt alle diese Eigenschaften jedem Element zuzuordnen, darf jedes Objekt Eigenschaftsdefinitionen für jedes andere Objekt bereitstellen. Dies ähnelt den "expando"-Features von JavaScript.

System.Windows.Media.Visual

Wenn ein System definiert ist, werden im nächsten Schritt die Pixel auf den Bildschirm gezeichnet. Die Visual Klasse stellt die Erstellung einer Struktur visueller Objekte bereit, die optional Zeichnungsanweisungen und Metadaten zum Rendern dieser Anweisungen (Clipping, Transformation usw.) enthält. Visual ist so konzipiert, dass sie extrem leicht und flexibel ist, sodass die meisten Features keine öffentliche API-Exposition aufweisen und stark auf geschützte Rückruffunktionen angewiesen sind.

Visual ist wirklich der Einstiegspunkt zum WPF-Kompositionssystem. Visual ist der Verbindungspunkt zwischen diesen beiden Subsystemen, der verwalteten API und der nicht verwalteten Milcore.

WPF zeigt Daten an, indem die nicht verwalteten Datenstrukturen durchlaufen werden, die von der Milcore verwaltet werden. Diese Strukturen, die als Kompositionsknoten bezeichnet werden, stellen eine hierarchische Anzeigestruktur mit Renderinganweisungen an jedem Knoten dar. Auf diesen Baum, der auf der rechten Seite der Abbildung unten dargestellt ist, kann nur über das Nachrichtenprotokoll zugegriffen werden.

Beim Programmieren von WPF erstellen Sie Visual-Elemente und abgeleitete Typen, die intern über dieses Messagingprotokoll mit der Kompositionsstruktur kommunizieren. Jede Visual in WPF kann eine, keine oder mehrere Kompositionsknoten erstellen.

Die visuelle Struktur von Windows Presentation Foundation.

Hier ist ein sehr wichtiges architektonisches Detail zu beachten – der gesamte Baum der visuellen Darstellungen und Zeichnungsanweisungen wird zwischengespeichert. In Grafikbegriffen verwendet WPF ein beibehaltenes Renderingsystem. Auf diese Weise kann das System bei hohen Aktualisierungsraten neu zeichnen, ohne dass das Kompositionssystem durch Rückrufe an den Benutzercode blockiert wird. Dadurch wird verhindert, dass eine nicht reagierende Anwendung auftritt.

Ein weiteres wichtiges Detail, das im Diagramm nicht wirklich spürbar ist, ist, wie das System die Komposition tatsächlich ausführt.

In User32 und GDI arbeitet das System mit einem unmittelbaren Modus für Clipping. Wenn eine Komponente gerendert werden muss, richtet das System eine Beschneidungsgrenzen ein, außerhalb derer die Komponente die Pixel nicht berühren darf, und dann wird die Komponente aufgefordert, Pixel in diesem Feld zu zeichnen. Dieses System funktioniert sehr gut in speicherbeschränkten Systemen, weil, wenn sich etwas ändert, nur die betroffene Komponente angepasst werden muss - keine zwei Komponenten tragen jemals zur Farbe eines einzelnen Pixels bei.

WPF verwendet ein "Maleralgorithmus"-Malmodell. Dies bedeutet, dass nicht jede Komponente abgeschnitten wird, sondern jede Komponente von der Rückseite bis zur Vorderseite der Anzeige gerendert wird. Auf diese Weise kann jede Komponente über das Display der vorherigen Komponente malen. Der Vorteil dieses Modells besteht darin, dass Sie komplexe, teilweise transparente Formen haben können. Mit der heutigen modernen Grafikhardware ist dieses Modell relativ schnell (was nicht der Fall war, als User32/GDI erstellt wurde).

Wie bereits erwähnt, besteht eine Kernphilosophie von WPF darin, zu einem deklarativeren, "eigenschaftsorientierten" Programmiermodell zu wechseln. Im visuellen System zeigt sich dies an einigen interessanten Stellen.

Wenn Sie über das Grafiksystem für den beibehaltenen Modus nachdenken, bewegt sich dies wirklich von einem imperativen DrawLine/DrawLine-Typmodell zu einem datenorientierten Modell – neue Linie()/neue Linie(). Durch diesen Wechsel zum datengesteuerten Rendering können komplexe Vorgänge in den Zeichnungsanweisungen mithilfe von Eigenschaften ausgedrückt werden. Die von Drawing abgeleiteten Typen sind tatsächlich das Objektmodell für das Rendern.

Wenn Sie das Animationssystem auswerten, sehen Sie, dass es fast vollständig deklarativ ist. Anstatt dass ein Entwickler die nächste Position oder die nächste Farbe berechnen muss, können Sie Animationen als Eine Reihe von Eigenschaften für ein Animationsobjekt ausdrücken. Diese Animationen können dann die Absicht des Entwicklers oder Designers ausdrücken (verschieben Sie diese Schaltfläche von hier nach dort in 5 Sekunden), und das System kann die effizienteste Möglichkeit ermitteln, um dies zu erreichen.

System.Windows.UIElement

UIElement definiert Kernsubsysteme, einschließlich Layout, Eingabe und Ereignisse.

Layout ist ein Kernkonzept in WPF. In vielen Systemen gibt es entweder einen festen Satz von Layoutmodellen (HTML unterstützt drei Modelle für Layout, Fluss, Absolut und Tabellen) oder kein Modell für layout (User32 unterstützt wirklich nur absolute Positionierung). WPF begann mit der Annahme, dass Entwickler und Designer ein flexibles, erweiterbares Layoutmodell wollten, das von Eigenschaftswerten und nicht von imperativer Logik gesteuert werden kann. Auf der UIElement-Ebene wird das grundlegende Konzept für Layout eingeführt – ein Modell mit zwei Phasen, Measure- und Arrange-Durchläufen.

Measure ermöglicht einer Komponente zu bestimmen, wie viel Größe sie einnehmen möchte. Dies ist eine separate Phase von Arrange, da es viele Situationen gibt, in denen ein übergeordnetes Element ein untergeordnetes Element dazu auffordert, mehrmals zu messen, um seine optimale Position und Größe zu bestimmen. Die Tatsache, dass übergeordnete Elemente untergeordnete Elemente zur Messung auffordern, verdeutlicht einen weiteren wichtigen Grundsatz von WPF – Anpassung der Größe an den Inhalt. Alle Steuerelemente in WPF unterstützen die Möglichkeit, die Größe auf die natürliche Größe ihres Inhalts zu ändern. Dies erleichtert die Lokalisierung und ermöglicht das dynamische Layout von Elementen, da sich die Größe der Elemente ändert. Die Arrange Phase ermöglicht es einem übergeordneten Element, die endgültige Größe jedes untergeordneten Elements zu positionieren und zu bestimmen.

Viel Zeit wird häufig damit verbracht, über die Ausgabeseite von WPF – Visual und verwandten Objekten zu sprechen. Es gibt jedoch auch eine enorme Menge an Innovationen auf der Inputseite. Wahrscheinlich ist die grundlegendste Änderung des Eingabemodells für WPF das konsistente Modell, mit dem Eingabeereignisse über das System geleitet werden.

Die Eingabe stammt als Signal auf einem Kernelmodus-Gerätetreiber und wird an den richtigen Prozess und Thread durch einen komplizierten Prozess geleitet, der den Windows-Kernel und User32 umfasst. Sobald die User32-Nachricht, die der Eingabe entspricht, an WPF weitergeleitet wird, wird sie in eine unformatierte WPF-Eingabenachricht konvertiert und an den Verteiler gesendet. WPF ermöglicht die Konvertierung von unformatierten Eingabeereignissen in mehrere tatsächliche Ereignisse, sodass Features wie "MouseEnter" auf niedriger Ebene des Systems mit garantierter Zustellung implementiert werden können.

Jedes Eingabeereignis wird in mindestens zwei Ereignisse konvertiert – ein "Vorschau"-Ereignis und das tatsächliche Ereignis. Alle Ereignisse in WPF haben eine Vorstellung vom Routing über die Elementstruktur. Ereignisse "bubblen", wenn sie von einem Ziel nach oben durch den Baum bis zur Wurzel laufen, und "tunneln", wenn sie an der Wurzel beginnen und nach unten zu einem Ziel laufen. Tunnel für Eingabevorschauereignisse, wodurch jedes Element in der Struktur eine Möglichkeit zum Filtern oder Ausführen von Aktionen für das Ereignis ermöglicht wird. Die regulären Ereignisse (nicht in der Vorschau) werden dann vom Ziel zur Wurzel weitergeleitet.

Diese Aufteilung zwischen der Tunnel- und Blasenphase macht die Implementierung von Merkmalen wie Tastenkombinationen in einer zusammengesetzten Welt auf konsistente Weise möglich. In User32 würden Sie Zugriffstasten implementieren, indem Sie eine einzelne globale Tabelle mit allen Zugriffstasten haben, die Sie unterstützen wollten (STRG+N-Zuordnung zu "Neu"). Im Dispatcher für Ihre Anwendung würden Sie TranslateAccelerator aufrufen, der die Eingabemeldungen in User32 überprüfen würde und bestimmt, ob eine mit einer registrierten Kurztaste übereinstimmt. In WPF funktioniert dies nicht, da das System vollständig "komponierbar" ist – jedes Element kann jeden Tastaturbeschleuniger verarbeiten und verwenden. Mit diesem zweistufigen Modell für eingaben können Komponenten ihre eigene "TranslateAccelerator" implementieren.

Um diesen Schritt weiter zu gehen, führt UIElement auch den Begriff "CommandBindings" ein. Mit dem WPF-Befehlssystem können Entwickler Funktionen in Form eines Befehlsendpunkts definieren – etwas, das ICommand implementiert. Befehlsbindungen ermöglichen es einem Element, eine Zuordnung zwischen einer Eingabegeste (STRG+N) und einem Befehl (Neu) zu definieren. Sowohl die Eingabegesten als auch die Befehlsdefinitionen sind erweiterbar und können zur Verwendungszeit miteinander verbunden werden. Dies macht es trivial, z. B. es einem Endbenutzer zu ermöglichen, die Tastenbindungen anzupassen, die er in einer Anwendung verwenden möchte.

Zu diesem Punkt im Thema standen "Kernfeatures" von WPF – Features, die in der PresentationCore-Assembly implementiert wurden, im Mittelpunkt. Beim Erstellen von WPF war eine klare Trennung zwischen grundlegenden Teilen (z. B. der Vertrag für layout mit Measure und Arrange) und Framework-Teilen (wie die Implementierung eines bestimmten Layouts wie Grid) das gewünschte Ergebnis. Ziel war es, einen erweiterbaren Punkt niedrig im Stapel bereitzustellen, der es externen Entwicklern ermöglichen würde, bei Bedarf eigene Frameworks zu erstellen.

System.Windows.FrameworkElement

FrameworkElement kann auf zwei verschiedene Arten betrachtet werden. Es führt eine Reihe von Richtlinien und Anpassungen für die Subsysteme ein, die in niedrigeren Ebenen von WPF eingeführt wurden. Außerdem wird eine Reihe neuer Subsysteme eingeführt.

Die Hauptrichtlinie, die von FrameworkElement eingeführt wurde, betrifft das Layout der Anwendung. FrameworkElement baut auf dem grundlegenden von UIElement eingeführten Layoutvertrag auf und fügt den Begriff eines Layout-'Slots' hinzu, der es Layoutautoren erleichtert, einen konsistenten Satz von eigenschaftsgesteuerten Layoutsemantiken zu verwenden. Eigenschaften wie HorizontalAlignment, VerticalAlignment, MinWidth, und Margin (um einige zu nennen) geben alle Komponenten, die von FrameworkElement einem konsistenten Verhalten innerhalb von Layoutcontainern abgeleitet werden.

FrameworkElement Bietet auch eine einfachere API-Gefährdung für viele Features in den Kernebenen von WPF. Ermöglicht beispielsweise FrameworkElement direkten Zugriff auf Animationen über die BeginStoryboard Methode. A Storyboard bietet eine Möglichkeit, mehrere Animationen für eine Reihe von Eigenschaften zu skripten.

Die beiden wichtigsten Dinge, die FrameworkElement einführt, sind Datenbindung und Stile.

Das Datenbindungssubsystem in WPF sollte für alle Benutzer, die Windows Forms oder ASP.NET zum Erstellen einer Benutzeroberfläche (Application User Interface, UI) verwendet haben, relativ vertraut sein. In jedem dieser Systeme gibt es eine einfache Möglichkeit auszudrücken, dass eine oder mehrere Eigenschaften eines bestimmten Elements an eine Datenmenge gebunden werden sollen. WPF unterstützt die Eigenschaftenbindung, Transformation und Listenbindung vollständig.

Eines der interessantesten Features der Datenbindung in WPF ist die Einführung von Datenvorlagen. Mit Datenvorlagen können Sie deklarativ angeben, wie ein Datenteil visualisiert werden soll. Anstatt eine benutzerdefinierte Benutzeroberfläche zu erstellen, die an Daten gebunden werden kann, können Sie stattdessen das Problem umgehen und es den Daten ermöglichen, die Anzeige zu bestimmen, die erstellt wird.

Das Styling ist wirklich eine leichtgewichtige Form der Datenbindung. Mithilfe der Stildefinition können Sie eine Gruppe von Eigenschaften aus einer gemeinsamen Definition an eine oder mehrere Instanzen eines Elements binden. Formatvorlagen werden auf ein Element entweder durch expliziten Verweis (durch Festlegen der Style Eigenschaft) oder implizit durch Zuordnen einer Formatvorlage zum CLR-Typ des Elements angewendet.

System.Windows.Controls.Control

Das bedeutendste Merkmal des Steuerelements ist das Vorlagensystem. Wenn Sie das Kompositionssystem von WPF als ein Renderingsystem im Retained-Modus betrachten, ermöglicht Templating einem Steuerelement, sein Rendering auf parametrisierte, deklarative Weise zu beschreiben. A ControlTemplate ist wirklich nicht mehr als ein Skript, um eine Reihe von untergeordneten Elementen zu erstellen, mit Bindungen an Eigenschaften, die vom Steuerelement angeboten werden.

Control stellt eine Reihe von Aktieneigenschaften bereit, Foreground, Background, Padding, um nur einige zu nennen, die Vorlagenautoren verwenden können, um die Anzeige eines Steuerelements anzupassen. Die Implementierung eines Steuerelements stellt ein Datenmodell und ein Interaktionsmodell bereit. Das Interaktionsmodell definiert eine Reihe von Befehlen (z. B. Schließen für ein Fenster) und Bindungen an Eingabegesten (z. B. Klicken auf das rote X in der oberen Ecke des Fensters). Das Datenmodell stellt eine Reihe von Eigenschaften bereit, um das Interaktionsmodell anzupassen oder die Anzeige anzupassen (bestimmt durch die Vorlage).

Diese Aufteilung zwischen dem Datenmodell (Eigenschaften), dem Interaktionsmodell (Befehlen und Ereignissen) und dem Anzeigemodell (Vorlagen) ermöglicht eine vollständige Anpassung des Aussehens und Verhaltens eines Steuerelements.

Ein allgemeiner Aspekt des Datenmodells von Steuerelementen ist das Inhaltsmodell. Wenn Sie ein Steuerelement wie Button betrachten, sehen Sie, dass es eine Eigenschaft namens "Content" vom Typ Object hat. In Windows Forms und ASP.NET wäre diese Eigenschaft in der Regel eine Zeichenfolge, die jedoch den Inhaltstyp einschränkt, den Sie in eine Schaltfläche einfügen können. Inhalt für eine Schaltfläche kann entweder eine einfache Zeichenfolge, ein komplexes Datenobjekt oder eine gesamte Elementstruktur sein. Bei einem Datenobjekt wird die Datenvorlage verwendet, um eine Anzeige zu erstellen.

Zusammenfassung

WPF ist so konzipiert, dass Sie dynamische, datengesteuerte Präsentationssysteme erstellen können. Jeder Teil des Systems dient dazu, Objekte durch Eigenschaftensätze zu erstellen, die das Verhalten steuern. Die Datenbindung ist ein grundlegender Bestandteil des Systems und ist auf jeder Ebene integriert.

Herkömmliche Anwendungen erstellen eine Anzeige und werden dann an bestimmte Daten gebunden. In WPF wird alles über das Steuerelement, jeder Aspekt der Anzeige, durch eine Art von Datenbindung generiert. Der In einer Schaltfläche gefundene Text wird angezeigt, indem ein zusammengesetztes Steuerelement innerhalb der Schaltfläche erstellt und die Anzeige an die Inhaltseigenschaft der Schaltfläche gebunden wird.

Wenn Sie mit der Entwicklung von WPF-basierten Anwendungen beginnen, sollten Sie sich sehr vertraut fühlen. Sie können Eigenschaften festlegen sowie Objekte und Daten binden, ähnlich wie bei der Verwendung von Windows Forms oder ASP.NET. Mit einer tieferen Untersuchung der Architektur von WPF werden Sie feststellen, dass die Möglichkeit besteht, viel umfangreichere Anwendungen zu erstellen, die Daten grundsätzlich als Kerntreiber der Anwendung behandeln.

Siehe auch