Compartilhar via


Demonstra Passo a passo: Hospedagem de um controle de Win32 simples em um aplicativo do Windows Presentation Foundation

Windows Presentation Foundation (WPF) provides a rich environment for creating applications. No entanto, quando você tem um investimento substancial em Win32 código, talvez seja mais eficiente a reutilização de pelo menos alguns dos que o código em seu WPF aplicativo em vez de reescrevê-la completamente. WPF Fornece um mecanismo simples para hospedar um Win32 janela, em uma WPF página.

Este tutorial mostra cada parte de um aplicativo, Hospedagem de um Controlarar Caixa de Listagem do Win32 no Windows Presentation Foundation Exemplo, que hospeda um controle Win32 de caixa de listagem. Esse procedimento geral pode ser estendido para hospedar qualquer janela Win32.

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

  • Requisitos
  • O Procedimento Básico
  • Implemente o layout da página
  • Implemente uma classe para hospedar o controle Microsoft Win32
  • Hospede o controle na página
  • Implemente a comunicação entre o controle e a página
  • Tópicos relacionados

Requisitos

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). Para uma introdução à programação Win32, consulte qualquer dos numerosos livros no assunto, em particular Programming Windows por Charles Petzold.

Como o exemplo que acompanha este tutorial é implementado em C#, ele utiliza Platform Invocation Services (PInvoke) para acessar a API Win32. Familiaridade com PInvoke é útil porém não essencial.

ObservaçãoObservação:

Este tutorial inclui uma série de exemplos de código a partir o assoc exemplo de iated. No entanto, para reada bilidade, ele não inclui o código de exemplo completo. Você pode obter ou exibir o código completo de Hospedagem de um Controlarar Caixa de Listagem do Win32 no Windows Presentation Foundation Exemplo.

O Procedimento Básico

Esta seção descreve o procedimento básico para hospedar uma janela Win32 em uma página WPF. As seções restantes passam pelos detalhes de cada etapa.

O procedimento básico de hospedagem é:

  1. Implemente uma página WPF para hospedar a janela. Uma técnica é criar um elemento Border para reservar uma seção da página para a janela hospedada.

  2. Implemente uma classe para hospedar o controle que herda de HwndHost.

  3. Nessa classe, sobrescreva o membro BuildWindowCore da classe HwndHost.

  4. Crie a janela hospedada como um filho da janela que contém a página WPF. Embora a programação WPF convencional não precise usá-lo explicitamente, a página de hospedagem é uma janela com um identificador (HWND). Você recebe o HWND da página por meio do parâmetro hwndParent do método BuildWindowCore. A janela hospedada deve ser criada como um filho deste HWND.

  5. Depois que você criar a janela do host, retorne a HWND da janela hospedada. Se você desejar hospedar um ou mais controles Win32, você normalmente cria uma janela hospedeira como um filho do HWND e faz com que os controles sejam filhos dessa janela hospedeira. O encapsulamento de controles em uma janela hospedeira fornece uma maneira simples de garantir que a sua página WPF receberá notificações a partir dos controles, e que lida com algumas questões específicas de Win32 relativas a notificações que cruzam a fronteira HWND.

  6. Trate as mensagens relevantes enviadas para a janela hospedeira (p.ex., notificações de controles-filho). Há duas maneiras para fazer isso.

    • Se você preferir tratar mensagens em sua classe de hospedagem, substitua o método WndProc da classe HwndHost.

    • Se você preferir que o WPF trate as mensagens, trate o evento MessageHook da classe HwndHost em seu code-behind. Esse evento ocorre para cada mensagem que é recebida pela janela hospedada. Se você escolher essa opção, você ainda deve sobrescrever WndProc, mas você precisa apenas uma implementação mínima.

  7. Sobrescreva os métodos DestroyWindowCore e WndProc da classe HwndHost. Você deve substituir esses métodos para satisfazer o contrato HwndHost, mas você só precisa fornecer uma implementação mínima.

  8. Em seu arquivo code-behind, crie uma instância da classe que hospeda controles e torne-a um filho do elemento Border destinado a hospedar a janela.

  9. Comunique-se com a janela hospedada enviando-lhe mensagens Microsoft Windows e tratando as mensagens de suas janelas-filho (p.ex., notificações enviadas pelos controles).

Implemente o layout da página

