연습: WPF에서 Win32 컨트롤 호스트

WPF(Windows Presentation Foundation)는 애플리케이션을 만들기 위한 풍부한 환경을 제공합니다. 그러나 Win32 코드에 상당한 투자를 한 경우 코드를 완전히 다시 작성하는 대신 WPF 애플리케이션에서 해당 코드의 일부를 다시 사용하는 것이 더 효과적일 수 있습니다. WPF는 WPF 페이지에서 Win32 창을 호스트하는 간단한 메커니즘을 제공합니다.

이 항목에서는 Win32 목록 상자 컨트롤을 호스트하는 애플리케이션인 WPF에서 Win32 ListBox 컨트롤 호스팅 샘플을 안내합니다. 이 일반적인 절차를 모든 Win32 창 호스팅으로 확장할 수 있습니다.

요구 사항

이 항목에서는 WPF 및 Windows API 프로그래밍에 대한 기본 지식이 있다고 가정합니다. WPF 프로그래밍에 대한 기본적인 소개는 시작을 참조하세요. Windows API 프로그래밍 소개를 보려면 해당 주제와 관련된 많은 서적, 특히 Charles Petzold가 저술한 Programming Windows를 참조하세요.

이 항목과 관련된 샘플은 C#으로 구현되므로 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에서 메시지를 처리하도록 하려는 경우 코드 숨김에서 HwndHost 클래스 MessageHook 이벤트를 처리합니다. 이 이벤트는 호스트된 창에서 받은 모든 메시지에 대해 발생합니다. 이 옵션을 선택하는 경우 WndProc는 여전히 재정의해야 하지만 최소 구현만 필요합니다.

  7. HwndHostDestroyWindowCoreWndProc 메서드를 재정의합니다. HwndHost 계약을 충족하려면 이러한 메서드를 재정의해야 하지만 최소 구현만 제공하면 됩니다.

  8. 코드 숨김 파일에서 컨트롤 호스팅 클래스의 인스턴스를 만들고 창을 호스트하기 위한 Border 요소의 자식으로 설정합니다.

  9. 컨트롤에서 보낸 알림과 같은 Microsoft Windows 메시지를 전송하고 자식 창에서 메시지를 처리하여 호스트된 창과 통신합니다.

페이지 레이아웃 구현

ListBox 컨트롤을 호스트하는 WPF 페이지의 레이아웃은 두 개의 영역으로 구성됩니다. 페이지의 왼쪽은 Win32 컨트롤을 조작하는 데 사용할 수 있는 UI(사용자 인터페이스)를 제공하는 여러 WPF 컨트롤을 호스트합니다. 페이지의 오른쪽 위에는 호스트된 ListBox 컨트롤에 대한 사각형 영역이 있습니다.

이 레이아웃을 구현하는 코드는 매우 간단합니다. 루트 요소는 두 개의 자식 요소가 있는 DockPanel입니다. 첫 번째는 ListBox 컨트롤을 호스트하는 Border 요소입니다. 이 요소는 페이지의 오른쪽 위에 있는 200x200 정사각형을 사용합니다. 두 번째는 정보를 표시하고 노출된 상호 운용 속성을 설정하여 ListBox 컨트롤을 조작할 수 있는 WPF 컨트롤 세트가 포함된 StackPanel 요소입니다. 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에서 상속받습니다. 생성자는 높이와 너비의 두 가지 매개 변수를 사용하는데, 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 컨트롤은 호스트 창의 자식으로 만듭니다. 두 창의 너비와 높이는 위에서 설명한 대로 생성자에 전달된 값으로 설정됩니다. 이렇게 하면 호스트 창과 컨트롤의 크기가 페이지에서 예약된 영역과 동일해집니다. 창을 만든 후 샘플에서는 호스트 창의 HWND를 포함하는 HandleRef 개체를 반환합니다.

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의 경우 handledfalse로 설정하여 메시지가 처리되지 않았음을 나타내고 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 개체를 Border 호스트의 Child 속성에 할당하여 페이지에서 컨트롤을 호스트합니다.

샘플에서는 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

사용자가 항목을 선택하거나 해당 선택 영역을 변경하면 컨트롤은 페이지에 대해 MessageHook 이벤트를 발생시키는 WM_COMMAND 메시지를 보내 호스트 창에 알립니다. 처리기는 호스트 창의 주 창 프로시저와 같은 정보를 수신합니다. 또한 부울 값 handled에 대한 참조를 전달합니다. handledtrue로 설정하여 메시지를 처리했으며 추가 처리가 필요하지 않음을 나타냅니다.

WM_COMMAND는 다양한 이유로 전송되므로 알림 ID를 검사하여 처리하려는 이벤트인지 여부를 확인해야 합니다. ID는 wParam 매개 변수의 상위 단언에 포함되어 있습니다. 샘플에서는 비트 연산자를 사용하여 ID를 추출합니다. 사용자가 선택을 수행하거나 선택을 변경한 경우 ID는 LBN_SELCHANGE가 됩니다.

LBN_SELCHANGE를 받으면 샘플은 컨트롤에 LB_GETCURSEL 메시지를 전송하여 선택한 항목의 인덱스를 가져옵니다. 텍스트를 가져오려면 먼저 StringBuilder를 만듭니다. 그런 다음, 컨트롤에 LB_GETTEXT 메시지를 전송합니다. 빈 StringBuilder 개체를 wParam 매개 변수로 전달합니다. SendMessage가 반환되면 StringBuilder에는 선택한 항목의 텍스트가 포함됩니다. SendMessage를 이렇게 사용하려면 또 다른 PInvoke 선언이 필요합니다.

마지막으로 handledtrue로 설정하여 메시지가 처리되었음을 나타냅니다.

참고 항목