برنامج تعليمي إنشاء تطبيق WPF يستضيف محتوى Win32
المتطلبات الأساسية
انظر نظرة عامة حول التشغيل التفاعلي ل Win32 و WPF
شرح تفصيلى لـ Win32 Inside Windows Presentation Framework (HwndHost)
لإعادة استخدام محتوي Win32 داخل تطبيقات WPF، استخدم HwndHost ، وهو عنصر تحكم الذي يجعل HWNDs يبدو مثل محتوى WPF. مثل HwndSource، HwndHost هو مباشر للاستخدام: اشتق من HwndHost ثم قم بتطبيق أساليب BuildWindowCore و DestroyWindowCore، ثم قم بإنشاء فئة المشتقة HwndHost الخاصة بك و ضعه داخل التطبيق WPF الخاص بك.
إذا كان منطق Win32 الخاص بك تم بالفعل حزمه كعنصر تحكم إذاً تطبيق BuildWindowCore الخاص بك أكثر قليلاً من استدعاء ال CreateWindow. على سبيل المثال، لإنشاء عنصر تحكم Win32LISTBOX في ++C:
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
}
ولكن افترض أن التعليمات البرمجية Win32 غير مُضمن تماماً ؟ و لذلك يمكنك إنشاء مربع حوار Win32 و تضمين محتوياته إلى تطبيق WPF أكبر. يبين نموذج هذا في Microsoft Visual Studio و ++C ، على الرغم من أنه من الممكن أيضاً القيام بذلك في لغة مختلفة أو في سطر الأوامر.
أبدأ بحوار بسيط مترجم إلى مشروع ++C DLL.
بعد ذلك قم تقديم مربع حوار إلى تطبيق WPF أكبر:
ترجم DLL كما تتم إدارته (/clr)
حول الحوار إلى عنصر تحكم
قم بتعريف فئة مشتقة من HwndHost مع أساليب BuildWindowCore و DestroyWindowCore
تجاوز أسلوب TranslateAccelerator لمعالجة مفاتيح الحوار
تجاوز أسلوب TabInto لدعم التبويب
تجاوز أسلوب OnMnemonic لدعم التعليمات الرمزية القصيرة
أنشأ الفئة الفرعية HwndHost و ضعها ضمن عنصر WPF الصحيح
حول الحوار إلى عنصر تحكم
يمكنك تحويل مربع الحوار إلى تابع HWND باستخدام أنماط WS_CHILD DS_CONTROL. انتقل إلى ملف المورد (.rc) حيث يتم تعريف مربع الحوار و احصل على بداية تعريف مربع الحوار:
IDD_DIALOG1 DIALOGEX 0, 0, 303, 121
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
قم بتغيير السطر الثاني إلى:
STYLE DS_SETFONT | WS_CHILD | WS_BORDER | DS_CONTROL
هذا الإجراء لا يحزمه بشكل كامل إلى عنصر تحكم مُضمن; لا تزال تحتاج لاستدعاء IsDialogMessage() حتى Win32 يمكنه معالجة رسائل معينة ولكن تغيير عنصر تحكم يوفر طريقة مباشرة لوضع عناصر التحكم هذه داخل HWND آخر.
فئة فرعية HwndHost
استيراد مساحات الأسماء التالية:
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;
ثم قم بإنشاء فئة مشتقة من HwndHost و تجاوز أساليب BuildWindowCore و DestroyWindowCore:
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
}
هنا يمكنك استخدام CreateDialog لإنشاء مربع الحوار الذي هو فعلاً عنصر التحكم. بما أن هذه إحدى الطرق الأولية المستدعاة داخل DLL، يجب أن تقوم ببعض التهيئة Win32 القياسية بواسطة استدعاء دالة سيتم تعريفها فيما بعد، تسمى InitializeGlobals():
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);
تجاوز أسلوب TranslateAccelerator لمعالجة مفاتيح الحوار
إذا قمت بتشغيل هذه العينة الآن، ستحصل على عنصر تحكم حوار الذي يعرض ولكن يتجاهل كافة معالجة المفاتيح التي تجعل مربع حوار مربع حوار وظيفي. يجب الآن تجاوز تطبيق TranslateAccelerator (الذي يأتي من IKeyboardInputSink ، الواجهة التي ينفذها HwndHost). هذا الأسلوب يتم استدعائه عندما يتلقى فيها التطبيق WM_KEYDOWN و WM_SYSKEYDOWN.
#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
}
هذا عدد كبير من التعليمات البرمجية في قطعة واحدة بحيث ستستخدم بعض التوضيحات الأكثر تفصيلاً. أولاً، التعليمات البرمجية باستخدام وحدات الماكرو ++C و ++C; ستحتاج أن تدرك أنه يوجد ماكرو باسم TranslateAccelerator ، الذي يتم تعريفه في winuser.h:
#define TranslateAccelerator TranslateAcceleratorW
لذا تأكد من تحديد أسلوب TranslateAccelerator و ليس أسلوب TranslateAcceleratorW.
وبشكل مماثل، هناك winuser.h غير المدار MSG و بنية Microsoft::Win32::MSG المدار. يمكن disambiguate بينهما باستخدام عامل التشغيل ++C ::.
virtual bool TranslateAccelerator(System::Windows::Interop::MSG% msg,
ModifierKeys modifiers) override
{
::MSG m = ConvertMessage(msg);
كلاً من MSGs يحتوا على نفس البيانات و لكن في بعض الأحيان يكون من السهل العمل مع التعريف غير مدار لذلك يمكنك تعريف إجراء التحويل الواضح في هذا النموذج:
::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;
}
رجوعاً إلى TranslateAccelerator. المبدأ الأساسي هو استدعاء Win32 الدالة IsDialogMessage لفعل العمل قدر الممكن, ولكن IsDialogMessage ليس لديه حق الوصول إلى أي شيء خارج مربع الحوار. أثناء تشغيل تبويب المستخدم حول مربع الحوار عندما يمر التبويب بعد آخر عنصر تحكم في مربع الحوار الخاص بنا, ستحتاج إلى تعيين تركيز إلى جزء WPF عن طريق استدعاء IKeyboardInputSite::OnNoMoreStops.
// 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);
}
أخيراً، قم باستدعاء IsDialogMessage. و لكن إحدى مسؤوليات أسلوب TranslateAccelerator تسرد WPF ما إذا قمت بمعالجة ضغط المفاتيح أو لا. إذا كنت لم تعالج ذلك, يمكن لحدث الإدخال النفق و الفقاعية خلال باقي التطبيق. هنا، سوف يعرض quirk من معالجة messange لوحة المفاتيح و طبيعة بنية الإدخال في Win32. لسوء الحظ، IsDialogMessage لا يُرجع بأي طريقة ما إذا كان يعالج ضغط مفاتيح معينة. الأسوأ, انه سوف يستدعى DispatchMessage() على ضغطات المفاتيح التي لا يجب أن يعالجها! بحيث يكون عليك إجراء هندسة عكسية IsDialogMessage ، و فقط استدعائها للمفاتيح التي تعرف أنها ستعالجها:
// 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;
}
تجاوز أسلوب TabInto لدعم Tabbing
الآن و قد طبقت TranslateAccelerator, يمكن للمستخدم التبويب بداخل مربع الحوار و التبويب خارجه إلى تطبيق WPF أكبر. ولكن لا يمكن لمستخدم التبويب مرة أخرى داخل مربع الحوار. لحل ذلك, يمكنك تجاوز TabInto:
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;
}
المعلمة TraversalRequest تخبرك ما إذا كان أداء التبويب هو تبويب أو تبويب مُحرَّك.
تجاوز أسلوب OnMnemonic لدعم التعليمات الرمزية القصيرة
معالجة لوحة المفاتيح اكتمل تقريباً ولكن هناك شيء مفقود – التعليمات الرمزية القصيرة لا تعمل. إذا كان ضغط المستخدم ALT+F, التركيز لا ينتقل إلى “الاسم الأول:” مربع تحرير لذا، تجاوز أسلوب OnMnemonic:
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
};
لماذا لا يتم استدعاء IsDialogMessage هنا ؟ لديك نفس المشكلة كما من قبل--تحتاج الى تمكين إعلام تعليمات البرمجية WPF ما إذا كان التعليمات البرمجية الخاصة بك تعالج ضغط المفاتيح أم لا, و IsDialogMessage لا يمكنه القيام بذلك. هناك أيضاً مشكلة ثانية لأن IsDialogMessage يرفض معالجة التعليمات الرمزية القصيرة إذا لم يكن HWND المركز داخل مربع الحوار.
إنشاء فئة HwndHost المشتقة
وأخيراً، الآن حيث دعم المفتاح و التبويب في مكانه، يمكنك وضع HwndHost الخاص بك إلى WPF أكبر. إذا تمت كتابة التطبيق الرئيسي في XAML ، أسهل طريقة لوضعه في المكان الصحيح هو بترك عنصر Border فارغ حيث تريد وضع HwndHost. هنا يمكنك إنشاء Border باسم insertHwndHostHere:
<Window x:Class="WPFApplication1.Window1"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://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>
يكون كل ما تبقى هو العثور على مكان جيد في تسلسل التعليمات البرمجية لإنشاء HwndHost و توصيله إلى Border. في هذا المثال، ستقوم بوضعه داخل المُنشئ لفئة Window المشتقة:
public partial class Window1 : Window {
public Window1() {
}
void Window1_Loaded(object sender, RoutedEventArgs e) {
HwndHost host = new ManagedCpp.MyHwndHost();
insertHwndHostHere.Child = host;
}
}
الذي يوفر لك: