Partilhar via


Demonstra Passo a passo: Hospedagem de conteúdo do WPF no Win32

Windows Presentation Foundation (WPF) provides a rich environment for creating applications. No entanto, quando você tem um investimento substancial Win32 código, talvez seja mais eficaz adicionar WPF a funcionalidade de seu aplicativo em vez de reescrever o código original. WPFFornece um mecanismo simples para hospedagem WPF conteúdo em um Win32 janela.

Este tutorial descreve como escrever um aplicativo de exemplo, Que hospeda conteúdo WPF em uma amostra da janela Win32, que hosts WPF conteúdo em um Win32 janela. You can extend this sample to host any Win32 window. Because it involves mixing managed and unmanaged code, the application is written in C++/CLI.

Este tópico contém as seguintes seções.

  • Requirements
  • The Basic Procedure
  • Implementing the Host Application
  • Implementing the WPF Page
  • Tópicos relacionados

Requirements

This tutorial assumes a basic familiarity with both WPF and Win32 programming. For a basic introduction to WPF programming, see Guia de Introdução (WPF). For an introduction to Win32 programming, you should reference any of the numerous books on the subject, in particular Programming Windows by Charles Petzold.

Because the sample that accompanies this tutorial is implemented in C++/CLI, this tutorial assumes familiarity with the use of C++ to program the Win32 API plus an understanding of managed code programming. Familiarity with C++/CLI is helpful but not essential.

Observação

This tutorial includes a number of code examples from the associated sample.However, for readability, it does not include the complete sample code.Para o código de exemplo completo, consulte Que hospeda conteúdo WPF em uma amostra da janela Win32.

The Basic Procedure

Esta seção descreve o procedimento básico, você pode usar para host WPF conteúdo em um Win32 janela. The remaining sections explain the details of each step.

A chave de hospedagem WPF conteúdo em um Win32 janela é o HwndSource classe. Essa classe encapsula a WPF conteúdo em um Win32 janela, permitindo que ele sejam incorporados em seu user interface (UI) como uma janela filho. The following approach combines the Win32 and WPF in a single application.

  1. Implementar o WPF conteúdo como uma classe de gerenciados.

  2. Implement a Win32 application with C++/CLI. If you are starting with an existing application and unmanaged C++ code, you can usually enable it to call managed code by changing your project settings to include the /clr compiler flag.

  3. Set the threading model to single-threaded apartment (STA).

  4. Lidar com o WM_CREATE notificação no seu procedimento de janela e faça o seguinte:

    1. Create a new HwndSource object with the parent window as its parent parameter.

    2. Criar uma instância do seu WPF classe de conteúdo.

    3. Atribua uma referência para o WPF o objeto de conteúdo para o RootVisual propriedade da HwndSource.

    4. Get the HWND for the content. The Handle property of the HwndSource object contains the window handle (HWND). To get an HWND that you can use in the unmanaged part of your application, cast Handle.ToPointer() to an HWND.

  5. Implement a managed class that contains a static field to hold a reference to your WPF content. This class allows you to get a reference to the WPF content from your Win32 code.

  6. Assign the WPF content to the static field.

  7. Receber notificações a partir do WPF conteúdo anexando um manipulador para um ou mais da WPF eventos.

  8. Communicate with the WPF content by using the reference that you stored in the static field to set properties, and so on.

Observação

Você também pode usar Extensible Application Markup Language (XAML) para implementar seu WPF conteúdo.However, you will have to compile it separately as a dynamic-link library (DLL) and reference that DLL from your Win32 application.The remainder of the procedure is similar to that outlined above.

Implementing the Host Application

Esta seção descreve como host WPF o conteúdo em um basic Win32 aplicativo. The content itself is implemented in C++/CLI as a managed class. For the most part, it is straightforward WPF programming. The key aspects of the content implementation are discussed in Implementing the WPF Content.

  • The Basic Application

  • Hosting the WPF Content

  • Holding a Reference to the WPF Content

  • Communicating with the WPF Content

The Basic Application

The starting point for the host application was to create a Microsoft Visual Studio 2005 template.

  1. Open Visual Studio 2005, and select New Project from the File menu.

  2. Select Win32 from the list of Visual C++ project types. If your default language is not C++, you will find these project types under Other Languages.

  3. Select a Win32 Project template, assign a name to the project and click OK to launch the Win32 Application Wizard.

  4. Accept the wizard's default settings and click Finish to start the project.

The template creates a basic Win32 application, including:

  • An entry point for the application.

  • A window, with an associated window procedure (WndProc).

  • A menu with File and Help headings. The File menu has an Exit item that closes the application. The Help menu has an About item that launches a simple dialog box.

