Interaktion zwischen WPF und Win32

Dieses Thema enthält eine Übersicht über die Interoperabilität von Windows Presentation Foundation (WPF) und Win32-Code. WPF stellt eine umfangreiche Umgebung zum Erstellen von Anwendungen bereit. Wenn Sie allerdings bereits erheblichen Aufwand für Win32-Code betrieben haben, kann es effektiver sein, zumindest einen Teil dieses Codes wiederzuverwenden.

Grundlagen der Interaktion zwischen WPF und Win32

Es gibt zwei grundlegende Verfahren der Interaktion zwischen WPF- und Win32-Code.

  • Hosten von WPF-Inhalten in einem Win32-Fenster. Mit diesem Verfahren können Sie die erweiterten Grafikfunktionen von WPF im Rahmen standardmäßiger Win32-Fenster und -Anwendungen nutzen.

  • Hosten eines Win32-Fensters in WPF-Inhalten. Mit diesem Verfahren können Sie ein vorhandenes benutzerdefiniertes Win32-Steuerelement im Zusammenhang mit anderem WPF-Inhalt verwenden und Daten über die Grenzen hinweg übergeben.

In diesem Thema wird das Konzept beider Verfahren erläutert. Eine eher codeorientierte Beschreibung zum Hosten von WPF in Win32 finden Sie unter Exemplarische Vorgehensweise: Hosten von WPF-Inhalten in Win32. Eine eher codeorientierte Beschreibung zum Hosten von Winn32 in WPF finden Sie unter Exemplarische Vorgehensweise: Hosten eines Win32-Steuerelements in WPF.

WPF-Interaktionsprojekte

Bei WPF-APIs handelt es sich um verwalteten Code, doch die meisten vorhandenen Win32-Programme werden in nicht verwaltetem C++ geschrieben. Sie können WPF-APIs nicht über ein echtes, nicht verwaltetes Programm aufrufen. Durch Verwendung der /clr-Option mit dem Microsoft Visual C++-Compiler können Sie jedoch eine Mischung aus verwaltetem und nicht verwaltetem Programm erstellen und dabei verwaltete und nicht verwaltete API-Aufrufe nahtlos mischen.

Eine Komplikation auf Projektebene besteht darin, dass Sie XAML-Dateien (Extensible Application Markup Language) nicht in ein C++-Projekt kompilieren können. Dieses Problem lässt sich durch verschiedene Verfahren zur Projektaufteilung kompensieren.

  • Erstellen Sie eine C#-DLL, die alle XAML-Seiten als kompilierte Assembly enthält, und schließen Sie dann diese DLL als Verweis in die ausführbare C++-Datei ein.

  • Erstellen Sie eine ausführbare C#-Datei für den WPF-Inhalt, und verweisen Sie auf eine C++-DLL, die den Win32-Inhalt enthält.

  • Verwenden Sie Load, um XAML zur Laufzeit zu laden, anstatt XAML zu kompilieren.

  • Verwenden Sie überhaupt kein XAML, und schreiben Sie WPF vollständig im Code, wobei die Elementstruktur über Application aufgebaut wird.

Verwenden Sie die für Sie am besten geeignete Vorgehensweise.

Hinweis

Wenn Sie C++ bzw. die CLI noch nicht verwendet haben, werden Ihnen möglicherweise einige „neue“ Schlüsselwörter wie gcnew und nullptr in den Interoperationcodebeispielen auffallen. Diese Schlüsselwörter treten an Stelle der älteren Syntax mit doppeltem Unterstrich (__gc) und ermöglichen eine natürlichere Syntax für verwalteten Code in C++. Weitere Informationen zu den verwalteten Funktionen von C++ bzw. der CLI finden Sie unter Komponentenerweiterungen für Laufzeitplattformen.

Wie WPF HWNDs verwendet

Um „HWND interop“ von WPF optimal nutzen zu können, müssen Sie wissen, wie WPF HWNDs verwendet. Bei keinem HWND können Sie WPF-Rendering mit DirectX-Rendering oder GDI-/GDI+-Rendering kombinieren. Dies hat verschiedene Auswirkungen. Um diese Renderingmodelle überhaupt kombinieren zu können, müssen Sie eine Interaktionslösung erstellen und festgelegte Interaktionssegmente für jedes gewählte Renderingmodell verwenden. Durch das Renderingverhalten wird außerdem eine "Airspace"-Beschränkung erzeugt, die die Funktionsfähigkeit der Interaktionslösung einschränkt. Das "Airspace"-Konzept wird im Thema Technology Regions Overview (Übersicht über die Bereichstechnologie) genauer erklärt.

