如何正确处理来自 MSFTEDIT_CLASS (RichEdit) 控件的 Windows 消息?
Posted
技术标签:
【中文标题】如何正确处理来自 MSFTEDIT_CLASS (RichEdit) 控件的 Windows 消息?【英文标题】:How to correctly handle Windows messages from an MSFTEDIT_CLASS (RichEdit) control? 【发布时间】:2013-08-24 13:03:17 【问题描述】:更新:根据要求,我添加了用于创建 Window 及其 RichEdit 控件的所有代码。
我正在尝试处理用作另一个窗口子级的 RichEdit 控件的窗口消息。
现在除了我自己的WndProc
之外,我确实可以使用 RichEdit 控件。问题是,当我设置wc.lpszClassName = MSFTEDIT_CLASS;
使其与CreateWindowEx()
中使用的lpClassName
匹配时,RichEdit 控件的内容似乎不再绘制(即文本等),但是,它的 WndProc 函数可以处理消息。
窗口的创建:
首先是构造函数:
SubWindow::SubWindow(const wchar_t *szAppNameImport)
szAppName = szAppNameImport;
cfmt = CHARFORMATW();
hwnd = HWND();
windowRect = RECT();
editControlHwnd = HWND();
wc = WNDCLASSEX();
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_CLASSDC;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = GetModuleHandle(NULL);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = szAppName;
wc.hIconSm = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
然后是Create()
函数:
VOID SubWindow::Create(unsigned int window_startX, unsigned int window_startY, unsigned int windowWidthInput, unsigned int windowHeightInput, HWND parent)
windowRect.left = window_startX;
windowRect.top = window_startY;
windowRect.right = windowWidthInput;
windowRect.bottom = windowHeightInput;
if(!RegisterClassEx(&wc))
throw std::exception();
if((hwnd = CreateWindowEx
(
WS_EX_CLIENTEDGE,
szAppName,
TEXT("Our classy sub window!"),
WS_OVERLAPPEDWINDOW| WS_VISIBLE,
windowRect.left, windowRect.top,
windowRect.right, windowRect.bottom,
parent,
NULL,
wc.hInstance,
NULL))==NULL)
throw std::exception();
SetWindowLongPtr(hwnd, GWL_USERDATA, (LONG_PTR)this);
ShowWindow(hwnd, SW_SHOWDEFAULT);
UpdateWindow(hwnd);
WndProc:
LRESULT CALLBACK SubWindow::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
SubWindow *childWindowPointer = (SubWindow*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
if(childWindowPointer != NULL)
if(childWindowPointer->GetEditControl() == hwnd)
OutputDebugString(L"I SHOULD NOT BE CALLED");
return childWindowPointer->MsgProc(hwnd, uMsg, wParam, lParam);
else
return DefWindowProc(hwnd, uMsg, wParam, lParam);
MsgProc:
LRESULT SubWindow::MsgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
PAINTSTRUCT ps;
HDC hdc;
switch(uMsg)
case WM_WINDOWPOSCHANGED:
GetClientRect(hwnd, &windowRect);
SetWindowPos(editControlHwnd, NULL, windowRect.left, windowRect.top, windowRect.right, windowRect.bottom, SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
case WM_DESTROY:
OutputDebugString(TEXT("DESTROYING A SUB WINDOW!\n"));
return 0;
case WM_PAINT:
InvalidateRect (hwnd, NULL, FALSE);
hdc = BeginPaint(hwnd, &ps);
EndPaint(hwnd, &ps);
return 0;
case EM_EXSETSEL:
if(hwnd == editControlHwnd)
OutputDebugString(L"Text selection changed");
return 0;
return DefWindowProc(hwnd, uMsg, wParam, lParam);
RichEdit 控件完美地绘制和运行,显然没有问题,除了它没有使用我定义的WndProc
。
我不确定我在这里做错了什么或如何正确解决这个问题。
编辑:
根据答案和 cmets,我已将我的代码恢复为仅使用包含 RichEdit 控件的 Window
类,由此创建:
void SubWindow::CreateEditControl()
std::wstring initialText = TEXT("TestWindow\r\n");
LoadLibrary(L"Msftedit.dll");
GetClientRect(hwnd, &windowRect);
editControlHwnd = CreateWindowEx(0, MSFTEDIT_CLASS, initialText.data(),
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_READONLY | WS_VSCROLL | ES_NOHIDESEL,
windowRect.left, windowRect.top,windowRect.right,windowRect.bottom,
hwnd,
NULL, NULL, NULL);
cfmt.cbSize = sizeof(CHARFORMAT);
cfmt.dwMask = CFM_COLOR | CFM_FACE | CFM_SIZE;
cfmt.dwEffects = 0;
cfmt.yHeight = 160;
cfmt.crTextColor = RGB(0,0,0);
wcscpy_s(cfmt.szFaceName, TEXT("Tahoma"));
SendMessage(editControlHwnd, EM_SETCHARFORMAT, SCF_DEFAULT, (LPARAM)&cfmt);
如何在 Window 的 MsgProc 中处理来自该控件的消息?
【问题讨论】:
使用名称MSFTEDIT_CLASS
注册您自己的类有效地替换了原来的类。完成此操作后,将无法再访问原始类,而尝试创建 MSFTEDIT_CLASS
会改为创建您的类。这就像用另一个文件覆盖一个文件。目前尚不清楚您的目标是什么。是否要在全局范围内部分替换富编辑控件?只有一个窗口?或者您只是想响应来自窗口的消息?
我试图在一个或多个窗口中创建一个RichEdit
控件,但现在它只是一个。
然后创建标准的MSFTEDIT_CLASS
。不要尝试自己编写。就像你说的,如果你使用标准控件,“RichEdit 控件可以完美地绘制和运行。”
我已经撤消了对代码的更改,因此只有一个用于 Window 的类,它使用我在上面的问题中添加的函数来创建 RichEdit 控件。
"如何在 Window 的 MsgProc 中处理来自该控件的消息?"你为什么要处理别人的消息?让他们处理自己的消息。
【参考方案1】:
当您使用默认类名 (MSFTEDIT_CLASS
) 创建富编辑控件窗口时,所有消息都将发送到其父窗口。由于您不是那个父窗口,因此您无法处理这些消息。
因此,您将需要对控件进行子类化,替换您的 自己的 将直接调用的窗口过程,而不是允许将消息传递给父级。这很容易做到;我之前在this answer for a regular edit control 中讨论过它。修改后的示例代码如下所示:
// Stores the old original window procedure for the rich edit control.
WNDPROC wpOldRichEditProc;
// The new custom window procedure for the rich edit control.
LRESULT CALLBACK CustomRichEditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
switch (msg)
...
// Pass the messages you don't process on to the original window procedure.
CallWindowProc(wpOldRichEditProc, hWnd, msg, wParam, lParam);
当你创建控件时:
// Create the rich edit control
HWND hWnd = CreateWindowEx(...)
// Subclass it.
wpOldRichEditProc= (WNDPROC)SetWindowLongPtr(hWnd,
GWLP_WNDPROC,
(WNDPROC)CustomRichEditProc);
您还需要确保在控件被销毁时取消子类化。另一个示例演示了这样做是为了响应父窗口收到的消息,但这在您的情况下不起作用,因为您没有收到父窗口的消息。相反,您需要从控件中删除子类以响应它自己的WM_NCDESTROY
消息:
SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)wpOldRichEditProc);
或者,第 6 版通用控件库引入了一种新的、不易出错的子类化方法,该方法使用 a set of utility functions。 (关键功能实际上存在于早期版本中,但没有记录。)考虑到您无法控制实际拥有窗口的进程,这可以说是首选方法。
有两种方法的演示here on MSDN。
当然,您不必仅对单个控件进行子类化。您还可以注册一个自定义窗口类,其行为方式与内置富编辑控件相同,但仍让您首先破解该类的窗口接收到的消息。我无法从问题中判断这是否有必要; 听起来你只关心一个控件。
【讨论】:
“原始”问题是我的父窗口没有收到 RichEdit 控件的消息,这就是我将 RichEdit 控件移到它自己的类中的原因。 @Interminable 我看不出你是怎么做到的。在问题的顶部,wc.lpszClassName
有一些令人困惑的地方,但是没有与之相关的代码,所以我不知道它指的是什么。无论如何,问题在于您没有调用富编辑控件的默认窗口过程。这意味着您错误地对其进行了子类化。
我有两个 wc 结构,每个班级一个。一个在Window
类中,一个在EditControl
类中。和以前一样,没有EditControl
类。相反,我只是在我的Window
类中有一个CreateEditControl()
函数。而Window
类只有一个 wc 结构。如果您愿意,我可以在创建一个完全独立的 EditControl
类之前使用我最初使用的代码编辑我的问题。
我不知道为什么我想看到与您实际使用的代码不同的代码..?如果要注册一个自定义窗口类,它是默认类之一的子类,则需要调用GetClassInfoEx
函数;我在您的代码中没有看到您正在这样做的证据。您将还需要保存指向原始窗口过程的指针,以便您可以使用CallWindowProc
调用它,就像我在回答中显示的那样。 DefWindowProc
只会调用基本窗口的默认窗口过程;它不会调用默认的丰富的编辑控件窗口过程。
使用上面我的问题中的附加代码,为了处理 RichEdit 控件的消息,我是否仍需要执行您的答案中所做的操作?恐怕部分问题可能是我对 Windows API 上下文中的子类化概念缺乏了解,对此深表歉意。【参考方案2】:
您说最初的问题是您的父窗口没有从 RichEdit 控件获取通知消息。您是否向 RichEdit 控件发送了 EM_SETEVENTMASK 消息?如果不这样做,RichEdit 控件将不会向其父窗口发送某些通知消息。见EM_SETEVENTMASK message。
【讨论】:
【参考方案3】:您能否展示您的代码涉及wc
结构和窗口的创建?我很确定您不希望主窗口与富编辑控件具有相同的类 - 这就是我目前正在阅读的内容。
我什至不知道您为什么将WNDCLASSEX
应用于富编辑控件。
我的建议是,在创建富编辑控件后简化操作并“子类化”富编辑控件,使用 SetWindowLong()
和 GWL_WNDPROC
到您的 EditControl::WndProc
。
【讨论】:
我的帖子现在包含我用来创建窗口及其 RichEdit 控件的所有代码。以上是关于如何正确处理来自 MSFTEDIT_CLASS (RichEdit) 控件的 Windows 消息?的主要内容,如果未能解决你的问题,请参考以下文章
Winapi - 将 LPWCSTR 作为 LPCSTR 传递