Freigeben über


Vorgehensweise: Hosten eines Win32-Steuerelements in WPF

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 eines Win32-Fensters in einer WPF-Seite.

Dieses Thema führt Sie durch eine Anwendung, Beispiel zum Hosten eines Win32-ListBox-Steuerelements in WPF, die ein Win32-Listenfeld-Steuerelement hostet. Dieses allgemeine Verfahren kann für das Hosten beliebiger Win32-Fenster erweitert werden.

Anforderungen

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

Da das in diesem Thema verwendete Beispiel in C# implementiert ist, verwendet es Platform Invocation Services (PInvoke) für den Zugriff auf die Windows API. Grundkenntnisse in PInvoke 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 unter Beispiel zum Hosten eines Win32-ListBox-Steuerelements in WPF ansehen oder von dort kopieren.

Das grundlegende Verfahren

Dieser Abschnitt umfasst das grundlegende Verfahren zum Hosten eines Win32-Fensters auf einer WPF-Seite. In den weiteren Abschnitten werden die einzelnen Schritte näher erläutert.

Die grundlegende Vorgehensweise beim Hosten ist:

  1. Implementieren einer WPF-Seite, die das Fenster hostet. Eine Möglichkeit ist das Erstellen eines Border-Elements, um einen Abschnitt der Seite für das zu hostende Fenster zu reservieren.

  2. Implementieren Sie eine Klasse, um das Steuerelement zu hosten, das von HwndHost erbt.

  3. Überschreiben Sie in dieser Klasse den Member BuildWindowCore der Klasse HwndHost.

  4. Erstellen Sie das gehostete Fenster als untergeordnetes Element des Fensters, das die WPF-Seite enthält. Obwohl in herkömmlicher WPF-Programmierung eine solch explizite Verwendung nicht erforderlich ist, ist die Hostseite ein Fenster mit einem Handle (HWND). Sie erhalten das HWND der Seite über die hwndParent-Parameter der BuildWindowCore-Methode. Das gehostete Fenster sollte als untergeordnetes Element dieses HWND erstellt werden.

  5. Sobald Sie das Hostfenster erstellt haben, geben Sie das HWND des gehosteten Fensters zurück. Wenn Sie eine oder mehrere Win32-Steuerelemente hosten möchten, erstellen Sie in der Regel ein Hostfenster als untergeordnetes Element des HWND und machen die Steuerelemente zu untergeordneten Elementen dieses Hostfensters. Das Einbinden von Steuerelementen in ein Hostfenster bietet eine einfache Möglichkeit, Benachrichtigungen der Steuerelemente an Ihre WPF-Seite zu übergeben und zugleich einige besondere Schwierigkeiten in Win32 bezüglich Benachrichtigungen über HWND-Grenzen hinweg zu umgehen.

  6. Behandeln Sie spezifische an das Hostfenster gesendete Nachrichten, z.B. Benachrichtigungen von untergeordneten Steuerelementen. Es gibt hierbei zwei Möglichkeiten.

    • Wenn Sie es bevorzugen, Nachrichten in ihrer Hostklasse zu behandeln, überschreiben Sie die WndProc-Methode der HwndHost-Klasse.

    • Wenn bevorzugen, dass WPF die Nachrichten handhabt, verwenden Sie das MessageHook-Ereignis der HwndHost-Klasse in ihrem CodeBehind. Dieses Ereignis tritt für jede Nachricht auf, die vom gehosteten Fenster empfangen wird. Wenn Sie diese Option auswählen, müssen Sie immer noch WndProc überschreiben, aber Sie benötigen nur eine minimale Implementierung.

  7. Überschreiben Sie die DestroyWindowCore-Methoden und WndProc-Methoden von HwndHost. Sie müssen diese Methoden überschreiben, um den HwndHost-Vertrag zu erfüllen, allerdings genügt eine minimale Implementierung.

  8. Erstellen Sie in der CodeBehind-Datei eine Instanz der Hostingklasse des Steuerelements, und machen Sie diese zu einem untergeordneten Element des Border-Elements, welches das Fenster hosten soll.

  9. Sie können mit dem gehosteten Fenster kommunizieren, indem Sie ihm Microsoft Windows-Nachrichten senden und aus dessen untergeordneten Fenstern gesendete Nachrichten, z.B. von Steuerelementen gesendete Benachrichtigungen, behandeln.

Implementieren des Seitenlayouts