Antes de começar a escrever código para o host de WPF conteúdo, você precisa fazer duas modificações ao modelo básico.

The first is to compile the project as managed code. By default, the project compiles as unmanaged code. However, because WPF is implemented in managed code, the project must be compiled accordingly.

  1. Right-click the project name in Solution Explorer and select Properties from the context menu to launch the Property Pages dialog box.

  2. Select Configuration Properties from the tree view in the left pane.

  3. Select Common Language Runtime support from the Project Defaults list in the right pane.

  4. Select Common Language Runtime Support (/clr) from the drop-down list box.

Observação

This compiler flag allows you to use managed code in your application, but your unmanaged code will still compile as before.

WPF uses the single-threaded apartment (STA) threading model. Para funcionar corretamente com o WPF código de conteúdo, você deve definir o modelo de threading do aplicativo para STA, aplicando um atributo para o ponto de entrada.

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

Hosting the WPF Content

O WPF conteúdo é um aplicativo de entrada de endereço simple. It consists of several TextBox controls to take user name, address, and so on. There are also two Button controls, OK and Cancel. When the user clicks OK, the button's Click event handler collects the data from the TextBox controls, assigns it to corresponding properties, and raises a custom event, OnButtonClicked. When the user clicks Cancel, the handler simply raises OnButtonClicked. The event argument object for OnButtonClicked contains a Boolean field that indicates which button was clicked.

O código para o host de WPF conteúdo é implementado em um manipulador para o WM_CREATE notificação na janela do host.

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;

O GetHwnd método usa o tamanho e posição informações além da janela pai manipular e retorna o identificador de janela do hospedado WPF conteúdo.

Observação

You cannot use a #using directive for the System::Windows::Interop namespace.Isso cria uma colisão de nomes entre o MSG no namespace e a estrutura de MSG declarado em WINUSER. h.Em vez disso, você deve usar nomes totalmente qualificados para acessar o conteúdo de namespace.

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();
}

Você não pode hospedar o WPF conteúdo diretamente na janela de aplicativo. Instead, you first create an HwndSource object to wrap the WPF content. Este objeto é, basicamente, uma janela que é projetada para hospedar uma WPF conteúdo. Você hospedar o HwndSource objeto na janela pai criando-a como um filho de um Win32 janela que é parte do seu aplicativo. The HwndSource constructor parameters contain much the same information that you would pass to CreateWindow when you create a Win32 child window.

Em seguida, você cria uma instância da WPF objeto de conteúdo. In this case, the WPF content is implemented as a separate class, WPFPage, using C++/CLI. You could also implement the WPF content with XAML. No entanto, para fazer isso você precisa configurar um projeto separado e criar o WPF conteúdo como um DLL. You can add a reference to that DLL to your project, and use that reference to create an instance of the WPF content.

You display the WPF content in your child window by assigning a reference to the WPF content to the RootVisual property of the HwndSource.

The next line of code attaches an event handler, WPFButtonClicked, to the WPF content OnButtonClicked event. This handler is called when the user clicks the OK or Cancel button. See communicating_with_the_WPF content for further discussion of this event handler.

The final line of code shown returns the window handle (HWND) that is associated with the HwndSource object. You can use this handle from your Win32 code to send messages to the hosted window, although the sample does not do so. The HwndSource object raises an event every time it receives a message. To process the messages, call the AddHook method to attach a message handler and then process the messages in that handler.

Holding a Reference to the WPF Content

For many applications, you will want to communicate with the WPF content later. For example, you might want to modify the WPF content properties, or perhaps have the HwndSource object host different WPF content. To do this, you need a reference to the HwndSource object or the WPF content. The HwndSource object and its associated WPF content remain in memory until you destroy the window handle. However, the variable you assign to the HwndSource object will go out of scope as soon as you return from the window procedure. The customary way to handle this issue with Win32 applications is to use a static or global variable. Unfortunately, you cannot assign a managed object to those types of variables. You can assign the window handle associated with HwndSource object to a global or static variable, but that doe not provide access to the object itself.

The simplest solution to this issue is to implement a managed class that contains a set of static fields to hold references to any managed objects that you need access to. The sample uses the WPFPageHost class to hold a reference to the WPF content, plus the initial values of a number of its properties that might be changed later by the user. This is defined in the header.

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;
};

The latter part of the GetHwnd function assigns values to those fields for later use while myPage is still in scope.

Communicating with the WPF Content

There are two types of communication with the WPF content. The application receives information from the WPF content when the user clicks the OK or Cancel buttons. The application also has a UI that allows the user to change various WPF content properties, such as the background color or default font size.

