Udostępnij za pośrednictwem


Przewodnik: hostowanie kontrolki Win32 w WPF

Program Windows Presentation Foundation (WPF) udostępnia bogate środowisko do tworzenia aplikacji. Jednak jeśli masz znaczną inwestycję w kod Win32, bardziej skuteczne może być ponowne użycie co najmniej jednego kodu w aplikacji WPF, a nie ponowne zapisywanie go całkowicie. WPF zapewnia prosty mechanizm hostowania okna Win32 na stronie WPF.

W tym temacie opisano aplikację, hostowanie kontrolki Win32 ListBox w przykładowymWPF, które hostuje kontrolkę pola listy Win32. Tę ogólną procedurę można rozszerzyć na hostowanie dowolnego okna Win32.

Wymagania

W tym temacie założono podstawową znajomość programowania zarówno WPF, jak i interfejsu API systemu Windows. Aby zapoznać się z podstawowym wprowadzeniem do programowania WPF, zobacz Wprowadzenie. Aby zapoznać się z programowaniem interfejsu API systemu Windows, zobacz dowolną z licznych książek na ten temat, w szczególności Programming Windows autorstwa Charlesa Petzolda.

Ponieważ przykład dołączony do tego tematu jest implementowany w języku C#, korzysta z usług Invocation Services (PInvoke) platformy w celu uzyskania dostępu do interfejsu API systemu Windows. Znajomość funkcji PInvoke jest pomocna, ale nie niezbędna.

Uwaga / Notatka

Ten temat zawiera szereg przykładów kodu ze skojarzonego przykładu. Jednak w celu zapewnienia czytelności nie zawiera kompletnego przykładowego kodu. Możesz uzyskać lub wyświetlić pełny kod z hostowanie kontrolki Win32 ListBox w przykładzie WPF.

Procedura podstawowa

W tej sekcji opisano podstawową procedurę hostowania okna Win32 na stronie WPF. Pozostałe sekcje zawierają szczegółowe informacje o poszczególnych krokach.

Podstawowa procedura hostingu to:

  1. Zaimplementuj stronę WPF do hostowania okna. Jedną z technik jest utworzenie elementu Border w celu zarezerwowania sekcji strony dla hostowanego okna.

  2. Zaimplementuj klasę do hostowania kontrolki dziedziczonej z HwndHost.

  3. W tej klasie, przesłoń element składowy HwndHostklasy BuildWindowCore.

  4. Utwórz hostowane okno jako element podrzędny okna zawierającego stronę WPF. Chociaż konwencjonalne programowanie WPF nie musi jawnie korzystać z niego, strona hostingu jest oknem z uchwytem (HWND). Otrzymujesz stronę HWND za pośrednictwem parametru hwndParent metody BuildWindowCore. Hostowane okno powinno zostać utworzone jako element podrzędny tego HWND.

  5. Po utworzeniu okna hosta zwróć wartość HWND okna hostowanego. Jeśli chcesz hostować co najmniej jedną kontrolkę Win32, zazwyczaj utworzysz okno hosta jako podrzędne do HWND i sprawisz, że kontrolki będą dziećmi tego okna hosta. Opakowanie kontrolek w oknie hosta zapewnia prosty sposób, w jaki strona WPF może odbierać powiadomienia z kontrolek, co rozwiązuje niektóre konkretne problemy Win32 z powiadomieniami przez granicę HWND.

  6. Obsługa wybranych komunikatów wysyłanych do okna hosta, takich jak powiadomienia z kontrolek podrzędnych. Istnieją dwa sposoby, aby to zrobić.

    • Jeśli wolisz obsługiwać komunikaty w klasie hostingu, przesłoń metodę WndProc klasy HwndHost.

    • Jeśli wolisz obsługiwać komunikaty przez platformę WPF, obsłuż zdarzenie HwndHost klasy MessageHook w kodzie. To zdarzenie występuje dla każdego komunikatu otrzymanego przez hostowane okno. Jeśli wybierzesz tę opcję, nadal musisz zastąpić WndProc, ale potrzebujesz tylko minimalnej implementacji.

  7. Zastąpij metody DestroyWindowCore i WndProcHwndHost. Należy zastąpić te metody, aby spełnić wymagania umowy HwndHost, ale może być konieczne tylko zapewnienie minimalnej implementacji.

  8. W pliku związanego z kodem utwórz wystąpienie klasy hostującej kontrolkę i ustaw je jako element podrzędny elementu Border przeznaczonego do hostowania okna.

  9. Komunikowanie się z oknem hostowanym przez wysłanie do niego komunikatów systemu Microsoft Windows i obsługę komunikatów z okien podrzędnych, takich jak powiadomienia wysyłane przez kontrolki.

Implementowanie układu strony