Das Layout für die WPF-Seite, die das ListBox-Steuerelement hostet, besteht aus zwei Bereichen. Die linke Seite der Seite hostet mehrere WPF-Steuerelemente, die eine Benutzeroberfläche bereitstellen, die Ihnen gestattet, das Win32-Steuerelement anzupassen. In der oberen rechten Ecke der Seite befindet sich ein quadratischer Bereich für das gehostete ListBox-Steuerelement.

Der Code zum Implementieren dieses Layouts ist recht simpel. Das Stammelement ist ein DockPanel und verfügt über zwei untergeordnete Elemente. Das erste ist ein Border-Element, welches das ListBox-Steuerelement hostet. Es belegt einen 200x200 großen quadratischen Bereich in der oberen rechten Ecke der Seite. Das zweite ist ein StackPanel-Element, das einen Satz WPF-Steuerelemente enthält, die Informationen anzeigen und Interoperations-Eigenschaften verfügbar machen, mit deren Hilfe Sie das ListBox-Steuerelement anpassen können. Details über die Natur und Funktion der einzelnen untergeordneten Elemente des StackPanel können Sie dem jeweiligen Referenzmaterial entnehmen. Sie werden im nachfolgenden Beispielcode aufgeführt, aber an dieser Stelle nicht weiter erläutert (das grundlegende Interoperations-Modell benötigt keins dieser Elemente, sie sollen dem Beispiel lediglich mehr Interaktivität verleihen).

<Window
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://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>  

Implementieren einer Klasse zum Hosten des Microsoft Win32-Steuerelements

Der Kern dieses Beispiels ist die Klasse ControlHost.cs, die das Steuerelement tatsächlich hostet. Er erbt von HwndHost. Der Konstruktor akzeptiert zwei Parameter, Höhe und Breite, die der Höhe und Breite des Border-Elements, welches das ListBox-Steuerelement hostet, entsprechen. Diese Werte werden später verwendet, um sicherzustellen, dass die Größe des Steuerelements der des Border-Elements entspricht.

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

  public ControlHost(double height, double width)
  {
    hostHeight = (int)height;
    hostWidth = (int)width;
  }
Public Class ControlHost
    Inherits HwndHost
  Private hwndControl As IntPtr
  Private hwndHost As IntPtr
  Private hostHeight, hostWidth As Integer

  Public Sub New(ByVal height As Double, ByVal width As Double)
          hostHeight = CInt(height)
          hostWidth = CInt(width)
  End Sub

Des Weiteren gibt es auch einen Satz von Konstanten. Diese Konstanten sind zum größten Teil aus Winuser.h entlehnt, und ermöglichen Ihnen die Verwendung konventioneller Namen beim Aufrufen von Win32-Funktionen.

internal const int
  WS_CHILD = 0x40000000,
  WS_VISIBLE = 0x10000000,
  LBS_NOTIFY = 0x00000001,
  HOST_ID = 0x00000002,
  LISTBOX_ID = 0x00000001,
  WS_VSCROLL = 0x00200000,
  WS_BORDER = 0x00800000;
Friend Const WS_CHILD As Integer = &H40000000, WS_VISIBLE As Integer = &H10000000, LBS_NOTIFY As Integer = &H00000001, HOST_ID As Integer = &H00000002, LISTBOX_ID As Integer = &H00000001, WS_VSCROLL As Integer = &H00200000, WS_BORDER As Integer = &H00800000

Überschreiben von BuildWindowCore zum Erstellen des Microsoft Win32-Fensters

Überschreiben Sie diese Methode, um das von der Seite zu hostende Win32-Fenster zu erstellen und die Verbindung zwischen Fenster und Seite herzustellen. Da dieses Beispiel das Hosten eines ListBox-Steuerelements umfasst, werden zwei Fenster erstellt. Die erste ist das Fenster, das tatsächlich von der WPF-Seite gehostet wird. Das ListBox-Steuerelement wird als untergeordnetes Element dieses Fensters erstellt.

Dieser Ansatz wurde bewusst gewählt, um den Empfang von Benachrichtigungen aus dem Steuerelement zu vereinfachen. Die HwndHost-Klasse ermöglicht Ihnen die Verarbeitung von Nachrichten, die zum hostenden Fenster gesendet wurden. Wenn Sie ein Win32-Steuerelement direkt hosten, erhalten Sie die zur internen Nachrichtenschleife des Steuerelements gesendeten Nachrichten. Sie können das Steuerelement anzeigen und ihm Nachrichten senden, aber Sie erhalten keine der Benachrichtigungen, die das Steuerelement an das übergeordnete Fenster sendet. Dies bedeutet unter anderem, dass Sie keine Möglichkeit haben, zu erkennen, wann der Benutzer mit dem Steuerelement interagiert. Erstellen Sie stattdessen ein Hostfenster, und machen Sie das Steuerelement zu einem untergeordneten Element dieses Fensters. Dadurch können Sie sowohl Nachrichten für das Hostfenster als auch vom Steuerelement gesendete Benachrichtigungen verarbeiten. Da das Hostfenster kaum mehr als ein einfacher Wrapper für das Steuerelement ist, bezeichnen wir das Gesamtpaket der Einfachheit halber künftig als ListBox-Steuerelement.

