Partager via


Procédure pas à pas : hébergement d'un contrôle Win32 simple dans une application Windows Presentation Foundation

Mise à jour : novembre 2007

Windows Presentation Foundation (WPF) fournit un environnement riche pour créer des applications. Toutefois, lorsque vous avez un investissement substantiel dans du code Win32, il peut être plus efficace de réutiliser au moins une partie de ce code dans votre application WPF plutôt que le réécrire complètement. WPF propose un mécanisme simple pour héberger une fenêtre Win32, sur une page WPF.

Ce didacticiel vous montre pas à pas une application, Hébergement d'un contrôle ListBox Win32 dans Windows Presentation Foundation, exemple, qui héberge un contrôle zone de liste Win32. Cette procédure générale peut être étendue à l'hébergement de toute fenêtre Win32.

Cette rubrique comprend les sections suivantes.

  • Configuration requise
  • La procédure de base :
  • Implémenter la Présentation Page
  • Implémenter une classe pour héberger le contrôle Microsoft Win32
  • Héberger le contrôle dans la page
  • Implémenter la communication entre le contrôle et la page
  • Rubriques connexes

Configuration requise

Ce didacticiel suppose que avez des connaissances de base en matière de programmation WPF et Win32. Pour une présentation générale de la programmation WPF, consultez Mise en route (WPF). Pour une présentation de la programmation Win32, consultez les divers manuels qui traitent de ce sujet, notamment La programmation Windows de Charles Petzold.

Comme l'exemple qui accompagne ce didacticiel est implémenté dans C#, il utilise services d'appel à la plateforme (PInvoke) pour accéder à l'Win32API. Une certaine familiarisation avec PInvoke est utile, mais pas essentielle.

Remarque :

Ce didacticiel inclut des exemples de code de l'exemple associé. Toutefois, il n'inclut pas l'exemple de code complet pour une meilleure lisibilité. Vous pouvez obtenir ou consulter le code complet à partir de Hébergement d'un contrôle ListBox Win32 dans Windows Presentation Foundation, exemple.

La procédure de base :

Cette section décrit la procédure de base pour héberger une fenêtre Win32 sur une page WPF. Les sections restantes reprennent chaque étape en détail.

La procédure d'hébergement de base est :

  1. Implémenter une page WPF pour héberger la fenêtre. Une technique consiste à créer un élément Border pour réserver une section de la page pour la fenêtre hébergée.

  2. Implémenter une classe pour héberger le contrôle qui hérite de HwndHost.

  3. Dans cette classe hôte, substituez le membre de classe BuildWindowCoreHwndHost.

  4. Créer la fenêtre hébergée comme un enfant de la fenêtre qui contient la page WPF. Bien que la programmation WPF conventionnelle n'ait pas besoin de l'utiliser explicitement, la page d'hébergement est une fenêtre avec un handle (HWND). Vous recevez le page HWND via le paramètre hwndParent de la méthode BuildWindowCore. La fenêtre hébergée doit être créée comme un enfant de ce HWND.

  5. Une fois que vous avez créé la fenêtre hôte, retournez le HWND de la fenêtre hébergée. Si vous souhaitez héberger un ou plusieurs contrôles Win32, vous créez en général une fenêtre hôte comme un enfant du HWND et faites des contrôles les enfants de cette fenêtre d'hôte. L'encapsulation des contrôles dans une fenêtre hôte offre un moyen simple permettant à votre page WPF de recevoir des notifications des contrôles, qui traitent de quelques problèmes Win32 particuliers avec les notifications à travers la limite HWND.

  6. Gérer les messages sélectionnés envoyés à la fenêtre hôte, telles que les notifications des contrôles enfants. Il y a deux manières pour effectuer cette opération :

    • Si vous préférez gérer des messages dans votre classe d'hébergement, substituez la méthode WndProc de la classe HwndHost.

    • Si vous préférez que le WPF gère les messages, gérez la classe HwndHost d'événement MessageHook dans votre code-behind. Cet événement se produit pour chaque message reçu par la fenêtre hébergée. Si vous choisissez cette option, vous devez quand même substituer WndProc, mais vous n'avez besoin que d'une implémentation minime.

  7. Substituez les méthodes DestroyWindowCore et WndProc de HwndHost. Vous devez substituer ces méthodes pour satisfaire le contrat HwndHost, mais vous pouvez ne devoir fournir qu'une implémentation minime.

  8. Dans votre fichier code-behind, créez une instance de la classe d'hébergement de contrôle et faites-en un enfant de l'élément Border prévu pour héberger la fenêtre.

  9. Communiquez avec la fenêtre hébergée en lui envoyant des messages Microsoft Windows et en gérant des messages à partir de ses fenêtres enfants, telles que les notifications envoyées par des contrôles.

Implémenter la Présentation Page