Układ strony WPF hostujący kontrolkę ListBox składa się z dwóch regionów. Po lewej stronie znajduje się kilka kontrolek WPF, które udostępniają interfejs użytkownika, który umożliwia manipulowanie kontrolką Win32. Prawy górny róg strony ma region kwadratowy dla hostowanej kontrolki ListBox.

Kod do zaimplementowania tego układu jest dość prosty. Element główny jest DockPanel, który ma dwa elementy podrzędne. Pierwszy to element Border hostujący kontrolkę ListBox. Zajmuje 200x200 kwadrat w prawym górnym rogu strony. Drugi to element StackPanel zawierający zestaw kontrolek WPF, które wyświetlają informacje i umożliwiają manipulowanie kontrolką ListBox przez ustawienie uwidocznionych właściwości współdziałania. Dla każdego z elementów podrzędnych StackPanel, sprawdź materiał referencyjny dotyczący różnych elementów użytych do uzyskania szczegółowych informacji o tym, czym są te elementy lub jakie mają zastosowanie. Elementy te są wymienione w poniższym przykładowym kodzie, ale nie zostaną tutaj wyjaśnione. Podstawowy model współdziałania nie wymaga ich użycia; są one udostępniane, aby dodać pewną interaktywność do przykładu.

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

Implementowanie klasy do hostowania kontrolki Microsoft Win32

Rdzeniem tego przykładu jest klasa, która faktycznie hostuje kontrolkę, ControlHost.cs. Dziedziczy z HwndHost. Konstruktor przyjmuje dwa parametry, wysokość i szerokość, które odpowiadają wysokości i szerokości elementu Border hostujące kontrolkę ListBox. Te wartości są używane później, aby upewnić się, że rozmiar kontrolki jest zgodny z elementem 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

Istnieje również zestaw stałych. Te stałe są w dużej mierze pobierane z winuser.h i umożliwiają używanie konwencjonalnych nazw podczas wywoływania funkcji 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

Nadpisz BuildWindowCore, aby utworzyć okno Microsoft Win32

Zastąpisz tę metodę, aby utworzyć okno Win32, które będzie hostowane przez stronę, i nawiązać połączenie między oknem a stroną. Ponieważ ten przykład obejmuje hostowanie kontrolki ListBox, tworzone są dwa okna. Pierwszy to okno, które jest rzeczywiście hostowane przez stronę WPF. Kontrolka ListBox jest tworzona jako element podrzędny tego okna.

Przyczyną tego podejścia jest uproszczenie procesu odbierania powiadomień z kontrolki. Klasa HwndHost umożliwia przetwarzanie komunikatów wysyłanych do okna, które hostuje. W przypadku bezpośredniego hostowania kontrolki Win32 otrzymujesz komunikaty wysyłane do wewnętrznej pętli komunikatów tej kontrolki. Kontrolkę można wyświetlić i wysłać do niej komunikaty, ale nie otrzymasz powiadomień wysyłanych przez kontrolkę do okna nadrzędnego. Oznacza to między innymi, że nie ma możliwości wykrywania, gdy użytkownik wchodzi w interakcję z kontrolką. Zamiast tego utwórz okno hosta i ustaw kontrolkę jako element podrzędny tego okna. Dzięki temu można przetwarzać komunikaty dla okna hosta, w tym powiadomienia wysyłane do niego przez kontrolkę. Dla wygody, ponieważ okno hosta jest czymś więcej niż tylko prostą otoczką dla kontrolki, będziemy odnosić się do niej jako do kontrolki ListBox.

Tworzenie okna hosta i kontrolki ListBox

Możesz użyć funkcji PInvoke, aby utworzyć okno hosta dla kontrolki, tworząc i rejestrując klasę okien itd. Jednak znacznie prostszą metodą jest utworzenie okna ze wstępnie zdefiniowaną klasą okien "statycznych". Zapewnia to procedurę okna wymaganą do odbierania powiadomień z kontrolki i wymaga minimalnej ilości kodu.

HWND kontrolki jest uwidoczniona za pośrednictwem właściwości tylko do odczytu, tak aby strona hosta mogła jej używać do wysyłania komunikatów do kontrolki.

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

Kontrolka ListBox jest tworzona jako element podrzędny okna hosta. Wysokość i szerokość obu okien są ustawione na wartości przekazane do konstruktora, omówione powyżej. Dzięki temu rozmiar okna hosta i kontrolki są identyczne z obszarem zarezerwowanym na stronie. Po utworzeniu okien, przykładowa aplikacja zwraca obiekt HandleRef zawierający HWND okna hosta.

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

Implementowanie systemu DestroyWindow i WndProc

Oprócz BuildWindowCore, należy również przesłonić metody WndProc i DestroyWindowCore w klasie HwndHost. W tym przykładzie komunikaty dla kontrolki są obsługiwane przez program obsługi MessageHook, dlatego implementacja WndProc i DestroyWindowCore jest minimalna. W przypadku WndProcustaw handled na false, aby wskazać, że komunikat nie został obsłużony i zwróć 0. W przypadku DestroyWindowCorepo prostu zniszczyć okno.

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

