Exemplarische Vorgehensweise: Hosten von WPF-Inhalt in Win32

Windows Presentation Foundation (WPF) bietet eine umfangreiche Umgebung zum Erstellen von Anwendungen. Wenn Sie allerdings bereits erheblichen Aufwand für Win32-Code betrieben haben, kann es effektiver sein, zumindest einen Teil dieses Codes in Ihrer WPF-Anwendung wieder zu verwenden, anstatt ihn vollständig neu zu schreiben. WPF bietet ein einfaches Verfahren zum Hosten von WPF-Inhalten in einem Win32-Fenster.

Dieses Tutorial führt Sie durch die Anwendung Beispiel für das Hosten von WPF-Inhalt in einem Win32-Fenster, in der WPF-Inhalt in einem Win32-Fenster gehostet wird. Dieses Beispiel kann auf das Hosten beliebiger Win32-Fenster erweitert werden. Da es das Mischen von verwaltetem und nicht verwaltetem Code umfasst, wird die Anwendung in C++/CLI geschrieben.

Anforderungen

In diesem Lernprogramm wird vorausgesetzt, dass Sie mit den Grundlagen der WPF-Programmierung und der Win32-Programmierung vertraut sind. Eine Einführung in die Grundlagen der WPF-Programmierung finden Sie unter Erste Schritte. Eine Einführung in die Win32-Programmierung finden Sie in zahlreichen Büchern zu diesem Thema, insbesondere in Windows-Programmierung von Charles Petzold.

Da das Beispiel für dieses Lernprogramm in C++/CLI implementiert wurde, werden Kenntnisse im Umgang mit C++ für die Programmierung von Windows API vorausgesetzt. Außerdem sollten Sie mit der Programmierung verwalteten Codes vertraut sein. Grundkenntnisse in C++/CLI sind hilfreich, aber keine Voraussetzung.

Hinweis

Dieses Lernprogramm enthält eine Reihe von Codebeispielen aus dem genannten Beispiel. Aus Gründen der besseren Lesbarkeit wird jedoch nicht der gesamte Beispielcode aufgeführt. Den vollständigen Code können Sie im Beispiel für das Hosten von WPF-Inhalt in einem Win32-Fenster einsehen.

Das grundlegende Verfahren

Dieser Abschnitt enthält das grundlegende Verfahren zum Hosten von WPF-Inhalt in einem Win32-Fenster. In den weiteren Abschnitten werden die einzelnen Schritte näher erläutert.

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

  1. Implementieren Sie Ihren WPF-Inhalt als verwaltete Klasse.

  2. Implementieren Sie eine Windows Anwendung mit C++/CLI. Wenn Sie mit einer vorhandenen Anwendung und nicht verwaltetem C++-Code beginnen, können Sie diese in der Regel verwalteten Code aufrufen lassen, indem Sie das /clr-Compilerflag in die Projekteinstellungen aufnehmen.

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

  4. Behandeln Sie die WM_CREATE-Benachrichtigung in der Fensterprozedur, und führen Sie danach folgende Aktionen durch:

    1. Erstellen Sie ein neues HwndSource-Objekt, und verwenden Sie das übergeordnete Fenster als parent-Parameter.

    2. Erstellen Sie eine Instanz der WPF-Inhaltsklasse.

    3. Weisen Sie dem -Inhaltsobjekt einen Verweis auf die RootVisual-Eigenschaft von HwndSource zu.

    4. Rufen Sie HWND für den Inhalt ab. Die Handle-Eigenschaft des HwndSource-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.

  5. Implementieren Sie eine verwaltete Klasse, die ein statisches Feld mit einem Verweis auf den WPF-Inhalt enthält. Diese Klasse ermöglicht Ihnen, einen Verweis auf den WPF-Inhalt vom Win32-Code abzurufen.

  6. Weisen Sie dem statischen Feld den WPF-Inhalt zu.

  7. Um Benachrichtigungen vom WPF-Inhalt zu empfangen, fügen Sie an eines oder mehrere WPF-Ereignisse einen Handler an.

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