O layout para a página WPF que hospeda o controle ListBox consiste de duas regiões. O lado esquerdo da página hospeda vários controles WPF que fornecem uma interface do usuário (UI) que permite que você manipule o controle Win32. O canto superior direito da página tem uma região quadrada para o controle ListBox hospedado.

O código para implementar esse layout é bastante simples. O elemento-raiz é um DockPanel que tem dois elementos-filho. O primeiro é um elemento Border que hospeda o controle ListBox. Ele ocupa um quadrado 200x200 no canto superior direito da página. O segundo é um elemento StackPanel que contém um conjunto de controles que exibem informações do WPF e permitem que você manipule o controle ListBox pela configuração de propriedades de interoperação expostas. Para cada um dos elementos que são filhos de StackPanel, consulte o material de referência para os vários elementos usados para obter detalhes sobre o que são esses elementos ou o que eles fazem, eles estão listados no código de exemplo abaixo mas não serão explicados aqui (o modelo básico de interoperação não requer nenhum deles, eles são fornecidos para adicionar alguma interatividade ao exemplo).

<Window
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="WPF_Hosting_Win32_Control.HostWindow"
  Name="mainWindow"
  Loaded="On_UIReady">

  <DockPanel Background="LightGreen">
    <Border Name="ControlHostElement"
    Width="200"
    Height="200"
    HorizontalAlignment="Right"
    VerticalAlignment="Top"
    BorderBrush="LightGray"
    BorderThickness="3"
    DockPanel.Dock="Right"/>
    <StackPanel>
      <Label HorizontalAlignment="Center"
        Margin="0,10,0,0"
        FontSize="14"
        FontWeight="Bold">Control the Control</Label>
      <TextBlock Margin="10,10,10,10" >Selected Text: <TextBlock  Name="selectedText"/></TextBlock>
      <TextBlock Margin="10,10,10,10" >Number of Items: <TextBlock  Name="numItems"/></TextBlock>

      <Line X1="0" X2="200"
        Stroke="LightYellow"
        StrokeThickness="2"
        HorizontalAlignment="Center"
        Margin="0,20,0,0"/>

      <Label HorizontalAlignment="Center"
        Margin="10,10,10,10">Append an Item to the List</Label>
      <StackPanel Orientation="Horizontal">
        <Label HorizontalAlignment="Left"
          Margin="10,10,10,10">Item Text</Label>
        <TextBox HorizontalAlignment="Left"
          Name="txtAppend"
          Width="200"
          Margin="10,10,10,10"></TextBox>
      </StackPanel>

      <Button HorizontalAlignment="Left"
        Click="AppendText"
        Width="75"
        Margin="10,10,10,10">Append</Button>

      <Line X1="0" X2="200"
        Stroke="LightYellow"
        StrokeThickness="2"
        HorizontalAlignment="Center"
        Margin="0,20,0,0"/>

      <Label HorizontalAlignment="Center"
        Margin="10,10,10,10">Delete the Selected Item</Label>

      <Button Click="DeleteText"
        Width="125"
        Margin="10,10,10,10"
        HorizontalAlignment="Left">Delete</Button>
    </StackPanel>
  </DockPanel>
</Window>  

Implemente uma classe para hospedar o controle Microsoft Win32

O núcleo desse exemplo é a classe que hospeda o controle, ControlHost.cs realmente. Ele herda de HwndHost. O construtor aceita dois parâmetros, altura e largura, que correspondem à altura e largura do elemento Border que hospeda o controle ListBox. Esses valores são usados mais tarde para garantir que o tamanho do controle corresponda ao elemento Border.

public class ControlHost : HwndHost
{
  IntPtr hwndControl;
  IntPtr hwndHost;
  int hostHeight, hostWidth;