Erstellen des Hostfensters und des ListBox-Steuerelements

Sie können PInvoke verwenden, um ein Hostfenster für das Steuerelement zu erstellen, indem Sie eine Fensterklasse erstellen und registrieren, und so weiter. Ein wesentlicher einfacherer Ansatz ist jedoch, ein Fenster mithilfe der vordefinierten „statischen“ Fensterklasse zu definieren. Dadurch erhalten Sie mit minimalem Codieraufwand die Fensterprozedur, die Sie benötigen, um Benachrichtigungen vom Steuerelement zu empfangen.

Das HWND des Steuerelements wird über eine schreibgeschützte Eigenschaft verfügbar gemacht, sodass es von der Hostseite zum Senden von Nachrichten an das Steuerelement verwendet werden kann.

public IntPtr hwndListBox
{
  get { return hwndControl; }
}
Public ReadOnly Property hwndListBox() As IntPtr
  Get
      Return hwndControl
  End Get
End Property

Das ListBox-Steuerelement wird als untergeordnetes Element des Hostfensters erstellt. Die Höhe und Breite beider Fenster werden wie oben erläutert auf die an den Konstruktor übergebenen Werte festgelegt. Dadurch wird sichergestellt, dass die Größe von Hostfenster und Steuerelement exakt dem auf der Seite reservierten Bereich entspricht. Nachdem die Fenster erstellt wurden, gibt das Beispiel ein HandleRef-Objekt zurück, welches das HWND des Hostfensters enthält.

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

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

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

  return new HandleRef(this, hwndHost);
}
Protected Overrides Function BuildWindowCore(ByVal hwndParent As HandleRef) As HandleRef
  hwndControl = IntPtr.Zero
  hwndHost = IntPtr.Zero

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

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

  Return New HandleRef(Me, hwndHost)
End Function
//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);
'PInvoke declarations
<DllImport("user32.dll", EntryPoint := "CreateWindowEx", CharSet := CharSet.Unicode)>
Friend Shared Function CreateWindowEx(ByVal dwExStyle As Integer, ByVal lpszClassName As String, ByVal lpszWindowName As String, ByVal style As Integer, ByVal x As Integer, ByVal y As Integer, ByVal width As Integer, ByVal height As Integer, ByVal hwndParent As IntPtr, ByVal hMenu As IntPtr, ByVal hInst As IntPtr, <MarshalAs(UnmanagedType.AsAny)> ByVal pvParam As Object) As IntPtr
End Function

Implementieren von DestroyWindow und WndProc

Zusätzlich zu BuildWindowCore müssen Sie auch die WndProc- und DestroyWindowCore- Methoden des HwndHost überschreiben. In diesem Beispiel werden die Nachrichten für das Steuerelement durch die MessageHook-Handler behandelt, wodurch sich die Implementierung von WndProc und DestroyWindowCore auf ein Minimum beschränkt. Im Fall von WndProc legen Sie handled auf false fest, um anzugeben, dass die Meldung nicht verarbeitet wurde und 0 zurückgegeben wird. Bei DestroyWindowCore zerstören Sie einfach das Fenster.

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);
}
Protected Overrides Function WndProc(ByVal hwnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr, ByRef handled As Boolean) As IntPtr
  handled = False
  Return IntPtr.Zero
End Function

Protected Overrides Sub DestroyWindowCore(ByVal hwnd As HandleRef)
  DestroyWindow(hwnd.Handle)
End Sub
[DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)]
internal static extern bool DestroyWindow(IntPtr hwnd);
<DllImport("user32.dll", EntryPoint := "DestroyWindow", CharSet := CharSet.Unicode)>
Friend Shared Function DestroyWindow(ByVal hwnd As IntPtr) As Boolean
End Function

Hosten des Steuerelements auf der Seite

