Пошаговое руководство. Размещение элемента управления Win32 в WPF
Windows Presentation Foundation (WPF) предоставляет среду с широкими возможностями для создания приложений. Тем не менее, при наличии значительных инвестиций в коде Win32, возможно, более эффективно использовать по крайней мере некоторые этого кода в приложении WPF, а не переписывать его заново. WPF предоставляет простой механизм для размещения окна Win32, на странице WPF.
В этом разделе рассматриваются приложения, размещения элемента управления ListBox Win32 в WPF, что узлы управления полем со списком Win32. Эта общая процедура может быть расширена для размещения любого окна Win32.
Требования
В этом разделе предполагается Знакомство с программированием для WPF и Windows API. Основные сведения о программировании WPF, см. в разделе Приступая к работе. Введение в программирование Windows API, см. в разделе любой из многочисленных книг по этой теме, в частности программирования Windows Чарльза Петцольда.
Поскольку пример, используемый в этом разделе, реализован в C#, он позволяет использовать службы вызова платформы (PInvoke) для доступа к Windows API. Знакомство с PInvoke, желательно, но не обязательно.
Note
Этот учебник включает ряд примеров кода из связанного примера. Однако для удобства чтения он не содержит полный пример кода. Можно получить или просмотреть полный код из размещения элемента управления ListBox Win32 в WPF.
Основная процедура
В этом разделе рассматривается основная процедура размещения окна Win32 на странице WPF. В остальных разделах подробно описывается каждый шаг.
Основная процедура размещения
Реализация страницы WPF для размещения окна. Один из способов — создание Border элемент резервирующего раздел страницы для размещенного окна.
Реализация класса для размещения элемента управления, который наследует от HwndHost.
В этом классе переопределите HwndHost член класса BuildWindowCore.
Создайте размещенного окна в качестве дочернего окна, содержащего страницу WPF. Несмотря на то, что применяется в программировании WPF не требуется явно использовать, размещающая страница представляет собой окно с маркером (HWND). Вы получаете страницу HWND через
hwndParent
параметр BuildWindowCore метод. Размещаемое окно необходимо создать в качестве дочернего объекта этого окна HWND.Создав главное окно, верните HWND размещаемого окна. Если вы хотите разместить один или несколько элементов управления Win32, обычно создать главное окно как дочерний элемент HWND и дочерние элементы управления главного окна. Размещение элементов управления в главном окне предоставляет простой способ для страницы WPF для получения уведомлений от элементов управления, которое обрабатывает некоторые конкретные проблемы Win32 с уведомлениями за пределы HWND.
Обрабатывайте некоторые сообщения, которые отправляются в главное окно, например уведомления от дочерних элементов управления. Это можно сделать двумя способами.
Если вы предпочитаете обрабатывать сообщения в классе размещения, переопределите WndProc метод HwndHost класса.
Если вы предпочитаете заставить WPF обрабатывать сообщения, обрабатывать HwndHost класс MessageHook событие в фоновом коде. Это событие возникает для каждого сообщения, получаемого размещаемым окном. Если выбран этот параметр, необходимо переопределить WndProc, но требуется минимальная реализация.
Переопределить DestroyWindowCore и WndProc методы HwndHost. Необходимо переопределить эти методы для удовлетворения HwndHost контракт, но вам может потребоваться лишь минимальная реализация.
В файле кода программной части создайте экземпляр класса, размещающего элемент управления и дочернего элемента Border элемент, который предназначен для размещения окна.
Обмениваться данными с размещенным окном, отправляя ему Microsoft Windows сообщения и обрабатывая сообщения из его дочерних окон, таких как уведомления, отправленные элементами управления.
Реализация макета страницы
Макет для страницы WPF, на котором размещается элемент управления ListBox состоит из двух регионов. В левой части страницы содержит несколько элементов управления WPF, которые предоставляют UI , позволяющий работать с элементом управления Win32. В правом верхнем углу страницы имеется квадратная область для размещенного элемента управления ListBox.
Код для реализации этого макета довольно прост. Корневой элемент — DockPanel , имеет два дочерних элемента. Во-первых, Border элемент, на котором размещается элемент управления ListBox. Он занимает квадрат размером 200 x 200 в верхнем правом углу страницы. Второй — StackPanel элемент, который содержит набор элементов управления WPF, отображающих сведения и позволяют работать с элементом управления ListBox, задав предоставленные свойства взаимодействия. Для каждого из элементов, являющихся потомками StackPanel, справочные материалы для различных элементов, о том, что эти элементы и их назначение, перечислены в следующем примере кода, но не будут описаны здесь (basic модели взаимодействия не требуются, они предоставлены для добавления интерактивности в пример).
<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. Конструктор принимает два параметра, высоту и ширину, которые соответствуют высоте и ширине Border элемент, на котором размещается элемент управления ListBox. Эти значения используются Далее, чтобы убедиться, что размер элемента управления соответствует Border элемент.
public class ControlHost : HwndHost
{
IntPtr hwndControl;
IntPtr hwndHost;
int hostHeight, hostWidth;
public ControlHost(double height, double width)
{
hostHeight = (int)height;
hostWidth = (int)width;
}
Кроме того, имеется набор констант. Эти константы в основном берутся из файла 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;
Переопределение класса BuildWindowCore для создания окна Microsoft Win32
Можно переопределить этот метод для создания окна Win32, которое будет размещено на странице и создайте подключение между окном и страницей. Поскольку в этом примере также показано размещение элемента управления ListBox, создаются два окна. Первый — это окно, которое фактически размещено на странице WPF. Элемент управления ListBox создается в качестве дочернего элемента этого окна.
Это делается для того, чтобы упростить получение уведомлений от элемента управления. HwndHost Класс позволяет обрабатывать сообщения, отправляемые в окно, на котором он размещен. Если напрямую управлять узла Win32, вы получите сообщения, отправленные внутренний цикл сообщений элемента управления. Можно отображать элемент управления и отправлять ему сообщения, но не получать уведомления, которые элемент управления отправляет своему родительскому окну. Это означает, среди прочего, что нет способа обнаружения взаимодействия пользователя с элементом управления. Вместо этого следует создать главное окно и сделать элемент управления дочерним элементом этого окна. Это позволит обрабатывать сообщения для главного окна, включая уведомления, отправляемые элементом управления. Для удобства, поскольку главное окно — это всего лишь программа-оболочка для элемента управления, мы будем называть этот пакет "элементом управления ListBox".
Создание главного окна и элемента управления ListBox
Можно использовать PInvoke для создания главного окна для элемента управления путем создания и регистрации класса окна и т. д. Однако гораздо проще создать окно с помощью готового "статического" класса окон. Это позволит реализовать в окне процедуру, необходимую для получения уведомлений из элемента управления, и требует минимальной работы с кодом.
HWND элемента управления предоставляется через доступное только для чтения свойство, чтобы главная страница могла использовать его для отправки сообщения элементу управления.
public IntPtr hwndListBox
{
get { return hwndControl; }
}
Элемент управления 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);
}
//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);
Реализация классов DestroyWindow и WndProc
В дополнение к BuildWindowCore, необходимо также переопределить WndProc и DestroyWindowCore методы HwndHost. В этом примере сообщения для элемента управления обрабатываются MessageHook обработчик, поэтому реализация WndProc и DestroyWindowCore сводится к минимуму. В случае использования 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);
}
[DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)]
internal static extern bool DestroyWindow(IntPtr hwnd);
Размещение элемента управления на странице
Чтобы разместить элемент управления на странице, сначала создайте новый экземпляр класса ControlHost
класса. Передайте значения высоты и ширины пограничного элемента, который содержит элемент управления (ControlHostElement
) для ControlHost
конструктор. Это позволит гарантировать, что ListBox имеет правильный размер. Затем необходимо разместить элемент управления на странице, назначив ControlHost
объект Child свойство узла Border.
Образец присоединяет обработчик к MessageHook событие ControlHost
для получения сообщений из элемента управления. Это событие вызывается для каждого сообщения, отправляемого в главное окно. В данном случае это сообщения, отправленные в окно, которое служит оболочкой текущего элемента управления ListBox, включая уведомления от элемента управления. В этом примере вызывается метод SendMessage, чтобы получать информацию от элемента управления и менять его содержимое. Подробные сведения о том, как страница обменивается данными с элементом управления, рассматриваются в следующем разделе.
Note
Обратите внимание на то, что существует два объявления PInvoke для класса SendMessage. Это необходимо, поскольку один использует 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();
}
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);
Обмен данными между элементом управления и страницей
Управление элементом управления, отправляя ему сообщения Windows. Элемент управления уведомляет вас, когда пользователь взаимодействует с ним, отправляя уведомления в главное окно. Размещения элемента управления ListBox Win32 в WPF пример включает в себя пользовательский Интерфейс, который содержит несколько примеров того, как это работает:
Добавить элемент в список
Удалить выбранный элемент из списка
Отобразить текст выбранного в настоящее время элемента
Отобразить число элементов в списке
Пользователь может также выбрать элемент в поле со списком, щелкнув его, так же, как и для — в стандартном приложении 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();
}
Когда пользователь выбирает элемент, или изменяет их выделение, элемент управления уведомляет главное окно, отправляя ему WM_COMMAND
сообщение, который вызывает MessageHook событий для страницы. Обработчик получает те же сведения, что и процедура главного окна в главном окне. Он также передает ссылку на логическое значение, равное handled
. Можно задать handled
для true
для указания, что сообщение было обработано и дальнейшая обработка не требуется.
WM_COMMAND
отправляется по различным причинам, поэтому следует проанализировать идентификатор уведомления, чтобы определить, является ли это событие, которое вы хотите обрабатывать. Этот идентификатор содержится в старшем слове wParam
параметра. Пример использует побитовые операторы для получения идентификатора. Если пользователь сделать выбор или изменил его, идентификатор будет LBN_SELCHANGE
.
Когда LBN_SELCHANGE
— получено, пример кода также получает индекс выбранного элемента путем отправки элемента управления LB_GETCURSEL
сообщение. Чтобы получить текст, сначала нужно создать StringBuilder. Затем следует отправить элемент управления LB_GETTEXT
сообщение. Передайте пустой StringBuilder объекта в виде wParam
параметра. Когда SendMessage
возвращении StringBuilder будет содержать текст выбранного элемента. Такое использование SendMessage
требует объявления еще один PInvoke.
Наконец, установите handled
для true
для указания, что сообщение было обработано.