Alle WPF-Elemente auf dem Bildschirm werden letztlich von einem HWND unterstützt. Wenn Sie ein WPF-Window erstellen, erstellt WPF ein HWND auf oberster Ebene und fügt das Window und dessen WPF-Inhalt mit HwndSource in das HWND ein. Dieses einzige HWND wird vom Rest des WPF-Inhalts in der Anwendung gemeinsam verwendet. Davon ausgenommen sind Menüs, Dropdown-Kombinationsfelder und andere Popupelemente. Diese Elemente erstellen ihr eigenes Fenster auf der obersten Ebene, weshalb ein WPF-Menü über das Fenster-HWND hinausgehen kann, in dem es enthalten ist. Wenn Sie ein HWND mit HwndHost in WPF einfügen, informiert WPF Win32, wie das neue untergeordnete HWND relativ zum Window-HWND von WPF positioniert werden soll.

Ein mit dem HWND verwandtes Konzept ist die Transparenz innerhalb und zwischen jedem HWND. Dies wird auch im Thema Technology Regions Overview (Übersicht über die Bereichstechnologie) erläutert.

Hosten von WPF-Inhalt in einem Microsoft Win32-Fenster

Entscheidend für das Hosten eines WPF in einem Win32-Fenster ist die HwndSource-Klasse. Diese Klasse umschließt den WPF-Inhalt in einem Win32-Fenster, sodass der WPF-Inhalt als untergeordnetes Fenster in Ihre Benutzeroberfläche integriert werden kann. Bei folgender Vorgehensweise werden Win32 und WPF in einer einzigen Anwendung zusammengefasst.

  1. Implementieren Sie den WPF-Inhalt (das Stammelement des Inhalts) als verwaltete Klasse. In der Regel erbt diese Klasse von einer der Klassen, die mehrere untergeordnete Elemente enthalten und/oder als Stammelement verwendet werden können, wie z. B. DockPanel oder Page. In nachfolgenden Schritten wird diese Klasse als WPF-Inhaltsklasse und Instanzen dieser Klasse als WPF-Inhaltsobjekte bezeichnet.

  2. Implementieren Sie eine Windows Anwendung mit C++ bzw. der CLI. Wenn Sie eine vorhandene nicht verwaltete C++-Anwendung verwenden, können Sie diese in der Regel verwalteten Code aufrufen lassen, indem Sie das /clr-Compilerflag in die Projekteinstellungen aufnehmen (die Voraussetzungen zur Unterstützung der /clr-Kompilierung werden in diesem Thema nicht in vollem Umfang beschrieben).

  3. Legen Sie das Threadingmodell auf Singlethread-Apartment (STA) fest. WPF verwendet dieses Threadingmodell.

  4. Behandeln Sie die WM_CREATE-Benachrichtigung in der Fensterprozedur.

  5. Gehen Sie im Handler (oder in einer Funktion, die vom Handler aufgerufen wird) wie folgt vor:

    1. Erstellen Sie ein neues HwndSource-Objekt mit dem HWND des übergeordneten Fensters als dessen parent-Parameter.

    2. Erstellen Sie eine Instanz der WPF-Inhaltsklasse.

    3. Weisen Sie dem WPF-Inhaltsobjekt einen Verweis auf die RootVisual-Eigenschaft des HwndSource-Objekts zu.

    4. Die HwndSource-Eigenschaft des Handle-Objekts enthält das Fensterhandle (HWND). Um ein HWND zu erhalten, das Sie im nicht verwalteten Teil der Anwendung verwenden können, wandeln Sie Handle.ToPointer() in ein HWND um.

  6. Implementieren Sie eine verwaltete Klasse, die ein statisches Feld mit einem Verweis auf das WPF-Inhaltsobjekt enthält. Mithilfe dieser Klasse können Sie einen Verweis auf das WPF-Inhaltsobjekt aus dem Win32-Code erstellen, außerdem verhindert sie, dass das HwndSource-Element versehentlich an die Garbage Collection übergeben wird.

  7. Fügen Sie zum Empfangen von Benachrichtigungen vom WPF-Inhaltsobjekt an ein oder mehrere WPF-Inhaltsobjektereignisse einen Handler an.

  8. Kommunizieren Sie mithilfe des im statischen Feld gespeicherten Verweises mit dem WPF-Inhaltsobjekt, um Eigenschaften festzulegen, Methoden aufzurufen usw.

Hinweis

Sie können einige oder alle WPF-Inhaltsklassendefinitionen für Schritt 1 in XAML mit der standardmäßigen Teilklasse der Inhaltsklasse verwenden, wenn Sie eine separate Assembly erstellen und dann darauf verweisen. Obwohl Sie typischerweise ein Application-Objekt als Teil der Kompilierung von XAML in eine Assembly einschließen, verwenden Sie dieses Application-Objekt nicht als Teil der Interoperation, sondern nur mindestens eine der Stammklassen für XAML-Dateien, auf die die Anwendung verweist, und referenzieren deren Teilklassen. Der Rest der Prozedur entspricht im Wesentlichen der oben beschriebenen.