As mentioned above, when the user clicks either button the WPF content raises an OnButtonClicked event. The application attaches a handler to this event to receive these notifications. If the OK button was clicked, the handler gets the user information from the WPF content and displays it in a set of static controls.

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: ");
    }
}

The handler receives a custom event argument object from the WPF content, MyPageEventArgs. The object's IsOK property is set to true if the OK button was clicked, and false if the Cancel button was clicked.

If the OK button was clicked, the handler gets a reference to the WPF content from the container class. It then collects the user information that is held by the associated WPF content properties and uses the static controls to display the information on the parent window. Because the WPF content data is in the form of a managed string, it has to be marshaled for use by a Win32 control. If the Cancel button was clicked, the handler clears the data from the static controls.

The application UI provides a set of radio buttons that allow the user to modify the background color of the WPF content, and several font-related properties. The following example is an excerpt from the application's window procedure (WndProc) and its message handling that sets various properties on different messages, including the background color. The others are similar, and are not shown. See the complete sample for details and context.

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;

To set the background color, get a reference to the WPF content (hostedPage) from WPFPageHost and set the background color property to the appropriate color. O exemplo usa três opções de cores: a cor original, luz verde ou luz Salmão. The original background color is stored as a static field in the WPFPageHost class. To set the other two, you create a new SolidColorBrush object and pass the constructor a static colors value from the Colors object.

Implementing the WPF Page

Você pode hospedar e usar o WPF o conteúdo sem qualquer conhecimento de implementação real. Se o WPF conteúdo tivesse sido empacotado em um separado DLL, ele foi criado em qualquer common language runtime (CLR) language. Following is a brief walkthrough of the C++/CLI implementation that is used in the sample. This section contains the following subsections.

  • Layout

  • Returning the Data to the Host Window

  • Setting the WPF Properties

Layout

O UI elementos de WPF conteúdo consistem em cinco TextBox controla, com associado Label controles: Nome, endereço, cidade, estado e CEP. There are also two Button controls, OK and Cancel

The WPF content is implemented in the WPFPage class. Layout is handled with a Grid layout element. The class inherits from Grid, which effectively makes it the WPF content root element.

O WPF Construtor de conteúdo utiliza o necessário largura e altura e tamanhos de Grid adequadamente. Em seguida, define o layout básico, criando um conjunto de ColumnDefinition e RowDefinition objetos e adicioná-los para o Grid base do objeto ColumnDefinitions e RowDefinitions coleções, respectivamente. This defines a grid of five rows and seven columns, with the dimensions determined by the contents of the cells.

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]);
  }

Next, the constructor adds the UI elements to the Grid. The first element is the title text, which is a Label control that is centered in the first row of the grid.

//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);

The next row contains the Name Label control and its associated TextBox control. Because the same code is used for each label/textbox pair, it is placed in a pair of private methods and used for all five label/textbox pairs. The methods create the appropriate control, and call the Grid class static SetColumn and SetRow methods to place the controls in the appropriate cell. After the control is created, the sample calls the Add method on the Children property of the Grid to add the control to the grid. The code to add the remaining label/textbox pairs is similar. See the sample code for details.

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

The implementation of the two methods is as follows:

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;
}

Finally, the sample adds the OK and Cancel buttons and attaches an event handler to their Click events.

//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);

Returning the Data to the Host Window

When either button is clicked, its Click event is raised. The host window could simply attach handlers to these events and get the data directly from the TextBox controls. The sample uses a somewhat less direct approach. Ele lida com o Click dentro do WPF de conteúdo e em seguida, gera um evento personalizado OnButtonClicked, para notificar o WPF conteúdo. Isso permite que o WPF o conteúdo para fazer alguma validação de parâmetro antes de notificar o host. The handler gets the text from the TextBox controls and assigns it to public properties, from which the host can retrieve the information.

The event declaration, in WPFPage.h:

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

The Click event handler, 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));
}

Setting the WPF Properties

O Win32 host permite que o usuário altere várias WPF Propriedades de conteúdo. From the Win32 side, it is simply a matter of changing the properties. The implementation in the WPF content class is somewhat more complicated, because there is no single global property that controls the fonts for all controls. Instead, the appropriate property for each control is changed in the properties' set accessors. O exemplo a seguir mostra o código para o DefaultFontFamily propriedade. Setting the property calls a private method that in turn sets the FontFamily properties for the various controls.

From WPFPage.h:

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

From 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;
}

Consulte também

Referência

HwndSource

Conceitos

WPF e a interoperação de Win32