首页 > 解决方案 > 尽管 DLGC_WANTTAB 多行 EDIT 对话框控件不使用制表符

问题描述

我想要一个多行EDIT控件,它是对话框的子控件,将选项卡作为常规文本输入(而不是切换到下一个控件)。

根据多个资源,这样做的正确方法是处理WM_GETDLGCODE并返回DLGC_WANTTAB(或检查 + DLGC_WANTMESSAGE)。

看:

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以便能够关闭它,但这无关紧要。)

观察:

此外:如果 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确实像宣传的那样工作,我们确实得到WM_KEYDOWNVK_TAB. 我痛苦地通过反汇编调试,发现:

...
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='*'\"")

这是一个包含整个项目的存储库

标签: cwindowswinapiinput

解决方案


如果您希望多行编辑控件过程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])


推荐阅读