Hinweis

Außerdem können Sie den WPF-Schalter verwenden. Sie müssen sie jedoch separat als DLL (Dynamic Link Library) kompilieren und diese DLL aus Ihrer Win32-Anwendung referenzieren. Die übrigen Schritte ähneln dem oben beschriebenen Verfahren.

Implementieren der Hostanwendung

In diesem Abschnitt wird beschrieben, wie Sie WPF-Inhalt in einer grundlegenden Win32-Anwendung hosten. Der Inhalt selbst wird in C++/CLI als verwaltete Klasse implementiert. Größtenteils handelt es sich um eine einfache WPF-Programmierung. Die wichtigsten Aspekte bei der Implementierung von Inhalt werden unter Implementieren des WPF-Inhalts erläutert.

Die grundlegende Anwendung

Der Ausgangspunkt für die Hostanwendung bestand darin, eine Visual Studio 2005-Vorlage zu erstellen.

  1. Öffnen Sie Visual Studio 2005, und wählen Sie im Menü Datei die Option Neues Projekt aus.

  2. Wählen Sie Win32 aus der Liste der Visual C++-Projekttypen aus. Wenn Ihre Standardsprache nicht C++ ist, finden Sie diese Projekttypen unter Andere Sprachen.

  3. Wählen Sie eine Win32-Projekt-Vorlage aus, weisen Sie dem Projekt einen Namen zu, und klicken Sie auf OK, um den Win32-Anwendungs-Assistenten zu starten.

  4. Übernehmen Sie die Standardeinstellungen des Assistenten, und klicken Sie auf Fertigstellen, um das Projekt zu starten.

Mit der Vorlage wird eine grundlegende Win32-Anwendung erstellt, die Folgendes enthält:

  • Einen Einstiegspunkt für die Anwendung.

  • Ein Fenster mit einer zugeordneten Fensterprozedur (WndProc).

  • Ein Menü mit den Überschriften Datei und Hilfe. Das Menü Datei enthält das Element Beenden, mit dem die Anwendung geschlossen wird. Das Menü Hilfe enthält das Element Info, mit dem ein einfaches Dialogfeld gestartet wird.

Bevor Sie mit dem Schreiben von Code zum Hosten des WPF-Inhalts beginnen, müssen Sie zwei Änderungen an der grundlegenden Vorlage vornehmen.

Die erste besteht darin, das Projekt als verwalteten Code zu kompilieren. Standardmäßig wird das Projekt als nicht verwalteter Code kompiliert. Da aber WPF in verwaltetem Code implementiert wird, muss das Projekt entsprechend kompiliert werden.

  1. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf den Projektnamen, und wählen Sie im Kontextmenü Eigenschaften aus, um das Dialogfeld Eigenschaftenseiten aufzurufen.

  2. Wählen Sie links im Strukturansichtsbereich Konfigurationseigenschaften aus.

  3. Wählen Sie in der Liste Projektstandards im rechten Bereich die Unterstützung für Common Language Runtime aus.

  4. Wählen Sie im Dropdown-Listenfeld Common Language Runtime-Unterstützung (/clr) aus.

Hinweis

Das Compilerflag ermöglicht Ihnen, in der Anwendung verwalteten Code zu verwenden. Der nicht verwaltete Code wird jedoch wie zuvor kompiliert.

WPF verwendet das Threadingmodell Singlethread-Apartment (STA). Damit es ordnungsgemäß mit dem WPF-Inhaltscode funktioniert, müssen Sie das Threadingmodell der Anwendung auf STA festlegen, indem Sie ein Attribut auf den Einstiegspunkt anwenden.

