演练:在 WPF 中承载 Win32 控件

Windows Presentation Foundation (WPF) 提供了用于创建应用程序的丰富环境。 但是,当你对 Win32 代码有大量投入时,在 WPF 应用程序中重复使用至少某些代码(而不是彻底替代)可能更有效。 WPF 提供了一个简单的机制,用于在 WPF 页面上承载 Win32 窗口。

本主题介绍用于承载 Win32 列表框控件的应用程序,即在 WPF 中承载 Win32 ListBox 控件示例。 可将此常规步骤扩展到承载任何 Win32 窗口。

要求

本主题假定你已基本熟悉 WPF 和 Windows API 编程。 有关 WPF 编程的基本介绍,请参阅入门。 有关 Windows API 编程的介绍,请参考有关该主题的众多书籍,尤其是 Charles Petzold 所著的“Programming Windows”

由于此主题随附的示例是用 C# 实现的,因此它使用 Platform Invocation Services (PInvoke) 来访问 Windows API。 稍微熟悉 PInvoke 将有所帮助,但并非必备条件。

注意

本主题包括来自相关示例的一些代码示例。 但是,出于可读性考虑,不包括完整的示例代码。 若要获取或查看完整代码,请访问在 WPF 中承载 Win32 ListBox 控件示例

基本过程

本部分概述了在 WPF 页面中承载 Win32 窗口的基本步骤。 其余各节介绍了每个步骤的详细内容。

基本的承载步骤如下:

  1. 实现一个 WPF 页面以承载窗口。 一种方法是创建 Border 元素,以便为所承载的窗口保留该页的一部分。

  2. 实现一个类以承载继承自 HwndHost 的控件。

  3. 在该类中,替代 HwndHost 类成员 BuildWindowCore

  4. 将所承载的窗口创建为包含 WPF 页面的窗口的子窗口。 尽管传统的 WPF 编程不需要显式利用承载页,但需要指出的是,承载页是一个带有句柄 (HWND) 的窗口。 你通过 BuildWindowCore 方法的 hwndParent 参数接收页面 HWND。 应将所承载的窗口创建为此 HWND 的子窗口。

  5. 在创建宿主窗口之后,返回所承载的窗口的 HWND。 如果想要承载一个或多个 Win32 控件,通常需要将宿主窗口创建为该 HWND 的子窗口,并使这些控件成为该宿主窗口的子窗口。 通过将控件包装在宿主窗口中,可以提供一种让 WPF 页从这些控件接收通知的简单方式,该方式可以处理一些与跨 HWND 边界的通知有关的特定 Win32 问题。

  6. 处理发送到宿主窗口的选定消息,例如,来自子控件的通知。 可通过两种方式来执行此操作。

    • 如果希望在承载类中处理消息,可以替代 HwndHost 类的 WndProc 方法。

    • 如果希望让 WPF 处理消息,可以在代码隐藏中处理 HwndHostMessageHook 事件。 对于所承载的窗口收到的每条消息,都将发生此事件。 如果选择此选项,仍然必须替代 WndProc,但只需提供最小实现。

  7. 替代 HwndHostDestroyWindowCoreWndProc 方法。 必须替代这些方法才能履行 HwndHost 协议,但只需提供最小实现。

  8. 在代码隐藏文件中,创建控件承载类的一个实例,并使其成为用于承载窗口的 Border 元素的子元素。

  9. 通过向所承载的窗口发送 Microsoft Windows 消息以及处理来自其子窗口的消息(例如由控件发送的通知),与该窗口进行通信。

实现页面布局

承载 ListBox 控件的 WPF 页面的布局由两个区域组成。 页面左侧承载了多个 WPF 控件,这些控件提供了用户界面 (UI),使你可以操作 Win32 控件。 页面右上角具有一个正方形区域,用于放置所承载的 ListBox 控件。

用于实现此操作的代码十分简单。 根元素是具有两个子元素的 DockPanel。 第一个是承载 ListBox 控件的 Border 元素。 它在该页的右上角占据了一个大小为 200x200 的正方形。 第二个是包含一组 WPF 控件的 StackPanel 元素,这些控件显示信息,并使你可以通过设置已公开的互操作属性来操作 ListBox 控件。 对于 StackPanel 的每个子元素,请参阅关于所用的各种元素的参考资料,了解关于这些元素是什么或者它们有哪些功能的详细信息。下面的代码示例中列出了这些元素,但这里不对其进行说明(基本互操作模型不需要它们中的任何一个,提供它们的目的是为该示例增加一些交互性)。

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

实现类以承载 Microsoft Win32 控件

此示例的核心是实际承载控件的类,即 ControlHost.cs。 它继承自 HwndHost。 构造函数接受两个参数,即 height 和 width,它们分别对应于承载 ListBox 控件的 Border 元素的高度和宽度。 这些值将在以后用于确保控件的大小与 Border 元素相匹配。

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

还有一组常量。 这些常量主要取自 Winuser.h,它们使你可以在调用 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;
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

替代 BuildWindowCore 以创建 Microsoft Win32 窗口

替代此方法以创建将由页面承载的 Win32 窗口,并在窗口与页面之间建立连接。 由于此示例涉及承载 ListBox 控件,因此将创建两个窗口。 第一个窗口是由 WPF 页面实际承载的窗口。 ListBox 控件被创建为该窗口的子窗口。