  public ControlHost(double height, double width)
  {
    hostHeight = (int)height;
    hostWidth = (int)width;
  }

Há também um conjunto de constantes. Essas constantes são basicamente extraídas de WinUser.h e permitem que você use nomes convencionais ao chamar funções Win32.

internal const int
  WS_CHILD = 0x40000000,
  WS_VISIBLE = 0x10000000,
  LBS_NOTIFY = 0x00000001,
  HOST_ID = 0x00000002,
  LISTBOX_ID = 0x00000001,
  WS_VSCROLL = 0x00200000,
  WS_BORDER = 0x00800000;

Substitua BuildWindowCore para criar a janela Microsoft Win32

Você substitui esse método para criar a janela Win32 que irá ser hospedada pela página e fazer a conexão entre a janela e a página. Como esse exemplo lida com a hospedagem de um controle ListBox, duas janelas são criadas. A primeira é a janela que é realmente hospedada pela página WPF. O controle ListBox é criado como um filho nessa janela.

A razão para essa abordagem é para simplificar o processo de receber notificações a partir do controle. A classe HwndHost permite que você processe as mensagens enviadas para a janela que ela está hospedando. Se você hospedar um controle Win32 diretamente, você receberá as mensagens enviadas para o loop de mensagem interno do controle. Você pode exibir o controle e enviar-lhe mensagens, mas você não receberá as notificações que o controle envia para a janela-pai. Isso significa, entre outras coisas, que você não tem nenhuma maneira de detectar quando o usuário interage com o controle. Em vez disso, crie uma janela hospedeira e faça com que o controle seja um filho de nessa janela. Isso permite que você processe as mensagens para a janela hospedeira incluindo as notificações enviadas a ela pelo controle. Para sua conveniência, visto que a janela hospedeira é pouco mais do que um encapsulador simples para o controle, de agora em diante falaremos desse pacote como se fosse um controle de ListBox.

Crie a janela hospedeira e o controle ListBox

Você pode usar PInvoke para criar uma janela hospedeira para o controle ao criar e registrar uma classe de janela, e assim por diante. No entanto, uma abordagem muito mais simples é criar uma janela com a classe de janela predefinida "estática". Isso fornece o procedimento de janela você precisa para receber notificações a partir do controle, e requer uma quantidade mínima de código.

O HWND do controle é exposto através uma propriedade somente leitura, de modo que a página hospedeira possa usá-lo para enviar mensagens ao controle.

public IntPtr hwndListBox
{
  get { return hwndControl; }
}

O controle ListBox é criado como um filho nessa janela. A altura e largura de ambas as janelas são definidas como os valores passados para o construtor, discutido acima. Isso garante que os tamanhos da janela hospedeira e do controle são idênticos à área reservada na página. Após a criação das janelas, o exemplo retorna um objeto HandleRef que contém o HWND da janela hospedeira.

protected override HandleRef BuildWindowCore(HandleRef hwndParent)
{
  hwndControl = IntPtr.Zero;
  hwndHost = IntPtr.Zero;

  hwndHost = CreateWindowEx(0, "static", "",
                            WS_CHILD | WS_VISIBLE,
                            0, 0,
                            hostHeight, hostWidth,
                            hwndParent.Handle,
                            (IntPtr)HOST_ID,
                            IntPtr.Zero,
                            0);

  hwndControl = CreateWindowEx(0, "listbox", "",
                                WS_CHILD | WS_VISIBLE | LBS_NOTIFY
                                  | WS_VSCROLL | WS_BORDER,
                                0, 0,
                                hostHeight, hostWidth,
                                hwndHost,
                                (IntPtr) LISTBOX_ID,
                                IntPtr.Zero,
                                0);

  return new HandleRef(this, hwndHost);
}
//PInvoke declarations
[DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateWindowEx(int dwExStyle,
                                              string lpszClassName,
                                              string lpszWindowName,
                                              int style,
                                              int x, int y,
                                              int width, int height,
                                              IntPtr hwndParent,
                                              IntPtr hMenu,
                                              IntPtr hInst,
                                              [MarshalAs(UnmanagedType.AsAny)] object pvParam);

Implemente DestroyWindow e WndProc

Além de BuildWindowCore, você também deverá sobrescrever os métodos WndProc e DestroyWindowCore do HwndHost. Nesse exemplo, as mensagens para o controle são tratadas pelo tratador MessageHook, assim, a implementação de WndProc e DestroyWindowCore é mínima. No caso de WndProc, definido handled para false para indicar que a mensagem não foi tratada e retornar 0. For DestroyWindowCore, simplesmente destruir a janela.

protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
  handled = false;
  return IntPtr.Zero;
}

protected override void DestroyWindowCore(HandleRef hwnd)
{
  DestroyWindow(hwnd.Handle);
}
[DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)]
internal static extern bool DestroyWindow(IntPtr hwnd);

