c - 尽管 DLGC_WANTTAB 多行 EDIT 对话框控件不使用制表符
问题描述
我想要一个多行EDIT
控件,它是对话框的子控件,将选项卡作为常规文本输入(而不是切换到下一个控件)。
根据多个资源,这样做的正确方法是处理WM_GETDLGCODE
并返回DLGC_WANTTAB
(或检查 + DLGC_WANTMESSAGE
)。
看:
- https://stackoverflow.com/a/16668256/653473
- https://stackoverflow.com/a/42352363/653473
- Raymond Chen在这里的第一条评论:https ://stackoverflow.com/a/18444839/653473
Raymond 的文章表明我想要的实际上是默认行为,这不是我观察到的。
再生产:
这是基于 Visual Studio 的默认Windows 桌面应用程序C++ 模板。如果由于某种原因你没有那个,那么内容就不那么重要了;重要的是,该功能会显示一个“关于”对话框DialogBox
。
在对话框中创建一个 EDIT 控件,并将其子类化:
WNDPROC OriginalEditWndProc;
// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
{
HWND hEdit = CreateWindowExW(0, L"EDIT", NULL, WS_VISIBLE | WS_CHILD | ES_MULTILINE, 0, 0, 100, 100, hDlg, NULL, NULL, NULL);
OriginalEditWndProc = (WNDPROC)GetWindowLongPtrW(hEdit, GWLP_WNDPROC);
SetWindowLongPtrW(hEdit, GWLP_WNDPROC, (LONG_PTR)EditSubclassProc);
return (INT_PTR)TRUE;
}
}
return (INT_PTR)FALSE;
}
使用这个子类WNDPROC
:
static LRESULT CALLBACK EditSubclassProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_GETDLGCODE)
{
OutputDebugStringA("WM_GETDLGCODE\r\n");
return OriginalEditWndProc(hWnd, message, wParam, lParam) | DLGC_WANTTAB /* this should make it work */;
}
else if (message == WM_KEYDOWN)
{
if (wParam == VK_TAB)
{
OutputDebugStringA("got VK_TAB\r\n");
return OriginalEditWndProc(hWnd, message, wParam, lParam);
}
}
return OriginalEditWndProc(hWnd, message, wParam, lParam);
}
(请注意,使用 comctl32 没有任何区别SetWindowSubclass
。还要注意,此对话框不起作用,因为它不处理 aWM_COMMAND
以便能够关闭它,但这无关紧要。)
观察:
- 每次按下选项卡时,我们都会得到“got VK_TAB”。
- 如果
ES_MULTILINE
存在,则键盘焦点转到“关于”对话框的“确定”按钮。 - 如果
ES_MULTILINE
被删除,则在按下 tab 时不会发生任何事情。 - 返回
DLGC_WANTMESSAGE
而不是仅仅DLGC_WANTTAB
改变任何东西。
此外:如果 About 对话框不显示为对话框(换句话说,IsDialogMessage
未调用),而是作为常规窗口显示,则行为不同。
通过修改处理程序来更改对话框的显示方式IDM_ABOUT
:
case IDM_ABOUT:
{
//DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
HWND hDlg = CreateDialogParamW(NULL, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About, 0);
ShowWindow(hDlg, SW_SHOW);
break;
}
观察:
- 第一次显示对话框时有
WM_GETDLGCODE
一个(原因未知),然后不再显示这些消息(如预期的那样)。 - 每次按下选项卡时,我们都会得到“got VK_TAB”。
- 如果
ES_MULTILINE
存在,则在文本中插入一个选项卡(期望的行为)。 - 如果
ES_MULTILINE
被删除,则在按下 tab 时不会发生任何事情(与以前相同)。 - 返回
DLGC_WANTMESSAGE
而不是仅仅DLGC_WANTTAB
改变任何东西。
首先,这些观察与雷蒙德的文章相冲突。如前所述,那篇文章建议我应该默认将制表符插入到文本中,而不做任何事情。这不是发生的事情。
其次,WM_GETDLGCODE
确实像宣传的那样工作,我们确实得到WM_KEYDOWN
了VK_TAB
. 我痛苦地通过反汇编调试,发现:
- 控件有两种不同
WNDPROC
,EDIT
一种用于单行,一种用于多行。 - 收到
WM_KEYDOWN
withVK_TAB
后,多行版本 (MLWndProc
iirc) 最终调用MLKeyDown
. 然后该函数发送一条WM_NEXTDLGCTL
消息。这是罪魁祸首:
...
765D6DEA push ebx
765D6DEB push 0Dh
765D6DED push 100h
765D6DF2 push esi
765D6DF3 jmp MLKeyDown+1B6h (765D6D4Bh)
765D6DF8 cmp edx,1
765D6DFB jne MLKeyDown+270h (765D6E05h)
765D6DFD push ecx
765D6DFE push 9
765D6E00 jmp MLKeyDown+3C0h (765D6F55h)
765D6E05 test dword ptr [edi+68h],40000h
765D6E0C je MLKeyDown+648h (765D71DDh)
765D6E12 xor eax,eax
765D6E14 cmp edx,2
765D6E17 push 0
765D6E19 sete al
765D6E1C push eax
765D6E1D push 28h ; WM_NEXTDLGCTL
765D6E1F push dword ptr [edi+58h]
765D6E22 call SendMessageW (7658BB20h)
因此,控件似乎EDIT
坚持这种行为。
不过这里有些不对劲。我不可能是第一个遇到这个问题的人,而且,Raymond 的文章再次表明,这首先不应该发生。
最后:
使用 Common Controls 库的 v6(用于获得“现代”视觉风格 [对于“现代”的非常谦虚的定义])不会改变任何东西:
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
解决方案
如果您希望多行编辑控件过程VK_TAB
就像它不在对话框内一样 - 无需传递WM_GETDLGCODE
给编辑控件,而是自行处理。所以解决方案可以是下一个 -
随叫随到WM_INITDIALOG
_
SetWindowSubclass(GetDlgItem(hwndDlg, IDC_EDIT1), sSubclassProc, 0, 0);
(当然是IDC_EDIT1
你的实际编辑ID)
并且sSubclassProc
可以非常简单:
static LRESULT CALLBACK sSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR /*dwRefData*/)
{
switch (uMsg)
{
case WM_GETDLGCODE:
return DLGC_WANTCHARS|DLGC_HASSETSEL|DLGC_WANTALLKEYS|DLGC_WANTARROWS;
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, sSubclassProc, uIdSubclass);
break;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
在此编辑过程VK_TAB
键之后。
无论如何,它确实得到了VK_TAB
(内部WM_KEYDOWN
和)。WM_CHAR
但是如何处理这个 - 取决于一些内部标志。它可以插入制表位位置 xor 向WM_NEXTDLGCTL
父级发送消息。此标志(您在汇编代码中查看它 - 这是test dword ptr [edi+68h],40000h
在处理WM_GETDLGCODE
消息期间设置的行(对话框在处理时将此消息发送给控件WM_ACTIVATE
)
所以你的错误是一致的
return OriginalEditWndProc(hWnd, message, wParam, lParam) | DLGC_WANTTAB ;
你打电话OriginalEditWndProc
,它设置了标志(40000 in [this+68h])
推荐阅读
- push-notification - 注销后停止推送通知
- javascript - 如何从表中获取输入值(无 ID)
- python - 熊猫数据框“ValueError:无法从重复轴重新索引”重复索引强力解决方案?
- c# - 您如何在不使用键和值的情况下格式化查询字符串?
- elasticsearch - 用于显示时间序列索引价格变化的弹性搜索高级查询
- javascript - 如何在滚动时更改 ThreeJs 背景
- asp.net-core - 防止 Kestrel 在端口使用时崩溃
- python-3.x - 如何在python中添加笛卡尔集
- printing - 通过树莓派设置打印机服务器时,我的打印机不打印文档
- json - 如何获取所选动作表选择器Swift的索引路径