Jeder dieser Schritte wird durch Code im Thema Exemplarische Vorgehensweise: Hosten von WPF-Inhalt in Win32 veranschaulicht.

Hosten eines Microsoft Win32-Fensters in WPF

Entscheidend für das Hosten eines Win32-Fensters in anderem WPF-Inhalt ist die HwndHost-Klasse. Diese Klasse umschließt das Fenster mit einem WPF-Element, das einer WPF-Elementstruktur hinzugefügt werden kann. HwndHost unterstützt auch APIs zur Ausführung von Aufgaben wie der Verarbeitung von Meldungen für das gehostete Fenster. Die grundlegende Prozedur lautet wie folgt:

  1. Erstellen Sie eine Elementstruktur für eine WPF-Anwendung (durch Code oder Markup). Suchen Sie einen geeigneten und zulässigen Punkt in der Elementstruktur, an dem die HwndHost-Implementierung als untergeordnetes Element hinzugefügt werden kann. In den nachfolgenden Schritten wird dieses Element als reservierendes Element bezeichnet.

  2. Erstellen Sie durch Ableiten von HwndHost ein Objekt mit dem Win32-Inhalt.

  3. Überschreiben Sie in dieser Hostklasse die HwndHost-Methode BuildWindowCore. Geben Sie das HWND des gehosteten Fensters zurück. Möglicherweise ist es sinnvoll, die eigentlichen Steuerelemente als untergeordnetes Fenster des zurückgegebenen Fensters zu umschließen; durch Umschließen der Steuerelemente mit einem Hostfenster kann der WPF-Inhalt Benachrichtigungen von den Steuerelementen erhalten. Diese Vorgehensweise hilft bei der Behebung einiger Win32-Probleme in Bezug auf die Meldungsbehandlung an der Begrenzung des gehosteten Steuerelements.

  4. Überschreiben Sie die HwndHost-Methoden DestroyWindowCore und WndProc. Hiermit sollen eine Bereinigung durchgeführt und Verweise auf den gehosteten Inhalt entfernt werden, insbesondere dann, wenn Sie Verweise auf nicht verwaltete Objekte erstellt haben.

  5. Erstellen Sie in der CodeBehind-Datei eine Instanz der Steuerelement-Hostingklasse, und machen Sie diese zu einem untergeordneten Element des reservierenden Elements. In der Regel wird ein Ereignishandler wie Loaded oder der Konstruktor der partiellen Klasse verwendet. Sie können den Interaktionsinhalt aber auch durch ein Laufzeitverhalten hinzufügen.

  6. Verarbeiten Sie ausgewählte Fenstermeldungen wie Steuerelementbenachrichtigungen. Es gibt zwei Verfahrensweisen. Beide ermöglichen denselben Zugang zum Meldungsstream, Sie können also das Verfahren auswählen, das Ihnen einfacher erscheint.

    • Implementieren Sie die Meldungsverarbeitung für alle Meldungen (nicht nur Meldungen zum Herunterfahren) in der Überschreibung der HwndHost-Methode WndProc.

    • Lassen Sie das WPF-Hostingelement die Meldungen durch Behandeln des MessageHook-Ereignisses verarbeiten. Dieses Ereignis wird für jede Meldung ausgelöst, die an die Hauptfensterprozedur des gehosteten Fensters gesendet wird.

    • Sie können keine Meldungen von prozessexternen Fenstern mit WndProc verarbeiten.

  7. Kommunizieren Sie mit dem gehosteten Fenster mithilfe eines Plattformaufrufs, um die nicht verwaltete SendMessage-Funktion aufzurufen.

Durch Befolgen dieser Schritte wird eine Anwendung erstellt, die per Mauseingabe funktioniert. Sie können das gehostete Fenster durch Implementieren der IKeyboardInputSink-Schnittstelle mit Unterstützung der TAB-Verwendung versehen.

Jeder dieser Schritte wird durch Code im Thema Exemplarische Vorgehensweise: Hosten eines Win32-Steuerelements in WPF veranschaulicht.

HWNDs in WPF