Um das Steuerelement auf der Seite zu hosten, erstellen Sie zunächst eine neue Instanz der ControlHost-Klasse. Übergeben Sie die Höhe und Breite des Border-Elements, welches das Steuerelement enthält ( ControlHostElement), an den ControlHost-Konstruktor. Dadurch wird sichergestellt, dass das Listenfeld die richtige Größe hat. Sie können das Steuerelement auf der Seite zu hosten, indem Sie das ControlHost-Objekt der Child-Eigenschaft des Host-Border.

Das Beispiel fügt einen Ereignishandler für das MessageHook-Ereignis der ControlHost an, um Nachrichten vom Steuerelement zu empfangen. Dieses Ereignis wird für jede Nachricht ausgelöst, die an das gehostete Fenster gesendet wird. In diesem Fall sind dies die Nachrichten, die an das das eigentliche ListBox-Steuerelement umschließende Fenster gesendet werden, einschließlich der Benachrichtigungen aus dem Steuerelement. Das Beispiel ruft die SendMessage-Methode auf, um Informationen aus dem Steuerelement abzurufen und dessen Inhalt zu ändern. Wie die Seite im Detail mit dem Steuerelement kommuniziert, wird im nächsten Abschnitt erläutert.

Hinweis

Beachten Sie, dass es zwei PInvoke-Deklarationen für SendMessage gibt. Dies ist erforderlich, da eine den wParam-Parameter verwendet, um eine Zeichenfolge zu übergeben und die andere, um einen Integer zu übergeben. Um sicherzustellen, dass die Daten ordnungsgemäß gemarshallt werden, wird daher eine separate Deklaration für jede Signatur benötigt.

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();
}
Partial Public Class HostWindow
    Inherits Window
    Private selectedItem As Integer
    Private hwndListBox As IntPtr
    Private listControl As ControlHost
    Private app As Application
    Private myWindow As Window
    Private itemCount As Integer

    Private Sub On_UIReady(ByVal sender As Object, ByVal e As EventArgs)
        app = System.Windows.Application.Current
        myWindow = app.MainWindow
        myWindow.SizeToContent = SizeToContent.WidthAndHeight
        listControl = New ControlHost(ControlHostElement.ActualHeight, ControlHostElement.ActualWidth)
        ControlHostElement.Child = listControl
        AddHandler listControl.MessageHook, AddressOf ControlMsgFilter
        hwndListBox = listControl.hwndListBox
        For i As Integer = 0 To 14 'populate listbox
            Dim itemText As String = "Item" & i.ToString()
            SendMessage(hwndListBox, LB_ADDSTRING, IntPtr.Zero, itemText)
        Next i
        itemCount = SendMessage(hwndListBox, LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero)
        numItems.Text = "" & itemCount.ToString()
    End Sub

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

Private Function ControlMsgFilter(ByVal hwnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr, ByRef handled As Boolean) As IntPtr
    Dim textLength As Integer

    handled = False
    If msg = WM_COMMAND Then
        Select Case CUInt(wParam.ToInt32()) >> 16 And &HFFFF '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)
                Dim itemText As New StringBuilder()
                SendMessage(hwndListBox, LB_GETTEXT, selectedItem, itemText)
                selectedText.Text = itemText.ToString()
                handled = True
        End Select
    End If
    Return IntPtr.Zero
End Function
Friend Const LBN_SELCHANGE As Integer = &H1, WM_COMMAND As Integer = &H111, LB_GETCURSEL As Integer = &H188, LB_GETTEXTLEN As Integer = &H18A, LB_ADDSTRING As Integer = &H180, LB_GETTEXT As Integer = &H189, LB_DELETESTRING As Integer = &H182, LB_GETCOUNT As Integer = &H18B