[System::STAThreadAttribute] //Needs to be an STA thread to play nicely with WPF
int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{

Hosten des WPF-Inhalts

Der WPF-Inhalt ist eine einfache Anwendung für die Adresseingabe. Sie besteht aus mehreren TextBox-Steuerelementen zum Erfassen von Benutzername, Adresse usw. Darüber hinaus sind die beiden Button-Steuerelemente OK und Abbrechen enthalten. Wenn der Benutzer auf OK klickt, ruft der Click-Ereignishandler der Schaltfläche Daten aus den TextBox-Steuerelementen ab, weist sie entsprechenden Eigenschaften zu und löst das benutzerdefinierte Ereignis OnButtonClicked aus. Klickt der Benutzer hingegen auf Abbrechen, löst der Handler einfach OnButtonClicked aus. Das Ereignisargumentobjekt für OnButtonClicked enthält ein boolesches Feld, das angibt, auf welche Schaltfläche geklickt wurde.

Der Code zum Hosten des WPF-Inhalts wird in einem Handler für die WM_CREATE-Benachrichtigung im Hostfenster implementiert.

case WM_CREATE :
  GetClientRect(hWnd, &rect);
  wpfHwnd = GetHwnd(hWnd, rect.right-375, 0, 375, 250);
  CreateDataDisplay(hWnd, 275, rect.right-375, 375);
  CreateRadioButtons(hWnd);
break;

Die GetHwnd-Methode übernimmt Größen- und Positionsinformationen sowie das übergeordnete Fensterhandle und gibt das Fensterhandle des gehosteten WPF-Inhalts zurück.

Hinweis

Sie können keine #using-Direktive für den System::Windows::Interop-Namespace verwenden. Dies würde zu einem Namenskonflikt zwischen der MSG-Struktur in diesem Namespace und der in winuser.h deklarierten MSG-Struktur führen. Verwenden Sie stattdessen vollqualifizierte Namen, um auf den Inhalt dieses Namespace zuzugreifen.

HWND GetHwnd(HWND parent, int x, int y, int width, int height)
{
    System::Windows::Interop::HwndSourceParameters^ sourceParams = gcnew System::Windows::Interop::HwndSourceParameters(
    "hi" // NAME
    );
    sourceParams->PositionX = x;
    sourceParams->PositionY = y;
    sourceParams->Height = height;
    sourceParams->Width = width;
    sourceParams->ParentWindow = IntPtr(parent);
    sourceParams->WindowStyle = WS_VISIBLE | WS_CHILD; // style
    System::Windows::Interop::HwndSource^ source = gcnew System::Windows::Interop::HwndSource(*sourceParams);
    WPFPage ^myPage = gcnew WPFPage(width, height);
    //Assign a reference to the WPF page and a set of UI properties to a set of static properties in a class
    //that is designed for that purpose.
    WPFPageHost::hostedPage = myPage;
    WPFPageHost::initBackBrush = myPage->Background;
    WPFPageHost::initFontFamily = myPage->DefaultFontFamily;
    WPFPageHost::initFontSize = myPage->DefaultFontSize;
    WPFPageHost::initFontStyle = myPage->DefaultFontStyle;
    WPFPageHost::initFontWeight = myPage->DefaultFontWeight;
    WPFPageHost::initForeBrush = myPage->DefaultForeBrush;
    myPage->OnButtonClicked += gcnew WPFPage::ButtonClickHandler(WPFButtonClicked);
    source->RootVisual = myPage;
    return (HWND) source->Handle.ToPointer();
}

Sie können den WPF-Inhalt nicht direkt im Anwendungsfenster hosten. Stattdessen müssen Sie zuerst ein HwndSource-Objekt erstellen, um den WPF-Inhalt zu umschließen. Dieses Objekt stellt im Wesentlichen ein Fenster zum Hosten von WPF-Inhalt dar. Sie hosten das HwndSource-Objekt im übergeordneten Fenster, indem Sie es als untergeordnetes Element eines Win32-Fensters erstellen, das Teil Ihrer Anwendung ist. Die HwndSource-Konstruktorparameter enthalten größtenteils dieselben Informationen, die Sie beim Erstellen eines untergeordneten Win32-Fensters an CreateWindow übergeben würden.

Als Nächstes erstellen Sie eine Instanz des WPF-Inhaltsobjekts. In diesem Fall wird der WPF-Inhalt unter Verwendung von C++/CLI als separate Klasse, nämlich WPFPage implementiert. Sie können den WPF-Inhalt auch mit XAML implementieren. Dazu müssen Sie allerdings ein separates Projekt einrichten und den WPF-Inhalt als DLL erstellen. Sie können dem Projekt einen Verweis auf diese DLL hinzufügen und ihn zum Erstellen einer Instanz des WPF-Inhalts verwenden.

Sie zeigen den WPF-Inhalt im untergeordneten Fenster an, indem Sie der RootVisual-Eigenschaft von HwndSource einen Verweis auf den WPF-Inhalt zuweisen.

Durch die nächste Codezeile wird ein Ereignishandler (WPFButtonClicked) an das OnButtonClicked-Ereignis des WPF-Inhalts angefügt. Dieser Handler wird aufgerufen, wenn der Benutzer auf eine der Schaltflächen OK oder Abbrechen klickt. Weitere Informationen zu diesem Ereignishandler finden Sie unter Kommunizieren mit dem WPF-Inhalt.

Durch die letzte angezeigte Codezeile wird das Fensterhandle (HWND) zurückgegeben, das dem HwndSource-Objekt zugeordnet ist. Sie können dieses Handle vom Win32-Code zum Senden von Meldungen an das gehostete Fenster verwenden, auch wenn das in diesem Beispiel nicht gezeigt wird. Das HwndSource-Objekt löst jedes Mal ein Ereignis aus, wenn es eine Meldung empfängt. Rufen Sie zum Verarbeiten der Meldungen die AddHook-Methode auf, um einen Meldungshandler anzufügen und anschließend die Meldungen in diesem Handler zu verarbeiten.

Verweisen auf den WPF-Inhalt

Bei vielen Anwendungen möchten Sie zu einem späteren Zeitpunkt mit dem WPF-Inhalt kommunizieren. Angenommen, Sie möchten die WPF-Inhaltseigenschaften ändern oder vom HwndSource-Objekt andere -Inhalte hosten lassen. Dazu benötigen Sie einen Verweis auf das HwndSource-Objekt oder den WPF-Inhalt. Das HwndSource-Objekt und der zugehörige WPF-Inhalt bleiben im Arbeitsspeicher, bis Sie das Fensterhandle zerstören. Die Variable, die Sie dem HwndSource-Objekt zuweisen, verlässt jedoch den Gültigkeitsbereich, sobald die Steuerung von der Fensterprozedur zurückgegeben wird. Eine übliche Methode, dieses Problem bei Win32-Anwendungen zu umgehen, besteht in der Verwendung einer statischen oder globalen Variablen. Leider können Sie diesen Variablentypen kein verwaltetes Objekt zuweisen. Sie können das Fensterhandle, das dem HwndSource-Objekt zugeordnet ist, einer globalen oder statischen Variablen zuweisen. Dadurch erhalten Sie aber noch keinen Zugriff auf das Objekt.

Die einfachste Lösung für dieses Problem besteht darin, eine verwaltete Klasse zu implementieren, die eine Reihe von statischen Feldern mit Verweisen auf alle verwalteten Objekte enthält, auf die Sie zugreifen müssen. Im Beispiel wird die WPFPageHost-Klasse für einen Verweis auf den WPF-Inhalt verwendet. Außerdem soll sie die Anfangswerte einiger Eigenschaften enthalten, die später vom Benutzer geändert werden können. Dies wird im Header definiert.

public ref class WPFPageHost
{
public:
  WPFPageHost();
  static WPFPage^ hostedPage;
  //initial property settings
  static System::Windows::Media::Brush^ initBackBrush;
  static System::Windows::Media::Brush^ initForeBrush;
  static System::Windows::Media::FontFamily^ initFontFamily;
  static System::Windows::FontStyle initFontStyle;
  static System::Windows::FontWeight initFontWeight;
  static double initFontSize;
};

Der letzte Teil der GetHwnd-Funktion weist diesen Feldern Werte zur späteren Verwendung zu, während myPage im Gültigkeitsbereich verbleibt.

Kommunizieren mit dem WPF-Inhalt

Es gibt zwei Arten der Kommunikation mit dem WPF-Inhalt. Die Anwendung empfängt Informationen vom WPF-Inhalt, wenn der Benutzer auf eine der Schaltflächen OK oder Abbrechen klickt. Die Anwendung verfügt auch über eine Benutzeroberfläche, die es dem Benutzer ermöglicht, zahlreiche WPF-Inhaltseigenschaften zu ändern, z. B. Hintergrundfarbe oder Standardschriftgröße.

Wie oben erwähnt, löst der WPF-Inhalt ein OnButtonClicked-Ereignis aus, wenn der Benutzer auf eine der beiden Schaltflächen klickt. Die Anwendung fügt einen Handler an dieses Ereignis an, um die entsprechenden Benachrichtigungen zu empfangen. Nach dem Klicken auf die Schaltfläche OK ruft der Handler die Benutzerinformationen aus dem WPF-Inhalt ab und zeigt sie in einer Reihe von statischen Steuerelementen an.

void WPFButtonClicked(Object ^sender, MyPageEventArgs ^args)
{
    if(args->IsOK) //display data if OK button was clicked
    {
        WPFPage ^myPage = WPFPageHost::hostedPage;
        LPCWSTR userName = (LPCWSTR) InteropServices::Marshal::StringToHGlobalAuto("Name: " + myPage->EnteredName).ToPointer();
        SetWindowText(nameLabel, userName);
        LPCWSTR userAddress = (LPCWSTR) InteropServices::Marshal::StringToHGlobalAuto("Address: " + myPage->EnteredAddress).ToPointer();
        SetWindowText(addressLabel, userAddress);
        LPCWSTR userCity = (LPCWSTR) InteropServices::Marshal::StringToHGlobalAuto("City: " + myPage->EnteredCity).ToPointer();
        SetWindowText(cityLabel, userCity);
        LPCWSTR userState = (LPCWSTR) InteropServices::Marshal::StringToHGlobalAuto("State: " + myPage->EnteredState).ToPointer();
        SetWindowText(stateLabel, userState);
        LPCWSTR userZip = (LPCWSTR) InteropServices::Marshal::StringToHGlobalAuto("Zip: " + myPage->EnteredZip).ToPointer();
        SetWindowText(zipLabel, userZip);
    }
    else
    {
        SetWindowText(nameLabel, L"Name: ");
        SetWindowText(addressLabel, L"Address: ");
        SetWindowText(cityLabel, L"City: ");
        SetWindowText(stateLabel, L"State: ");
        SetWindowText(zipLabel, L"Zip: ");
    }
}

Der Handler empfängt ein benutzerdefiniertes Ereignisargumentobjekt vom WPF-Inhalt: MyPageEventArgs. Die IsOK-Eigenschaft des Objekts wird auf true festgelegt, wenn auf die Schaltfläche OK geklickt wurde, und auf false wenn auf die Schaltfläche Abbrechen geklickt wurde.

Nach dem Klicken auf die Schaltfläche OK ruft der Handler einen Verweis auf den WPF-Inhalt aus der Containerklasse ab. Anschließend ruft er die Benutzerinformationen ab, die in den zugeordneten WPF-Inhaltseigenschaften enthalten sind. Dabei werden die statischen Steuerelemente zum Anzeigen der Informationen im übergeordneten Fenster verwendet. Da die WPF-Inhaltsdaten in Form einer verwalteten Zeichenfolge vorliegen, müssen Sie gemarshallt werden, damit sie von einem Win32-Steuerelement verwendet werden können. Wenn auf die Schaltfläche Abbrechen geklickt wurde, löscht der Handler die Daten aus den statischen Steuerelementen.

Die UI der Anwendung stellt eine Reihe von Optionsfeldern bereit, mit denen der Benutzer die Hintergrundfarbe des WPF-Inhalts und verschiedene Schriftarteigenschaften ändern kann. Das folgende Beispiel ist ein Auszug aus der Fensterprozedur der Anwendung (WndProc) und deren Meldungsbehandlung, die zahlreiche Eigenschaften für verschiedene Meldungen festlegt, darunter die Hintergrundfarbe. Die anderen sind ähnlich und werden nicht angezeigt. Sehen Sie sich das vollständige Beispiel an, um ausführliche Informationen, auch zum Kontext, zu erhalten.

case WM_COMMAND:
  wmId    = LOWORD(wParam);
  wmEvent = HIWORD(wParam);

  switch (wmId)
  {
  //Menu selections
    case IDM_ABOUT:
      DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
    break;
    case IDM_EXIT:
      DestroyWindow(hWnd);
    break;
    //RadioButtons
    case IDC_ORIGINALBACKGROUND :
      WPFPageHost::hostedPage->Background = WPFPageHost::initBackBrush;
    break;
    case IDC_LIGHTGREENBACKGROUND :
      WPFPageHost::hostedPage->Background = gcnew SolidColorBrush(Colors::LightGreen);
    break;
    case IDC_LIGHTSALMONBACKGROUND :
      WPFPageHost::hostedPage->Background = gcnew SolidColorBrush(Colors::LightSalmon);
    break;

Wenn Sie die Hintergrundfarbe festlegen möchten, rufen Sie einen Verweis auf den WPF-Inhalt (hostedPage) von WPFPageHost ab, und legen Sie die Eigenschaft für die Hintergrundfarbe auf die entsprechende Farbe fest. Im Beispiel werden drei Farboptionen verwendet: die ursprüngliche Farbe, hellgrün oder helle Lachsfarbe. Die ursprüngliche Hintergrundfarbe wird als statisches Feld in der WPFPageHost-Klasse gespeichert. Um die anderen beiden festzulegen, erstellen Sie ein neues SolidColorBrush-Objekt und übergeben dem Konstruktor einen statischen Farbwert aus dem Colors-Objekt.

Implementieren der WPF-Seite

Sie können den WPF-Inhalt hosten und verwenden, ohne die tatsächliche Implementierung zu kennen. Wurde der WPF-Inhalt in einer separaten DLL gepackt, kann er in einer beliebigen CLR-Sprache erstellt worden sein. Nachfolgend finden Sie eine kurze exemplarische Vorgehensweise der C++/CLI-Implementierung, die im Beispiel verwendet wird. Dieser Abschnitt enthält folgende Unterabschnitte:

Layout

Die UI-Elemente im WPF-Inhalt bestehen aus fünf TextBox-Steuerelementen mit zugehörigen Label-Steuerelementen: Name, Adresse, Stadt, Bundesland und Postleitzahl. Darüber hinaus sind die beiden Button-Steuerelemente OK und Abbrechen enthalten.

Der WPF-Inhalt wird in der WPFPage-Klasse implementiert. Das Layout wird mit einem Grid-Layoutelement behandelt. Die Klasse erbt von Grid, wodurch sie zum Stammelement des WPF-Inhalts wird.

Der Konstruktor des WPF-Inhalts übernimmt die erforderliche Breite und Höhe und passt die Größe von Grid entsprechend an. Anschließend wird das grundlegende Layout definiert. Dazu wird eine Gruppe von ColumnDefinition- und RowDefinition-Objekten erstellt, die jeweils der Grid-Auflistung und der ColumnDefinitions-Auflistung der RowDefinitions-Objektbasis hinzugefügt werden. Dadurch wird ein Raster von fünf Zeilen und sieben Spalten definiert. Die Abmessungen werden durch den Inhalt der Zellen bestimmt.

WPFPage::WPFPage(int allottedWidth, int allotedHeight)
{
  array<ColumnDefinition ^> ^ columnDef = gcnew array<ColumnDefinition ^> (4);
  array<RowDefinition ^> ^ rowDef = gcnew array<RowDefinition ^> (6);

  this->Height = allotedHeight;
  this->Width = allottedWidth;
  this->Background = gcnew SolidColorBrush(Colors::LightGray);
  
  //Set up the Grid's row and column definitions
  for(int i=0; i<4; i++)
  {
    columnDef[i] = gcnew ColumnDefinition();
    columnDef[i]->Width = GridLength(1, GridUnitType::Auto);
    this->ColumnDefinitions->Add(columnDef[i]);
  }
  for(int i=0; i<6; i++)
  {
    rowDef[i] = gcnew RowDefinition();
    rowDef[i]->Height = GridLength(1, GridUnitType::Auto);
    this->RowDefinitions->Add(rowDef[i]);
  }

Anschließend fügt der Konstruktor die UI-Elemente zu Grid hinzu. Das erste Element ist der Titeltext. Dabei handelt es sich um ein Label-Steuerelement, das in der ersten Zeile des Rasters zentriert ist.

//Add the title
titleText = gcnew Label();
titleText->Content = "Simple WPF Control";
titleText->HorizontalAlignment = System::Windows::HorizontalAlignment::Center;
titleText->Margin = Thickness(10, 5, 10, 0);
titleText->FontWeight = FontWeights::Bold;
titleText->FontSize = 14;
Grid::SetColumn(titleText, 0);
Grid::SetRow(titleText, 0);
Grid::SetColumnSpan(titleText, 4);
this->Children->Add(titleText);

Die nächste Zeile enthält das Label-Steuerelement für den Namen und das zugehörige TextBox-Steuerelement. Da für jedes aus Beschriftung und Textfeld bestehende Paar derselbe Code verwendet wird, wird er in einem Paar privater Methoden platziert und für alle fünf Paare aus Beschriftung/Textfeld verwendet. Durch die Methoden wird das entsprechende Steuerelement erstellt, und es werden die statischen Methoden Grid und SetColumn der SetRow-Klasse aufgerufen, um die Steuerelemente in der entsprechenden Zelle zu platzieren. Nachdem das Steuerelement erstellt wurde, wird im Beispiel die Add-Methode für die Children-Eigenschaft von Grid aufgerufen, um das Steuerelement zum Raster hinzuzufügen. Der Code zum Hinzufügen der übrigen Beschriftung/Textfeld-Paare ist ähnlich. Sehen Sie sich den Beispielcode an, um ausführliche Informationen zu erhalten.

//Add the Name Label and TextBox
nameLabel = CreateLabel(0, 1, "Name");
this->Children->Add(nameLabel);
nameTextBox = CreateTextBox(1, 1, 3);
this->Children->Add(nameTextBox);

Die Implementierung der beiden Methoden wird wie folgt durchgeführt:

Label ^WPFPage::CreateLabel(int column, int row, String ^ text)
{
  Label ^ newLabel = gcnew Label();
  newLabel->Content = text;
  newLabel->Margin = Thickness(10, 5, 10, 0);
  newLabel->FontWeight = FontWeights::Normal;
  newLabel->FontSize = 12;
  Grid::SetColumn(newLabel, column);
  Grid::SetRow(newLabel, row);
  return newLabel;
}
TextBox ^WPFPage::CreateTextBox(int column, int row, int span)
{
  TextBox ^newTextBox = gcnew TextBox();
  newTextBox->Margin = Thickness(10, 5, 10, 0);
  Grid::SetColumn(newTextBox, column);
  Grid::SetRow(newTextBox, row);
  Grid::SetColumnSpan(newTextBox, span);
  return newTextBox;
}

Abschließend werden in dem Beispiel die Schaltflächen OK und Abbrechen hinzugefügt, und es wird ein Ereignishandler an ihre Click-Ereignisse angefügt.

//Add the Buttons and atttach event handlers
okButton = CreateButton(0, 5, "OK");
cancelButton = CreateButton(1, 5, "Cancel");
this->Children->Add(okButton);
this->Children->Add(cancelButton);
okButton->Click += gcnew RoutedEventHandler(this, &WPFPage::ButtonClicked);
cancelButton->Click += gcnew RoutedEventHandler(this, &WPFPage::ButtonClicked);

Zurückgeben der Daten an das Hostfenster

Das Click-Ereignis wird durch Klicken auf eine der Schaltflächen ausgelöst. Das Hostfenster könnte einfach Handler an diese Ereignisse anfügen und die Daten direkt aus den TextBox-Steuerelementen abrufen. In diesem Beispiel wird ein weniger direkter Ansatz verwendet. Click wird innerhalb des WPF-Inhalts behandelt, und anschließend wird ein benutzerdefiniertes OnButtonClicked-Ereignis ausgelöst, um den WPF-Inhalt zu benachrichtigen. Dadurch kann der WPF-Inhalt die Gültigkeit einiger Parameter überprüfen, bevor der Host benachrichtigt wird. Der Handler fragt den Text aus den TextBox-Steuerelementen ab und weist ihn öffentlichen Eigenschaften zu, aus denen der Host die Informationen abrufen kann.

Die Ereignisdeklaration in WPFPage.h:

public:
  delegate void ButtonClickHandler(Object ^, MyPageEventArgs ^);
  WPFPage();
  WPFPage(int height, int width);
  event ButtonClickHandler ^OnButtonClicked;

Der Click-Ereignishandler in WPFPage.cpp:

void WPFPage::ButtonClicked(Object ^sender, RoutedEventArgs ^args)
{

  //TODO: validate input data
  bool okClicked = true;
  if(sender == cancelButton)
    okClicked = false;
  EnteredName = nameTextBox->Text;
  EnteredAddress = addressTextBox->Text;
  EnteredCity = cityTextBox->Text;
  EnteredState = stateTextBox->Text;
  EnteredZip = zipTextBox->Text;
  OnButtonClicked(this, gcnew MyPageEventArgs(okClicked));
}

Festlegen der WPF-Eigenschaften

Der Win32-Host ermöglicht es dem Benutzer, mehrere WPF-Inhaltseigenschaften zu ändern. In Win32 müssen lediglich Eigenschaften geändert werden. Die Implementierung in der WPF-Inhaltsklasse ist etwas komplizierter, da es keine einzelne globale Eigenschaft gibt, mit der die Schriftarten für alle Steuerelemente gesteuert werden. Stattdessen wird die entsprechende Eigenschaft für die einzelnen Steuerelemente in den set-Accessoren der Eigenschaften geändert. Im folgenden Beispiel wird der Code für die DefaultFontFamily-Eigenschaft angezeigt. Durch Festlegen der Eigenschaft wird eine private Methode aufgerufen, mit der die FontFamily-Eigenschaften für die verschiedenen Steuerelemente festgelegt werden.

Aus WPFPage.h:

property FontFamily^ DefaultFontFamily
{
  FontFamily^ get() {return _defaultFontFamily;}
  void set(FontFamily^ value) {SetFontFamily(value);}
};

Aus WPFPage.cpp:

void WPFPage::SetFontFamily(FontFamily^ newFontFamily)
{
  _defaultFontFamily = newFontFamily;
  titleText->FontFamily = newFontFamily;
  nameLabel->FontFamily = newFontFamily;
  addressLabel->FontFamily = newFontFamily;
  cityLabel->FontFamily = newFontFamily;
  stateLabel->FontFamily = newFontFamily;
  zipLabel->FontFamily = newFontFamily;
}

Weitere Informationen