mirror of
https://github.com/Stone-Red-Code/docs-desktop.git
synced 2026-09-08 07:56:07 +02:00
Initial WPF content migrated (#17)
* Reset branch for WPF changes * Convert BMP to PNG; fix link-out-of-scope err * Add snippets for WPF... 6794 files!!!! * Add missing snippets * update file updated between migration * Fix paths to include * update breadcrumb and toc * fix index links * fix index links * fix index links * fix markdown
This commit is contained in:
@@ -0,0 +1,369 @@
|
||||
---
|
||||
title: "Hosting Win32 Content"
|
||||
ms.date: "03/30/2017"
|
||||
helpviewer_keywords:
|
||||
- "interoperability [WPF], tutorials"
|
||||
- "Win32 code [WPF], WPF interoperation"
|
||||
- "interoperability [WPF], Win32"
|
||||
ms.assetid: 3cc8644a-34f3-4082-9ddc-77623e4df2d8
|
||||
---
|
||||
|
||||
# Hosting Win32 Content in WPF
|
||||
|
||||
## Prerequisites
|
||||
|
||||
See [WPF and Win32 Interoperation](wpf-and-win32-interoperation.md).
|
||||
|
||||
## A Walkthrough of Win32 Inside Windows Presentation Framework (HwndHost)
|
||||
|
||||
To reuse Win32 content inside [!INCLUDE[TLA2#tla_winclient](../../../includes/tla2sharptla-winclient-md.md)] applications, use <xref:System.Windows.Interop.HwndHost>, which is a control that makes HWNDs look like [!INCLUDE[TLA2#tla_winclient](../../../includes/tla2sharptla-winclient-md.md)] content. Like <xref:System.Windows.Interop.HwndSource>, <xref:System.Windows.Interop.HwndHost> is straightforward to use: derive from <xref:System.Windows.Interop.HwndHost> and implement `BuildWindowCore` and `DestroyWindowCore` methods, then instantiate your <xref:System.Windows.Interop.HwndHost> derived class and place it inside your [!INCLUDE[TLA2#tla_winclient](../../../includes/tla2sharptla-winclient-md.md)] application.
|
||||
|
||||
If your Win32 logic is already packaged as a control, then your `BuildWindowCore` implementation is little more than a call to `CreateWindow`. For example, to create a Win32 LISTBOX control in C++:
|
||||
|
||||
```cpp
|
||||
virtual HandleRef BuildWindowCore(HandleRef hwndParent) override {
|
||||
HWND handle = CreateWindowEx(0, L"LISTBOX",
|
||||
L"this is a Win32 listbox",
|
||||
WS_CHILD | WS_VISIBLE | LBS_NOTIFY
|
||||
| WS_VSCROLL | WS_BORDER,
|
||||
0, 0, // x, y
|
||||
30, 70, // height, width
|
||||
(HWND) hwndParent.Handle.ToPointer(), // parent hwnd
|
||||
0, // hmenu
|
||||
0, // hinstance
|
||||
0); // lparam
|
||||
|
||||
return HandleRef(this, IntPtr(handle));
|
||||
}
|
||||
|
||||
virtual void DestroyWindowCore(HandleRef hwnd) override {
|
||||
// HwndHost will dispose the hwnd for us
|
||||
}
|
||||
```
|
||||
|
||||
But suppose the Win32 code is not quite so self-contained? If so, you can create a Win32 dialog box and embed its contents into a larger [!INCLUDE[TLA2#tla_winclient](../../../includes/tla2sharptla-winclient-md.md)] application. The sample shows this in Visual Studio and C++, although it is also possible to do this in a different language or at the command line.
|
||||
|
||||
Start with a simple dialog, which is compiled into a C++ DLL project.
|
||||
|
||||
Next, introduce the dialog into the larger [!INCLUDE[TLA2#tla_winclient](../../../includes/tla2sharptla-winclient-md.md)] application:
|
||||
|
||||
- Compile the DLL as managed (`/clr`)
|
||||
|
||||
- Turn the dialog into a control
|
||||
|
||||
- Define the derived class of <xref:System.Windows.Interop.HwndHost> with `BuildWindowCore` and `DestroyWindowCore` methods
|
||||
|
||||
- Override `TranslateAccelerator` method to handle dialog keys
|
||||
|
||||
- Override `TabInto` method to support tabbing
|
||||
|
||||
- Override `OnMnemonic` method to support mnemonics
|
||||
|
||||
- Instantiate the <xref:System.Windows.Interop.HwndHost> subclass and put it under the right [!INCLUDE[TLA2#tla_winclient](../../../includes/tla2sharptla-winclient-md.md)] element
|
||||
|
||||
### Turn the Dialog into a Control
|
||||
|
||||
You can turn a dialog box into a child HWND using the WS_CHILD and DS_CONTROL styles. Go into the resource file (.rc) where the dialog is defined, and find the beginning of the definition of the dialog:
|
||||
|
||||
```text
|
||||
IDD_DIALOG1 DIALOGEX 0, 0, 303, 121
|
||||
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||
```
|
||||
|
||||
Change the second line to:
|
||||
|
||||
```text
|
||||
STYLE DS_SETFONT | WS_CHILD | WS_BORDER | DS_CONTROL
|
||||
```
|
||||
|
||||
This action does not fully package it into a self-contained control; you still need to call `IsDialogMessage()` so Win32 can process certain messages, but the control change does provide a straightforward way of putting those controls inside another HWND.
|
||||
|
||||
## Subclass HwndHost
|
||||
|
||||
Import the following namespaces:
|
||||
|
||||
```cpp
|
||||
namespace ManagedCpp
|
||||
{
|
||||
using namespace System;
|
||||
using namespace System::Windows;
|
||||
using namespace System::Windows::Interop;
|
||||
using namespace System::Windows::Input;
|
||||
using namespace System::Windows::Media;
|
||||
using namespace System::Runtime::InteropServices;
|
||||
```
|
||||
|
||||
Then create a derived class of <xref:System.Windows.Interop.HwndHost> and override the `BuildWindowCore` and `DestroyWindowCore` methods:
|
||||
|
||||
```cpp
|
||||
public ref class MyHwndHost : public HwndHost, IKeyboardInputSink {
|
||||
private:
|
||||
HWND dialog;
|
||||
|
||||
protected:
|
||||
virtual HandleRef BuildWindowCore(HandleRef hwndParent) override {
|
||||
InitializeGlobals();
|
||||
dialog = CreateDialog(hInstance,
|
||||
MAKEINTRESOURCE(IDD_DIALOG1),
|
||||
(HWND) hwndParent.Handle.ToPointer(),
|
||||
(DLGPROC) About);
|
||||
return HandleRef(this, IntPtr(dialog));
|
||||
}
|
||||
|
||||
virtual void DestroyWindowCore(HandleRef hwnd) override {
|
||||
// hwnd will be disposed for us
|
||||
}
|
||||
```
|
||||
|
||||
Here you use the `CreateDialog` to create the dialog box that is really a control. Since this is one of the first methods called inside the DLL, you should also do some standard Win32 initialization by calling a function you will define later, called `InitializeGlobals()`:
|
||||
|
||||
```cpp
|
||||
bool initialized = false;
|
||||
void InitializeGlobals() {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
// TODO: Place code here.
|
||||
MSG msg;
|
||||
HACCEL hAccelTable;
|
||||
|
||||
// Initialize global strings
|
||||
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
|
||||
LoadString(hInstance, IDC_TYPICALWIN32DIALOG, szWindowClass, MAX_LOADSTRING);
|
||||
MyRegisterClass(hInstance);
|
||||
```
|
||||
|
||||
### Override TranslateAccelerator Method to Handle Dialog Keys
|
||||
|
||||
If you ran this sample now, you would get a dialog control that displays, but it would ignore all of the keyboard processing that makes a dialog box a functional dialog box. You should now override the `TranslateAccelerator` implementation (which comes from `IKeyboardInputSink`, an interface that <xref:System.Windows.Interop.HwndHost> implements). This method gets called when the application receives WM_KEYDOWN and WM_SYSKEYDOWN.
|
||||
|
||||
```cpp
|
||||
#undef TranslateAccelerator
|
||||
virtual bool TranslateAccelerator(System::Windows::Interop::MSG% msg,
|
||||
ModifierKeys modifiers) override
|
||||
{
|
||||
::MSG m = ConvertMessage(msg);
|
||||
|
||||
// Win32's IsDialogMessage() will handle most of our tabbing, but doesn't know
|
||||
// what to do when it reaches the last tab stop
|
||||
if (m.message == WM_KEYDOWN && m.wParam == VK_TAB) {
|
||||
HWND firstTabStop = GetDlgItem(dialog, IDC_EDIT1);
|
||||
HWND lastTabStop = GetDlgItem(dialog, IDCANCEL);
|
||||
TraversalRequest^ request = nullptr;
|
||||
|
||||
if (GetKeyState(VK_SHIFT) && GetFocus() == firstTabStop) {
|
||||
// this code should work, but there’s a bug with interop shift-tab in current builds
|
||||
request = gcnew TraversalRequest(FocusNavigationDirection::Last);
|
||||
}
|
||||
else if (!GetKeyState(VK_SHIFT) && GetFocus() == lastTabStop) {
|
||||
request = gcnew TraversalRequest(FocusNavigationDirection::Next);
|
||||
}
|
||||
|
||||
if (request != nullptr)
|
||||
return ((IKeyboardInputSink^) this)->KeyboardInputSite->OnNoMoreTabStops(request);
|
||||
|
||||
}
|
||||
|
||||
// Only call IsDialogMessage for keys it will do something with.
|
||||
if (msg.message == WM_SYSKEYDOWN || msg.message == WM_KEYDOWN) {
|
||||
switch (m.wParam) {
|
||||
case VK_TAB:
|
||||
case VK_LEFT:
|
||||
case VK_UP:
|
||||
case VK_RIGHT:
|
||||
case VK_DOWN:
|
||||
case VK_EXECUTE:
|
||||
case VK_RETURN:
|
||||
case VK_ESCAPE:
|
||||
case VK_CANCEL:
|
||||
IsDialogMessage(dialog, &m);
|
||||
// IsDialogMessage should be called ProcessDialogMessage --
|
||||
// it processes messages without ever really telling you
|
||||
// if it handled a specific message or not
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false; // not a key we handled
|
||||
}
|
||||
```
|
||||
|
||||
This is a lot of code in one piece, so it could use some more detailed explanations. First, the code using C++ and C++ macros; you need to be aware that there is already a macro named `TranslateAccelerator`, which is defined in winuser.h:
|
||||
|
||||
```cpp
|
||||
#define TranslateAccelerator TranslateAcceleratorW
|
||||
```
|
||||
|
||||
So make sure to define a `TranslateAccelerator` method and not a `TranslateAcceleratorW` method.
|
||||
|
||||
Similarly, there is both the unmanaged winuser.h MSG and the managed `Microsoft::Win32::MSG` struct. You can disambiguate between the two using the C++ `::` operator.
|
||||
|
||||
```cpp
|
||||
virtual bool TranslateAccelerator(System::Windows::Interop::MSG% msg,
|
||||
ModifierKeys modifiers) override
|
||||
{
|
||||
::MSG m = ConvertMessage(msg);
|
||||
}
|
||||
```
|
||||
|
||||
Both MSGs have the same data, but sometimes it is easier to work with the unmanaged definition, so in this sample you can define the obvious conversion routine:
|
||||
|
||||
```cpp
|
||||
::MSG ConvertMessage(System::Windows::Interop::MSG% msg) {
|
||||
::MSG m;
|
||||
m.hwnd = (HWND) msg.hwnd.ToPointer();
|
||||
m.lParam = (LPARAM) msg.lParam.ToPointer();
|
||||
m.message = msg.message;
|
||||
m.wParam = (WPARAM) msg.wParam.ToPointer();
|
||||
|
||||
m.time = msg.time;
|
||||
|
||||
POINT pt;
|
||||
pt.x = msg.pt_x;
|
||||
pt.y = msg.pt_y;
|
||||
m.pt = pt;
|
||||
|
||||
return m;
|
||||
}
|
||||
```
|
||||
|
||||
Back to `TranslateAccelerator`. The basic principle is to call the Win32 function `IsDialogMessage` to do as much work as possible, but `IsDialogMessage` does not have access to anything outside the dialog. As a user tab around the dialog, when tabbing runs past the last control in our dialog, you need to set focus to the [!INCLUDE[TLA2#tla_winclient](../../../includes/tla2sharptla-winclient-md.md)] portion by calling `IKeyboardInputSite::OnNoMoreStops`.
|
||||
|
||||
```cpp
|
||||
// Win32's IsDialogMessage() will handle most of the tabbing, but doesn't know
|
||||
// what to do when it reaches the last tab stop
|
||||
if (m.message == WM_KEYDOWN && m.wParam == VK_TAB) {
|
||||
HWND firstTabStop = GetDlgItem(dialog, IDC_EDIT1);
|
||||
HWND lastTabStop = GetDlgItem(dialog, IDCANCEL);
|
||||
TraversalRequest^ request = nullptr;
|
||||
|
||||
if (GetKeyState(VK_SHIFT) && GetFocus() == firstTabStop) {
|
||||
request = gcnew TraversalRequest(FocusNavigationDirection::Last);
|
||||
}
|
||||
else if (!GetKeyState(VK_SHIFT) && GetFocus() == lastTabStop) { {
|
||||
request = gcnew TraversalRequest(FocusNavigationDirection::Next);
|
||||
}
|
||||
|
||||
if (request != nullptr)
|
||||
return ((IKeyboardInputSink^) this)->KeyboardInputSite->OnNoMoreTabStops(request);
|
||||
}
|
||||
```
|
||||
|
||||
Finally, call `IsDialogMessage`. But one of the responsibilities of a `TranslateAccelerator` method is telling [!INCLUDE[TLA2#tla_winclient](../../../includes/tla2sharptla-winclient-md.md)] whether you handled the keystroke or not. If you did not handle it, the input event can tunnel and bubble through the rest of the application. Here, you will expose a quirk of keyboard messange handling and the nature of the input architecture in Win32. Unfortunately, `IsDialogMessage` does not return in any way whether it handles a particular keystroke. Even worse, it will call `DispatchMessage()` on keystrokes it should not handle! So you will have to reverse-engineer `IsDialogMessage`, and only call it for the keys you know it will handle:
|
||||
|
||||
```cpp
|
||||
// Only call IsDialogMessage for keys it will do something with.
|
||||
if (msg.message == WM_SYSKEYDOWN || msg.message == WM_KEYDOWN) {
|
||||
switch (m.wParam) {
|
||||
case VK_TAB:
|
||||
case VK_LEFT:
|
||||
case VK_UP:
|
||||
case VK_RIGHT:
|
||||
case VK_DOWN:
|
||||
case VK_EXECUTE:
|
||||
case VK_RETURN:
|
||||
case VK_ESCAPE:
|
||||
case VK_CANCEL:
|
||||
IsDialogMessage(dialog, &m);
|
||||
// IsDialogMessage should be called ProcessDialogMessage --
|
||||
// it processes messages without ever really telling you
|
||||
// if it handled a specific message or not
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### Override TabInto Method to Support Tabbing
|
||||
|
||||
Now that you have implemented `TranslateAccelerator`, a user can tab around inside the dialog box and tab out of it into the greater [!INCLUDE[TLA2#tla_winclient](../../../includes/tla2sharptla-winclient-md.md)] application. But a user cannot tab back into the dialog box. To solve that, you override `TabInto`:
|
||||
|
||||
```cpp
|
||||
public:
|
||||
virtual bool TabInto(TraversalRequest^ request) override {
|
||||
if (request->FocusNavigationDirection == FocusNavigationDirection::Last) {
|
||||
HWND lastTabStop = GetDlgItem(dialog, IDCANCEL);
|
||||
SetFocus(lastTabStop);
|
||||
}
|
||||
else {
|
||||
HWND firstTabStop = GetDlgItem(dialog, IDC_EDIT1);
|
||||
SetFocus(firstTabStop);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
The `TraversalRequest` parameter tells you whether the tab action is a tab or shift tab.
|
||||
|
||||
### Override OnMnemonic Method to Support Mnemonics
|
||||
|
||||
Keyboard handling is almost complete, but there is one thing missing – mnemonics do not work. If a user presses alt-F, focus doe not jump to the "First name:" edit box. So, you override the `OnMnemonic` method:
|
||||
|
||||
```cpp
|
||||
virtual bool OnMnemonic(System::Windows::Interop::MSG% msg, ModifierKeys modifiers) override {
|
||||
::MSG m = ConvertMessage(msg);
|
||||
|
||||
// If it's one of our mnemonics, set focus to the appropriate hwnd
|
||||
if (msg.message == WM_SYSCHAR && GetKeyState(VK_MENU /*alt*/)) {
|
||||
int dialogitem = 9999;
|
||||
switch (m.wParam) {
|
||||
case 's': dialogitem = IDOK; break;
|
||||
case 'c': dialogitem = IDCANCEL; break;
|
||||
case 'f': dialogitem = IDC_EDIT1; break;
|
||||
case 'l': dialogitem = IDC_EDIT2; break;
|
||||
case 'p': dialogitem = IDC_EDIT3; break;
|
||||
case 'a': dialogitem = IDC_EDIT4; break;
|
||||
case 'i': dialogitem = IDC_EDIT5; break;
|
||||
case 't': dialogitem = IDC_EDIT6; break;
|
||||
case 'z': dialogitem = IDC_EDIT7; break;
|
||||
}
|
||||
if (dialogitem != 9999) {
|
||||
HWND hwnd = GetDlgItem(dialog, dialogitem);
|
||||
SetFocus(hwnd);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; // key unhandled
|
||||
};
|
||||
```
|
||||
|
||||
Why not call `IsDialogMessage` here? You have the same issue as before--you need to be able to inform [!INCLUDE[TLA2#tla_winclient](../../../includes/tla2sharptla-winclient-md.md)] code whether your code handled the keystroke or not, and `IsDialogMessage` cannot do that. There is also a second issue, because `IsDialogMessage` refuses to process the mnemonic if the focused HWND is not inside the dialog box.
|
||||
|
||||
### Instantiate the HwndHost Derived Class
|
||||
|
||||
Finally, now that all the key and tab support is in place, you can put your <xref:System.Windows.Interop.HwndHost> into the larger [!INCLUDE[TLA2#tla_winclient](../../../includes/tla2sharptla-winclient-md.md)] application. If the main application is written in [!INCLUDE[TLA2#tla_xaml](../../../includes/tla2sharptla-xaml-md.md)], the easiest way to put it in the right place is to leave an empty <xref:System.Windows.Controls.Border> element where you want to put the <xref:System.Windows.Interop.HwndHost>. Here you create a <xref:System.Windows.Controls.Border> named `insertHwndHostHere`:
|
||||
|
||||
```xaml
|
||||
<Window x:Class="WPFApplication1.Window1"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Windows Presentation Framework Application"
|
||||
Loaded="Window1_Loaded"
|
||||
>
|
||||
<StackPanel>
|
||||
<Button Content="WPF button"/>
|
||||
<Border Name="insertHwndHostHere" Height="200" Width="500"/>
|
||||
<Button Content="WPF button"/>
|
||||
</StackPanel>
|
||||
</Window>
|
||||
```
|
||||
|
||||
Then all that remains is to find a good place in code sequence to instantiate the <xref:System.Windows.Interop.HwndHost> and connect it to the <xref:System.Windows.Controls.Border>. In this example, you will put it inside the constructor for the <xref:System.Windows.Window> derived class:
|
||||
|
||||
```csharp
|
||||
public partial class Window1 : Window {
|
||||
public Window1() {
|
||||
}
|
||||
|
||||
void Window1_Loaded(object sender, RoutedEventArgs e) {
|
||||
HwndHost host = new ManagedCpp.MyHwndHost();
|
||||
insertHwndHostHere.Child = host;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Which gives you:
|
||||
|
||||

|
||||
|
||||
## See also
|
||||
|
||||
- [WPF and Win32 Interoperation](wpf-and-win32-interoperation.md)
|
||||
Reference in New Issue
Block a user