<DllImport("user32.dll", EntryPoint:="SendMessage", CharSet:=CharSet.Unicode)>
Friend Shared Function SendMessage(ByVal hwnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Integer
End Function

<DllImport("user32.dll", EntryPoint:="SendMessage", CharSet:=CharSet.Unicode)>
Friend Shared Function SendMessage(ByVal hwnd As IntPtr, ByVal msg As Integer, ByVal wParam As Integer, <MarshalAs(UnmanagedType.LPWStr)> ByVal lParam As StringBuilder) As Integer
End Function

<DllImport("user32.dll", EntryPoint:="SendMessage", CharSet:=CharSet.Unicode)>
Friend Shared Function SendMessage(ByVal hwnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, ByVal lParam As String) As IntPtr
End Function

Implementieren der Kommunikation zwischen dem Steuerelement und der Seite

Sie bearbeiten das Steuerelement, indem Sie ihm Windows-Nachrichten senden. Das Steuerelement benachrichtigt Sie, wenn der Benutzer durch Senden von Benachrichtigungen an das Hostfenster mit ihm interagiert. Das Beispiel Hosten eines Win32-ListBox-Steuerelements in WPF umfasst eine Benutzeroberfläche, die diese Funktionsweise in mehreren Beispielen veranschaulicht:

  • Der Liste ein Element hinzufügen.

  • Das markierte Element aus der Liste löschen

  • Den Text des aktuell markierten Elements anzeigen.

  • Die Anzahl der Elemente in der Liste anzeigen.

Der Benutzer kann im Listenfeld auch ein Element auswählen, indem er darauf klickt, genau wie bei einer herkömmlichen Win32-Anwendung. Die angezeigten Daten werden bei jeder Zustandsänderung des Listenfelds durch den Benutzer aktualisiert, egal ob ein Element ausgewählt, hinzugefügt oder angefügt wurde.

Um Elemente anzufügen, senden Sie dem Listenfeld eine LB_ADDSTRING-Nachricht. Um Elemente zu löschen, senden Sie zunächst LB_GETCURSEL, um den Index der aktuellen Auswahl abzurufen, und dann LB_DELETESTRING, um das Element zu löschen. Das Beispiel sendet auch LB_GETCOUNT, und nutzt den zurückgegebenen Wert zum Aktualisieren der Anzeige, die die Anzahl der Elemente angibt. Beide dieser Instanzen von SendMessage verwenden eine der PInvoke-Deklarationen, die im vorherigen Abschnitt erläutert wurden.

private void AppendText(object sender, EventArgs args)
{
  if (!string.IsNullOrEmpty(txtAppend.Text))
  {
    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();
}
Private Sub AppendText(ByVal sender As Object, ByVal args As EventArgs)
    If txtAppend.Text <> String.Empty Then
        SendMessage(hwndListBox, LB_ADDSTRING, IntPtr.Zero, txtAppend.Text)
    End If
    itemCount = SendMessage(hwndListBox, LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero)
    numItems.Text = "" & itemCount.ToString()
End Sub
Private Sub DeleteText(ByVal sender As Object, ByVal args As EventArgs)
    selectedItem = SendMessage(listControl.hwndListBox, LB_GETCURSEL, IntPtr.Zero, IntPtr.Zero)
    If selectedItem <> -1 Then 'check for selected item
        SendMessage(hwndListBox, LB_DELETESTRING, New IntPtr(selectedItem), IntPtr.Zero)
    End If
    itemCount = SendMessage(hwndListBox, LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero)
    numItems.Text = "" & itemCount.ToString()
End Sub

Sobald der Benutzer ein Element auswählt oder die bestehende Auswahl ändert, benachrichtigt das Steuerelement das Hostfenster durch Senden einer WM_COMMAND-Meldung, die das MessageHook-Ereignis für die Seite auslöst. Der Handler empfängt dieselben Informationen wie die Hauptfensterprozedur des Hostfensters. Er gibt zudem einen Verweis auf einen booleschen Wert, handled, weiter. Sie setzen den Wert für handled auf true, um anzugeben, dass die Meldung verarbeitet wurde und keine weitere Verarbeitung erforderlich ist.

WM_COMMAND wird aus verschiedensten Gründen gesendet, weshalb Sie die Benachrichtigungs-ID überprüfen müssen, um festzustellen, ob es sich um ein Ereignis handelt, das Sie behandeln möchten. Die ID ist im oberen Wort des wParam-Parameters enthalten. Im Beispiel werden bitweise Operatoren, um die ID zu extrahieren. Wenn der Benutzer eine Auswahl vorgenommen oder geändert hat, wird die LBN_SELCHANGE sein.

Wenn LBN_SELCHANGE empfangen wird, ruft das Beispiel den Index des ausgewählten Elements ab, indem es eine LB_GETCURSEL-Nachricht an das Steuerelements sendet. Zum Abrufen des Texts erstellen Sie zuerst einen StringBuilder. Anschließend senden Sie eine LB_GETTEXT-Nachricht an das Steuerelement. Übergeben Sie das leere StringBuilder-Objekt als wParam-Parameter. Wenn SendMessage zurückgegeben wird, enthält der StringBuilder den Text des ausgewählten Elements. Diese Verwendung von SendMessage erfordert eine weitere PInvoke-Deklaration.

Setzen Sie abschließend den Wert von handled auf true, um anzugeben, dass die Meldung verarbeitet wurde.

Weitere Informationen