更新 : 2007 年 11 月
Windows Presentation Foundation (WPF) は、アプリケーションの作成に適した環境を提供します。ただし、Win32 コードに多くの投資を行った場合は、元のコードを全面的に記述し直すよりも、WPF アプリケーションのコードの少なくとも一部を再利用する方が効率的である場合があります。WPF には、WPF ページで Win32 ウィンドウをホストするための簡単な機構が用意されています。
このチュートリアルでは、「Windows Presentation Foundation での Win32 ListBox コントロールのホストのサンプル」に示されている、Win32 リスト ボックス コントロールをホストするアプリケーションについて説明します。この一般的な手順を拡張することにより、どの Win32 ウィンドウでもホストできます。
このトピックには次のセクションが含まれています。
- 要件
- 基本手順
- ページ レイアウトの実装
- Microsoft Win32 コントロールをホストするクラスの実装
- ページでのコントロールのホスト
- コントロールとページ間の通信の実装
- 関連トピック
要件
このチュートリアルは、WPF および Win32 のプログラミングに関する基本的な知識があることを前提としています。WPF プログラミングの概要については、「概要 (WPF)」を参照してください。Win32 プログラミングの概要については多数の書籍が出版されているので、それらを参照してください。特に、『プログラミング Windows』(Charles Petzold 著) が参考になります。
このチュートリアルに含まれるサンプルは C# で実装されるため、プラットフォーム呼び出しサービス (PInvoke) を利用して、Win32API にアクセスします。PInvoke の知識は役立ちますが、必須ではありません。
![]() |
---|
このチュートリアルには、関連するサンプルからのコード例が数多く含まれています。ただし、読みやすさのために、サンプル コード全体は含まれていません。コード全体については、「Windows Presentation Foundation での Win32 ListBox コントロールのホストのサンプル」を参照してください。 |
基本手順
ここでは、WPF ページで Win32 ウィンドウをホストするための基本手順を説明します。残りのセクションでは、各手順の詳細について説明します。
基本的なホスト手順は次のとおりです。
ウィンドウをホストする WPF ページを実装します。1 つの方法は、Border 要素を作成し、ホストされるウィンドウのページのセクションを予約することです。
HwndHost から継承するコントロールをホストするクラスを実装します。
そのクラスで、HwndHost クラス メンバの BuildWindowCore をオーバーライドします。
ホストされるウィンドウを、WPF ページを含むウィンドウの子として作成します。従来の WPF プログラミングでは明示的に利用する必要はありませんが、ホストするページは、ハンドル (HWND) を持つウィンドウです。ページの HWND は、BuildWindowCore メソッドの hwndParent パラメータを通じて受け取ります。ホストされるウィンドウは、この HWND の子として作成する必要があります。
ホスト ウィンドウを作成したら、ホストされるウィンドウの HWND を返します。1 つ以上の Win32 コントロールをホストする場合、通常は、ホスト ウィンドウを HWND の子として作成し、コントロールをそのホスト ウィンドウの子にします。複数のコントロールを 1 つのホスト ウィンドウにラップすると、WPF ページでそれらのコントロールから通知を受信することが簡素化され、HWND 境界を越えた通知に関する Win32 での問題を処理できます。
子コントロールからの通知など、ホスト ウィンドウに送信されたメッセージを選択して処理します。これには、2 つの方法があります。
ホストするクラスでメッセージを処理する場合は、HwndHost クラスの WndProc メソッドをオーバーライドします。
WPF でメッセージを処理する場合は、分離コードで HwndHost クラスの MessageHook イベントを処理します。このイベントは、ホストされるウィンドウで受信されるメッセージごとに発生します。このオプションを選択する場合でも WndProc をオーバーライドする必要がありますが、最小限の実装だけで十分です。
HwndHost の DestroyWindowCore メソッドと WndProc メソッドをオーバーライドします。HwndHost コントラクトを満たすためにはこれらのメソッドをオーバーライドする必要がありますが、場合によっては最小限の実装だけで十分です。
分離コード ファイルで、コントロール ホスト クラスのインスタンスを作成し、ウィンドウをホストする目的の Border 要素の子にします。
ホストされるウィンドウに Microsoft Windows メッセージを送信し、コントロールから送信された通知など、その子ウィンドウからのメッセージを処理して、そのウィンドウと通信します。
ページ レイアウトの実装
ListBox コントロールをホストする WPF ページのレイアウトは、2 つの領域で構成されます。ページの左側では、Win32 コントロールを操作するためのユーザー インターフェイス (UI) を提供する、いくつかの WPF コントロールをホストします。ページの右上隅には、ホストされる ListBox コントロールを表す正方形の領域があります。
このレイアウトを実装するコードは、非常に単純です。ルート要素は、2 つの子要素を持つ DockPanel です。1 つ目は、ListBox コントロールをホストする Border 要素です。これは、ページの右上隅の 200 × 200 の正方形部分を使用します。2 つ目は、情報を表示したり、公開されている相互運用プロパティを設定して ListBox コントロールを操作できる一連の WPF コントロールを含む StackPanel 要素です。StackPanel の子である各要素については、どのような要素があり、それらが何を実行するかについての詳細について、使用されている各要素のリファレンス資料で参照してください。これらの要素は、次のサンプル コードにリストされていますが、ここでは説明しません (それらは基本相互運用モデルには不要で、サンプルの対話性を強化するために提供されています)。
<Window
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://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 から継承されます。コンストラクタは、高さと幅の 2 つのパラメータを使用します。これらのパラメータは、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;
}
また、一連の定数もあります。これらの定数の多くは 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;
Microsoft Win32 ウィンドウを作成するための BuildWindowCore のオーバーライド
このメソッドをオーバーライドして、ページでホストされる Win32 ウィンドウを作成し、ウィンドウとページの間を接続します。このサンプルでは ListBox コントロールをホストするため、2 つのウィンドウが作成されます。1 つは、WPF ページで実際にホストされるウィンドウです。ListBox コントロールは、そのウィンドウの子として作成されます。
この方法を使用するのは、コントロールから通知を受信するプロセスを簡単にするためです。HwndHost クラスを使用することにより、ホストしているウィンドウに送信されるメッセージを処理できます。Win32 コントロールを直接ホストする場合は、コントロールの内部メッセージ ループに送信されたメッセージを受信します。コントロールを表示してそのコントロールにメッセージを送信できますが、コントロールがその親ウィンドウに送信する通知は受信されません。これが意味することはいくつかありますが、その 1 つは、ユーザーがコントロールと対話していることを検出する手段がないことです。そのため、代わりにホスト ウィンドウを作成し、コントロールをそのウィンドウの子にします。この方法を使用すると、コントロールがホスト ウィンドウに対して送信する通知を含め、ホスト ウィンドウのメッセージを処理できます。ホスト ウィンドウは、コントロールの単純なラッパーとほとんど変わりないため、便宜上、パッケージは ListBox コントロールとして参照されます。
ホスト ウィンドウと ListBox コントロールの作成
PInvoke を使用して、ウィンドウ クラスを作成、登録するなどの方法で、コントロールのホスト ウィンドウを作成できます。ただし、定義済みの "静的" ウィンドウ クラスを使用してウィンドウを作成した方がはるかに簡単です。この方法では、コントロールから通知を受信するために必要なウィンドウ プロシージャが提供されるので、最小限のコーディングだけで済みます。
コントロールの HWND は読み取り専用プロパティを通じて公開されるため、ホスト ページは HWND を使用してコントロールにメッセージを送信できます。
public IntPtr hwndListBox
{
get { return hwndControl; }
}
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,
hostHeight, hostWidth,
hwndParent.Handle,
(IntPtr)HOST_ID,
IntPtr.Zero,
0);
hwndControl = CreateWindowEx(0, "listbox", "",
WS_CHILD | WS_VISIBLE | LBS_NOTIFY
| WS_VSCROLL | WS_BORDER,
0, 0,
hostHeight, hostWidth,
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 に加え、HwndHost の WndProc メソッドと DestroyWindowCore メソッドもオーバーライドする必要があります。この例では、コントロールのメッセージが 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 のサイズが正しく設定されます。次に、ホストの Border の Child プロパティに ControlHost オブジェクトを割り当てて、ページ上のコントロールをホストします。
このサンプルでは、ControlHost の MessageHook イベントにハンドラをアタッチしてコントロールからのメッセージを受信します。このイベントは、ホストされているウィンドウに送信されたすべてのメッセージに対して発生します。この場合、これらのメッセージは、コントロールからの通知を含め、実際の ListBox コントロールをラップするウィンドウに送信されます。このサンプルでは、SendMessage を呼び出して、コントロールから情報を取得し、そのコンテンツを変更します。ページとコントロールとの通信方法の詳細については、次のセクションで説明します。
![]() |
---|
SendMessage には 2 つのPInvoke 宣言があります。2 つ必要なのは、一方は 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 メッセージを送信して、コントロールを操作します。コントロールは、そのホスト ウィンドウに通知を送信することで、ユーザーがコントロールと対話したことを通知します。「Windows Presentation Foundation での Win32 ListBox コントロールのホストのサンプル」には、この動作の次のようないくつかの例を示す UI が含まれています。
リストへの項目の追加
リストからの選択した項目の削除
現在選択されている項目のテキストの表示
リスト内の項目数の表示
ユーザーは、従来の Win32 アプリケーションと同様に、リスト ボックス内の項目をクリックして選択することもできます。表示されているデータは、ユーザーが項目を選択、追加して、リスト ボックスの状態を変更するたびに更新されます。
項目を追加するには、リスト ボックスに LB_ADDSTRING メッセージを送信します。項目を削除するには、LB_GETCURSEL を送信して現在の選択のインデックスを取得してから、LB_DELETESTRING を送信して項目を削除します。このサンプルでは、LB_GETCOUNT も送信し、返された値を使用することにより、項目数を示す表示を更新します。SendMessage のこれらのインスタンスはどちらも、前のセクションで説明した PInvoke 宣言の 1 つを使用しています。
private void AppendText(object sender, EventArgs args)
{
if (txtAppend.Text != string.Empty)
{
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 はさまざまな理由で送信されるため、通知 ID を調べて、処理するイベントかどうかを判断する必要があります。ID は、wParam パラメータの上位ワードに格納されています。Microsoft .NET には HIWORD マクロがないため、このサンプルではビット単位の演算子を使用して ID を抽出します。ユーザーが選択や選択の変更を行うと、ID は LBN_SELCHANGE になります。
サンプルは、LBN_SELCHANGE を受け取ると、コントロールに LB_GETCURSEL メッセージを送信することにより、選択した項目のインデックスを取得します。テキストを取得するには、最初に StringBuilder を作成します。その後、コントロールに LB_GETTEXT メッセージを送信します。空の StringBuilder オブジェクトを wParam パラメータとして渡します。SendMessage が返るときに、StringBuilder には選択した項目のテキストが含まれます。SendMessage を使用するには、さらに別の PInvoke 宣言が必要です。
最後に、handled を true に設定して、メッセージが処理されたことを示します。次のコードでは、この動作が実装されている ControlMsgFilter メソッドを示しています。
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;
}
参照
概念
Windows Presentation Foundation の概要