La présentation pour la page WPF qui héberge le contrôle ListBox se compose de deux régions. Le côté gauche de la page héberge plusieurs contrôles WPF qui fournissent une interface utilisateur (UI) vous permettant de manipuler le contrôle Win32. Le coin supérieur droit de la page comporte une zone carrée pour le contrôle ListBox hébergé.

Le code pour implémenter cette présentation est assez simple. L'élément racine est un DockPanel qui a deux éléments enfants. Le premier est un élément Border qui héberge le contrôle ListBox. Il occupe un carré de 200x200 dans le coin supérieur droit de la page. Le second est un élément StackPanel qui contient un jeu de contrôles WPF qui affichent des informations et vous permettent de manipuler le contrôle ListBox en définissant des propriétés d'interopérabilité exposées. Pour chacun des éléments qui sont enfants du StackPanel, consultez les documents de référence pour les divers éléments utilisés pour plus d'informations sur ce que sont ou font ces éléments, ceux-ci sont répertoriés dans l'exemple de code ci-dessous mais ne seront pas expliqués ici (le modèle d'interopérabilité de base n'a besoin d'aucun d'entre eux, ils sont fournis pour ajouter un peu d'interactivité à l'exemple).

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

Implémenter une classe pour héberger le contrôle Microsoft Win32

Le cœur de cet exemple est la classe qui héberge effectivement le contrôle, ControlHost.cs. Il hérite de HwndHost. Le constructeur prend deux paramètres, hauteur et largeur, qui correspondent à la hauteur et à la largeur de l'élément Border qui héberge le contrôle ListBox. Ces valeurs sont utilisées ultérieurement pour garantir que la taille du contrôle correspond à l'élément Border.

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

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

Il y a également un jeu de constantes. Ces constantes proviennent pour l'essentiel de Winuser.h, et vous permettent d'utiliser des noms conventionnels lors de l'appel de fonctions 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;

Substituer BuildWindowCore pour créer la fenêtre Microsoft Win32

Vous substituez cette méthode pour créer la fenêtre Win32 qui sera hébergée par la page et qui établira la connexion entre la fenêtre et la page. Comme cet exemple implique l'hébergement d'un contrôle ListBox, deux fenêtres sont créées. La première est la fenêtre hébergée effectivement par la page WPF. Le contrôle ListBox est créé comme un enfant de cette fenêtre.

La raison pour cette approche est de simplifier le processus de réception des notifications du contrôle. La classe HwndHost vous permet de traiter des messages envoyés à la fenêtre qu'elle héberge. Si vous hébergez un contrôle Win32 directement, vous recevez les messages envoyés à la boucle de messages interne du contrôle. Vous pouvez afficher le contrôle et lui envoyer des messages, mais vous ne recevez pas les notifications que le contrôle envoie à sa fenêtre parente. Cela signifie, entre autres choses, que vous n'avez aucun moyen de détecter quand l'utilisateur interagit avec le contrôle. Créez à la place une fenêtre hôte et faites du contrôle un enfant de cette fenêtre. Cela vous permet de traiter les messages pour la fenêtre hôte y compris les notifications qui lui sont envoyées par le contrôle. Par souci de commodité, comme la fenêtre hôte n'est guère plus qu'un simple wrapper pour le contrôle, le package sera connu sous le nom de contrôle ListBox.

Créer la fenêtre hôte et le contrôle ListBox

Vous pouvez utiliser PInvoke pour créer une fenêtre hôte pour le contrôle en créant et en enregistrant une classe de fenêtres, etc. Toutefois, une approche beaucoup plus simple consiste à créer une fenêtre avec la classe de fenêtres "static" prédéfinie. Cela vous fournit la procédure de fenêtre dont vous avez besoin pour recevoir des notifications du contrôle et requiert un codage minime.

Le HWND du contrôle est exposé via une propriété en lecture seule, afin que la page hôte puisse l'utiliser pour envoyer des messages au contrôle.

public IntPtr hwndListBox
{
  get { return hwndControl; }
}

Le contrôle ListBox est créé comme un enfant de la fenêtre hôte. Les valeurs de hauteur et de largeur des deux fenêtres sont celles valeurs passées au constructeur, traité ci-dessus. Cela garantit que la taille de la fenêtre hôte et du contrôle est identique à la zone réservée sur la page. Après avoir créé les fenêtres, l'exemple retourne un objet HandleRef qui contient le HWND de la fenêtre hôte.

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

Implémenter DestroyWindow et WndProc

En plus de BuildWindowCore, vous devez substituer les méthodes WndProc et DestroyWindowCore du HwndHost. Dans cet exemple, les messages pour le contrôle sont gérés par le gestionnaire MessageHook, de sorte que l'implémentation de WndProc et DestroyWindowCore est minime. Dans le cas de WndProc, affectez à handled la valeur false pour indiquer que le message n'a pas été géré et retourne 0. Pour DestroyWindowCore, détruisez simplement la fenêtre.

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