Sie können sich HwndHost als ein spezielles Steuerelement vorstellen. (Technisch gesehen ist HwndHost eine abgeleitete FrameworkElement-Klasse, keine abgeleitete Control-Klasse. Es kann jedoch als Steuerelement für Interaktionszwecke betrachtet werden.) HwndHost abstrahiert die zugrunde liegende Win32-Beschaffenheit des gehosteten Inhalts, sodass der Rest von WPF den gehosteten Inhalt als ein anderes steuerelementartiges Objekt ansieht, das Eingaben rendern und verarbeiten soll. HwndHost verhält sich grundsätzlich wie jedes andere WPF-FrameworkElement, in Bezug auf die Ausgabe (Zeichnung und Grafiken) und die Eingabe (Maus und Tastatur) bestehen basierend auf einer eingeschränkten Unterstützung durch HWNDs jedoch einige wichtige Unterschiede.

Wichtige Unterschiede im Ausgabeverhalten

  • FrameworkElement, die HwndHost-Basisklasse, verfügt über einige Eigenschaften, die Änderungen an der Benutzeroberfläche beinhalten. Hierzu gehören Eigenschaften wie FrameworkElement.FlowDirection, wodurch die Anordnung von Elementen innerhalb dieses Element als übergeordnetes Element geändert wird. Den meisten dieser Eigenschaften werden jedoch keine möglichen Win32-Entsprechungen zugeordnet, selbst wenn solche Entsprechungen vorhanden sind. Zu viele dieser Eigenschaften und ihre Bedeutungen sind zu renderingspezifisch, als dass Zuordnungen praktisch wären. Deshalb hat das Festlegen von Eigenschaften wie FlowDirection für HwndHost keine Auswirkungen.

  • HwndHost kann nicht gedreht, skaliert, verzerrt oder auf andere Weise durch eine Transformation geändert werden.

  • HwndHost bietet keine Unterstützung für die Opacity-Eigenschaft (Alphablending). Falls der Inhalt im HwndHostSystem.Drawing-Vorgänge ausführt, die Alphainformationen enthalten, stellt dies an sich keine Verletzung dar, von HwndHost als Ganzem wird jedoch nur eine Durchlässigkeit von 1,0 (100 %) unterstützt.

  • HwndHost wird über anderen WPF-Elementen in demselben Fenster auf oberster Ebene angezeigt. Ein mit ToolTip oder ContextMenu erstelltes Menü stellt jedoch ein separates Fenster auf oberster Ebene dar und verhält sich deshalb korrekt mit HwndHost.

  • HwndHost berücksichtigt den Auswahlbereich seines übergeordneten UIElement nicht. Dies stellt ein potenzielles Problem dar, wenn Sie versuchen, eine HwndHost-Klasse in einen Bildlaufbereich oder Canvas einzufügen.

Wichtige Unterschiede im Eingabeverhalten

  • Im Allgemeinen werden Eingabeereignisse bei Eingabegeräten innerhalb des von HwndHost gehosteten Win32-Bereichs direkt an Win32 weitergegeben.

  • Während sich die Maus über HwndHost befindet, empfängt die Anwendung keine WPF-Mausereignisse, und der Wert der WPF-Eigenschaft IsMouseOver ist false.

  • Während HwndHost Tastaturfokus besitzt, empfängt die Anwendung keine WPF-Tastaturereignisse, und der Wert der WPF-Eigenschaft IsKeyboardFocusWithin ist false.

  • Liegt der Fokus im HwndHost und wechselt zu einem anderen Steuerelement im HwndHost, werden die -Ereignisse GotFocus oder LostFocus nicht von der Anwendung empfangen.

  • Entsprechende Tablettstifteigenschaften und -Ereignisse verhalten sich analog und geben keine Informationen aus, wenn sich der Tablettstift über HwndHost befindet.

TAB-TASTE und Zugriffstasten

Die IKeyboardInputSink- und IKeyboardInputSite-Schnittstellen ermöglichen eine nahtlose Tastatureinbindung bei gemischten WPF-Anwendungen und Win32-Anwendungen:

  • Umschalten zwischen Win32- und WPF-Komponenten

  • Zugriffstasten funktionieren sowohl beim Fokus auf einer Win32-Komponente als auch auf einer WPF-Komponente.

Sowohl die HwndHost-Klasse als auch die HwndSource-Klasse stellen eine Implementierung von IKeyboardInputSink bereit, sie behandeln jedoch möglicherweise nicht alle Eingabemeldungen, die in komplexeren Szenarien erwünscht sind. Überschreiben Sie die entsprechenden Methoden, um das gewünschte Tastaturverhalten zu erhalten.

Die Schnittstellen unterstützen nur Vorgänge, die den Übergang zwischen dem WPF- und dem Win32-Bereich betreffen. Innerhalb des Win32-Bereichs wird das TAB-Verhalten ggf. vollständig durch die von Win32 implementierte Tabulatorreihenfolgenlogik gesteuert.

Weitere Informationen