Hostowanie kontrolki na stronie

Aby hostować kontrolkę na stronie, należy najpierw utworzyć nowe wystąpienie klasy ControlHost. Przekaż wysokość i szerokość elementu obramowania zawierającego kontrolkę (ControlHostElement) do konstruktora ControlHost. Dzięki temu pole ListBox ma prawidłowy rozmiar. Następnie należy umieścić kontrolkę na stronie, przypisując obiekt ControlHost do właściwości Child hosta Border.

Przykład dołącza procedurę obsługi zdarzeń do zdarzenia MessageHook obiektu ControlHost, aby odbierać komunikaty z komponentu sterującego. To zdarzenie jest zgłaszane dla każdego komunikatu wysyłanego do hostowanego okna. W takim przypadku są to komunikaty wysyłane do okna, które opakowuje rzeczywistą kontrolkę ListBox, w tym powiadomienia z kontrolki. Przykład wywołuje funkcję SendMessage, aby uzyskać informacje z kontrolki i zmodyfikować jej zawartość. Szczegółowe informacje o tym, jak strona komunikuje się z kontrolką, zostały omówione w następnej sekcji.

Uwaga / Notatka

Zwróć uwagę, że istnieją dwie deklaracje PInvoke dla funkcji SendMessage. Jest to konieczne, ponieważ jeden używa parametru wParam do przekazania ciągu, a drugi używa go do przekazania liczby całkowitej. Potrzebna jest osobna deklaracja dla każdego podpisu, aby upewnić się, że dane są prawidłowo przesyłane.

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

Implementowanie komunikacji między kontrolką a stroną

Kontrolkę można manipulować, wysyłając komunikaty systemu Windows. Kontrolka powiadamia o tym, kiedy użytkownik wchodzi z nim w interakcję, wysyłając powiadomienia do okna hosta. Przykład dotyczący hostowania kontrolki ListBox Win32 w środowisku WPF zawiera interfejs użytkownika, który przedstawia kilka przykładów działania tej funkcji:

  • Dołącz element do listy.

  • Usuń wybrany element z listy

  • Wyświetl tekst aktualnie wybranego elementu.

  • Wyświetl liczbę elementów na liście.

Użytkownik może również wybrać element w polu listy, klikając go tak samo jak w przypadku konwencjonalnej aplikacji Win32. Wyświetlane dane są aktualizowane za każdym razem, gdy użytkownik zmienia stan pola listy, wybierając, dodając lub dołączając element.

Aby dołączyć elementy, wyślij do pola listy komunikat LB_ADDSTRING. Aby usunąć elementy, wyślij LB_GETCURSEL, aby pobrać indeks bieżącego zaznaczenia, a następnie wyślij LB_DELETESTRING, aby usunąć element. Przykład wysyła również LB_GETCOUNTi używa zwróconej wartości do zaktualizowania ekranu, który pokazuje liczbę elementów. Oba te wystąpienia SendMessage używają jednej z deklaracji PInvoke omówionych w poprzedniej sekcji.

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

Gdy użytkownik wybierze element lub zmieni wybór, kontrolka powiadamia okno hosta, wysyłając mu komunikat WM_COMMAND, który zgłasza zdarzenie MessageHook dla strony. Procedura obsługi otrzymuje te same informacje co główna procedura okna hosta. Przekazuje również odwołanie do wartości logicznej handled. Ustaw handled na true, aby wskazać, że komunikat został obsłużony i nie jest potrzebne żadne dalsze przetwarzanie.

WM_COMMAND jest wysyłany z różnych powodów, dlatego należy sprawdzić identyfikator powiadomienia, aby określić, czy jest to zdarzenie, które chcesz obsłużyć. Identyfikator jest zawarty w wysokim słowie parametru wParam. W przykładzie użyto operatorów bitowych do wyodrębnienia identyfikatora. Jeśli użytkownik dokonał wyboru lub zmienił jego wybór, identyfikator będzie LBN_SELCHANGE.

Po odebraniu LBN_SELCHANGE przykład uzyskuje indeks wybranego elementu poprzez wysłanie do kontroli polecenia LB_GETCURSEL. Aby uzyskać tekst, należy najpierw utworzyć StringBuilder. Następnie należy wysłać kontrolkę wiadomości LB_GETTEXT. Przekaż pusty obiekt StringBuilder jako parametr wParam. Gdy SendMessage wróci, StringBuilder będzie zawierać tekst wybranego elementu. Użycie SendMessage wymaga jeszcze innej deklaracji PInvoke.

Na koniec ustaw handled na true, aby wskazać, że komunikat został obsłużony.

Zobacz także