Héberger le contrôle dans la page

Pour héberger le contrôle dans la page, vous créez tout d'abord une nouvelle instance de la classe ControlHost. Passez la hauteur et la largeur de l'élément de bordure qui contient le contrôle (ControlHostElement) au constructeur ControlHost. Cela garantit que la Zone de liste est dimensionnée correctement. Vous hébergez ensuite le contrôle dans la page en assignant l'objet ControlHost à la propriété Child de l'hôte Border.

L'exemple joint un gestionnaire à l'événement MessageHook du ControlHost pour recevoir des messages du contrôle. Cet événement est déclenché pour chaque message envoyé à la fenêtre hébergée. Dans ce cas, ce sont les messages envoyés à la fenêtre qui encapsulent le contrôle ListBox réel, y compris les notifications du contrôle. L'exemple appelle SendMessage pour obtenir des informations du contrôle et modifier son contenu. Les détails de la façon dont la page communique avec le contrôle sont traités dans la section suivante.

Remarque :

Remarquez qu'il existe deux déclarations PInvoke pour SendMessage. Ceci est nécessaire parce que l'un utilise le paramètre wParam pour passer une chaîne et que l'autre l'utilise pour passer un entier. Vous avez besoin d'une déclaration séparée pour chaque signature afin de garantir que les données sont canalisées correctement.

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

Implémenter la communication entre le contrôle et la page

Vous manipulez le contrôle en lui envoyant des messages Windows. Le contrôle vous notifie lorsque l'utilisateur interagit avec lui en envoyant des notifications à sa fenêtre hôte. L'exemple Hébergement d'un contrôle ListBox Win32 dans Windows Presentation Foundation, exemple inclut une interface utilisateur fournissant plusieurs exemples de ce fonctionnement :

  • Ajouter un élément à la liste.

  • Supprimer l'élément sélectionné dans la liste

  • Afficher le texte de l'élément actuellement sélectionné.

  • Afficher le nombre d'éléments de la liste.

L'utilisateur peut également sélectionner un élément dans la zone de liste en cliquant dessus, tout comme il le ferait pour une application Win32 conventionnelle. Les données affichées sont mises à jour chaque fois que l'utilisateur modifie l'état de la zone de liste en sélectionnant, en ajoutant, ou en rajoutant un élément.

Pour rajouter des éléments, envoyez un message LB_ADDSTRING à la zone de liste. Pour supprimer des éléments, envoyez LB_GETCURSEL pour obtenir l'index de la sélection actuelle, puis LB_DELETESTRING pour supprimer l'élément. L'exemple envoie également LB_GETCOUNT et utilise la valeur retournée pour mettre à jour l'affichage qui montre le nombre d'éléments. Ces deux instances de SendMessage utilisent l'une des déclarations PInvoke traitées dans la section précédente.

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

Lorsque l'utilisateur sélectionne un élément ou modifie sa sélection, le contrôle notifie la fenêtre hôte en lui envoyant un message WM_COMMAND, qui déclenche l'événement MessageHook pour la page. Le gestionnaire reçoit les mêmes informations que la procédure de fenêtre principale de la fenêtre hôte. Il passe également une référence à une valeur booléenne, handled. Vous définissez handled à true pour indiquer que vous avez géré le message et qu'aucun traitement supplémentaire n'est requis.

WM_COMMAND est envoyé pour diverses raisons, de sorte que vous devez examiner l'ID de notification pour déterminer s'il s'agit d'un événement que vous souhaitez gérer. L'ID est contenu dans le mot de poids fort du paramètre wParam. Comme Microsoft .NET n'a pas de macro HIWORD, l'exemple utilise des opérateurs au niveau du bit pour extraire l'ID. Si l'utilisateur a fait ou modifié sa sélection, l'ID sera LBN_SELCHANGE.

Lorsque LBN_SELCHANGE est reçu, l'exemple récupère l'index de l'élément sélectionné en envoyant un message LB_GETCURSEL au contrôle. Pour obtenir le texte, vous devez d'abord créer un StringBuilder. Vous envoyez ensuite un message LB_GETTEXT au contrôle. Passez l'objet StringBuilder vide comme paramètre wParam. Lorsque SendMessage est retourné, le StringBuilder contiendra le texte de l'élément sélectionné. Cette utilisation de SendMessage requiert encore une autre déclaration PInvoke.

Pour finir, affectez à handled la valeur true pour indiquer que le message a été géré. Le code suivant fait de nouveau ressortir la méthode ControlMsgFilter, qui est celle où ce comportement est implémenté.

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

Voir aussi

Concepts

Vue d'ensemble de l'interopérabilité WPF et Win32

Mise en route de Windows Presentation Foundation

Référence

HwndHost