Hospede o controle na página

Para hospedar o controle na página, você primeiro cria uma nova instância da classe ControlHost. Passe a altura e largura do elemento de borda que contém o controle (ControlHostElement) para o construtor do ControlHost. Isso garante que a caixa de listagem será dimensionada corretamente. Você então hospeda o controle na página atribuindo o objeto ControlHost à propriedade Child do Border hospedeiro.

O exemplo anexa um tratador ao evento MessageHook do ControlHost para receber mensagens a partir do controle. Este evento é gerado para cada mensagem enviada para a janela hospedada. Nesse caso, essas são as mensagens enviadas à janela que encapsula o verdadeiro controle ListBox, incluindo notificações vindas a partir do controle. O exemplo chama SendMessage para obter informações sobre o controle e modificar seu conteúdo. Os detalhes de como a página se comunica com o controle são discutidos na próxima seção.

ObservaçãoObservação:

Observe que há dois PInvoke declarações de SendMessage. Isso é necessário porque uma usa o parâmetro wParam para passar uma string e a outra o utiliza para passar um número inteiro. Você precisa de uma declaração separada para cada assinatura para garantir que os dados são empacotados corretamente.

 public partial class HostWindow : Window
    {
    int selectedItem;
    IntPtr hwndListBox;
    ControlHost listControl;
    Application app;
    Window myWindow;
    int itemCount;

    private void On_UIReady(object sender, EventArgs e)
    {
      app = System.Windows.Application.Current;
      myWindow = app.MainWindow;
      myWindow.SizeToContent = SizeToContent.WidthAndHeight;
      listControl = new ControlHost(ControlHostElement.ActualHeight, ControlHostElement.ActualWidth);
      ControlHostElement.Child = listControl;
      listControl.MessageHook += new HwndSourceHook(ControlMsgFilter);
      hwndListBox = listControl.hwndListBox;
      for (int i = 0; i < 15; i++) //populate listbox
      {
        string itemText = "Item" + i.ToString();
        SendMessage(hwndListBox, LB_ADDSTRING, IntPtr.Zero, itemText);
      }
      itemCount = SendMessage(hwndListBox, LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero);
      numItems.Text = "" +  itemCount.ToString();
    }
private IntPtr ControlMsgFilter(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
  int textLength;

  handled = false;
  if (msg == WM_COMMAND)
  {
    switch ((uint)wParam.ToInt32() >> 16 & 0xFFFF) //extract the HIWORD
    {
      case LBN_SELCHANGE : //Get the item text and display it
        selectedItem = SendMessage(listControl.hwndListBox, LB_GETCURSEL, IntPtr.Zero, IntPtr.Zero);
        textLength = SendMessage(listControl.hwndListBox, LB_GETTEXTLEN, IntPtr.Zero, IntPtr.Zero);
        StringBuilder itemText = new StringBuilder();
        SendMessage(hwndListBox, LB_GETTEXT, selectedItem, itemText);
        selectedText.Text = itemText.ToString();
        handled = true;
        break;
    }
  }
  return IntPtr.Zero;
}
internal const int
  LBN_SELCHANGE = 0x00000001,
  WM_COMMAND = 0x00000111,
  LB_GETCURSEL = 0x00000188,
  LB_GETTEXTLEN = 0x0000018A,
  LB_ADDSTRING = 0x00000180, 
  LB_GETTEXT = 0x00000189,
  LB_DELETESTRING = 0x00000182,
  LB_GETCOUNT = 0x0000018B;

[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
internal static extern int SendMessage(IntPtr hwnd,
                                       int msg,
                                       IntPtr wParam,
                                       IntPtr lParam);

[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
internal static extern int SendMessage(IntPtr hwnd,
                                       int msg,
                                       int wParam, 
                                       [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lParam);

[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
internal static extern IntPtr SendMessage(IntPtr hwnd,
                                          int msg,
                                          IntPtr wParam,
                                          String lParam);

Implemente a comunicação entre o controle e a página

Você manipula o controle enviando-lhe mensagens Windows. O controle manda um aviso quando o usuário interage com ele enviando notificações para sua janela hospedeira. O exemplo Hospedagem de um Controlarar Caixa de Listagem do Win32 no Windows Presentation Foundation Exemplo inclui uma interface do usuário que fornece vários exemplos de como isso funciona:

  • Acrescentar um item à lista.

  • Apagar o elemento selecionado da lista

  • Exibir o texto do item atualmente selecionado.

  • Obter o número de itens na lista.

O usuário pode também selecionar um item na caixa de listagem clicando sobre ele, como faria de um aplicativo Win32 convencional. Os dados exibidos são atualizados sempre que o usuário alterar o estado do caixa de listagem ao selecionar, adicionar, ou concatenar um item.

Para concatenar itens, envie uma mensagem LB_ADDSTRING à caixa de listagem. Para excluir os itens, envie LB_GETCURSEL para obter o índice da seleção atual e, em seguida, LB_DELETESTRING para excluir o item. O exemplo também envia LB_GETCOUNT e usa o valor retornado para atualizar o display que mostra o número de itens. Ambas as essas instâncias de SendMessage usam uma das declarações de PInvoke discutidas na seção anterior.

private void AppendText(object sender, EventArgs args)
{
  if (txtAppend.Text != string.Empty)
  {
    SendMessage(hwndListBox, LB_ADDSTRING, IntPtr.Zero, txtAppend.Text);
  }
  itemCount = SendMessage(hwndListBox, LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero);
  numItems.Text = "" + itemCount.ToString();
}
private void DeleteText(object sender, EventArgs args)
{
  selectedItem = SendMessage(listControl.hwndListBox, LB_GETCURSEL, IntPtr.Zero, IntPtr.Zero);
  if (selectedItem != -1) //check for selected item
  {
    SendMessage(hwndListBox, LB_DELETESTRING, (IntPtr)selectedItem, IntPtr.Zero);
  }
  itemCount = SendMessage(hwndListBox, LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero);
  numItems.Text = "" + itemCount.ToString();
}

Quando o usuário seleciona um item alterando sua seleção, o controle notifica a janela hospedeira ao enviar uma mensagem WM_COMMAND, que gera o evento MessageHook para a página. O tratador recebe as mesmas informações que o procedimento da janela principal da janela hospedeira. Ele também passa uma referência para um valor booleano, handled. Você define handled para true para indicar que você tratou a mensagem e nenhum processamento adicional é necessário.

WM_COMMAND pode ser enviado por várias razões, logo você deve examinar a identificação de notificação para determinar se ele é um evento que você deseja tratar. A identificação está contida na palavra alta (high word) do parâmetro wParam. Desde Microsoft .NET o não tem uma macro HIWORD, a amostra usa operadores bit a bit para extrair a ID. Se o usuário fez ou alterou a seleção, a ID será LBN_SELCHANGE.

Quando LBN_SELCHANGE for recebido, o exemplo obtém o índice do item selecionado enviando uma mensagem LB_GETCURSEL ao controle. Para obter o texto, você primeiro crie um StringBuilder. Você, em seguida, envia ao controle uma mensagem LB_GETTEXT. Passe o objeto StringBuilder vazio como o parâmetro wParam. Quando SendMessage retornar, o StringBuilder conterá o texto do item selecionado. Esse uso de SendMessage requer ainda outra declaração PInvoke.

Finalmente, defina handled como true para indicar que a mensagem foi tratada. O código a seguir ressalta o método ControlMsgFilter novamente, que é onde esse comportamento é implementado.

private IntPtr ControlMsgFilter(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
  int textLength;

  handled = false;
  if (msg == WM_COMMAND)
  {
    switch ((uint)wParam.ToInt32() >> 16 & 0xFFFF) //extract the HIWORD
    {
      case LBN_SELCHANGE : //Get the item text and display it
        selectedItem = SendMessage(listControl.hwndListBox, LB_GETCURSEL, IntPtr.Zero, IntPtr.Zero);
        textLength = SendMessage(listControl.hwndListBox, LB_GETTEXTLEN, IntPtr.Zero, IntPtr.Zero);
        StringBuilder itemText = new StringBuilder();
        SendMessage(hwndListBox, LB_GETTEXT, selectedItem, itemText);
        selectedText.Text = itemText.ToString();
        handled = true;
        break;
    }
  }
  return IntPtr.Zero;
}

Consulte também

Conceitos

Visão geral sobre interoperabilidade entre WPF e Win32

Getting Started with Windows Presentation Foundation

Referência

HwndHost