采用此方法的原因是为了简化接收来自控件的通知的过程。 HwndHost 类使你能够处理发送到它所承载的窗口中的消息。 如果直接承载 Win32 控件,你将收到发送到该控件内部消息循环的消息。 你可以显示控件并向其发送消息,但不会收到该控件发送到其父窗口的通知。 这意味你没有办法检测用户何时与该控件进行交互,以及其他更多方面。 可改为创建一个宿主窗口,并使控件成为该窗口的子窗口。 这样,你便能够处理宿主窗口的消息,包括由该控件发送给它的通知。 由于宿主窗口只是控件的一个简单包装器而已,为了方便起见,以后将把该包称为 ListBox 控件。

创建宿主窗口和 ListBox 控件

可以使用 PInvoke 通过创建和注册窗口类等来创建控件的宿主窗口。 但是,更简单的一种方法是使用预定义的“静态”窗口类创建窗口。 这将为你提供所需窗口步骤,以接收来自控件的通知,但需要完成最少的编码工作。

控件的 HWND 通过一个只读属性公开,以便宿主页面可使用它向控件发送消息。

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

ListBox 控件被创建为宿主窗口的子窗口。 两个窗口的高度和宽度被设置为传递给构造函数的值(前面已讨论)。 这可确保宿主窗口和控件的大小与页面上的保留区域相同。 在创建窗口之后,该示例将返回一个 HandleRef 对象,其中包含宿主窗口的 HWND。

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

实现 DestroyWindow 和 WndProc

BuildWindowCore 外,还必须替代 HwndHostWndProcDestroyWindowCore 方法。 在本示例中,控件的消息由 MessageHook 处理程序处理,因此 WndProcDestroyWindowCore 的实现是极少的。 在 WndProc 的情况下,将 handled 设为 false,以指示消息未处理,且返回 0。 对于 DestroyWindowCore,只需销毁该窗口。

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

在页面上承载控件

若要在页面上承载控件,首先需要创建 ControlHost 类的新实例。 将包含控件 (ControlHostElement) 的边框元素的高度和宽度传递给 ControlHost 构造函数。 这可确保 ListBox 具有正确的大小。 然后通过将 ControlHost 对象分配给主机 BorderChild 属性来承载页面上的控件。

此示例将一个处理程序附加到 ControlHostMessageHook 事件,以接收来自控件的消息。 对于每条发送到宿主窗口的消息,都将引发此事件。 在这种情况下,这些消息是发送到包装实际 ListBox 控件的窗口的消息,其中包括来自控件的通知。 此示例将调用 SendMessage 以从控件获取信息并修改其内容。 下一节将详细介绍页面如何与控件通信。

注意

请注意,SendMessage 有两个 PInvoke 声明。 这是必需的,因为其中一个声明使用 wParam 参数传递字符串,而另一个声明使用该参数传递整数。 对于每个签名,都需要一个单独的声明,以确保正确封送数据。

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

在控件和页面之间的实现通信

可以通过向控件发送 Windows 消息来操作控件。 当用户与控件交互时,控件会通过向其宿主窗口发送通知来通知你。 在 WPF 中承载 Win32 ListBox 控件示例包含一个 UI,其中提供了演示这一机制的工作方式的几个示例:

  • 向列表中追加项。

  • 从列表中删除选定项

  • 显示当前选定项的文本。

  • 显示列表中的项数。

还可以通过单击列表中的项来选择它,就像在常用 Win32 应用程序中一样。 每当用户通过选择、添加或追加项来更改列表框的状态时,都将更新所显示的数据。

若要追加项,请向列表框发送 LB_ADDSTRING 消息。 若要删除项,请发送 LB_GETCURSEL 获取当前选定项的索引,然后发送 LB_DELETESTRING 以删除项。 此示例还将发送 LB_GETCOUNT,并使用返回值更新显示项数的显示器。 SendMessage 的这两个实例都使用上一节中所述的 PInvoke 声明之一。

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

当用户选择项或更改其选择时,控件通过向承载窗口发送 WM_COMMAND 消息(这会引发页面的 MessageHook 事件),通知承载窗口。 处理程序将接收到与宿主窗口的主窗口过程相同的信息。 它还将传递对布尔值 handled 的引用。 将 handled 设为 true 可以指示你已经处理该消息并且无需进行其他处理。

发送 WM_COMMAND 的原因多种多样,因此必须检查通知 ID 以确定它是否是想要处理的事件。 该 ID 包含在 wParam 参数的高位字中。 示例使用按位运算符来提取 ID。 如果用户已选择或更改其选择,该 ID 将为 LBN_SELCHANGE

收到 LBN_SELCHANGE 后,该示例将通过向控件发送 LB_GETCURSEL 消息获取关于选定项的索引。 如需获取文本,首先创建 StringBuilder。 然后,向控件发送一条 LB_GETTEXT 消息。 将空 StringBuilder 对象作为 wParam 参数传递。 SendMessage 返回时,StringBuilder 将包含所选项的文本。 SendMessage 的这一用法还需要另一个 PInvoke 声明。

最后,将 handled 设为 true 以指示该消息已得到处理。

另请参阅