I want a multiline EDIT control, which is a child of a dialog, to take tabs as regular text input (instead of switching to the next control).
According to multiple resources, the correct way of doing this is to handle WM_GETDLGCODE and returning DLGC_WANTTAB (or checking + DLGC_WANTMESSAGE).
See:
- https://stackoverflow.com/a/16668256/653473
- https://stackoverflow.com/a/42352363/653473
- Raymond Chen's first comment here: https://stackoverflow.com/a/18444839/653473
Raymond's article suggests that what I want is in fact the default behavior, which is not what I'm observing.
Reproduction:
This is based on Visual Studio's default Windows Desktop Application C++ template. If you don't have that for some reason, the contents aren't that important; what's important is that there is an "About" dialog box being shown with the DialogBox function.
Create an EDIT control in the dialog, and subclass that:
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;
}
Using this subclass 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);
}
(Note that it makes no difference to use comctl32's SetWindowSubclass. Note also that this dialog is not functional because it does not handle a WM_COMMAND to be able close it, but that's irrelevant.)
Observations:
- We do get "got VK_TAB" every time tab is pressed.
- If
ES_MULTILINEis present, then the keyboard focus goes to the OK button of the About dialog. - If
ES_MULTILINEis removed, then nothing happens upon pressing tab. - Returning
DLGC_WANTMESSAGEinstead of merelyDLGC_WANTTABdoesn't change anything.
Furthermore: If the About dialog is not displayed as a dialog (in other words, IsDialogMessage is not called), but as a regular window, then the behavior is different.
Change the way the dialog box is shown by modifying IDM_ABOUT handler:
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;
}
Observations:
- There is one
WM_GETDLGCODEwhen the dialog is first displayed (for reasons unknown), and then no more of these messages (as expected). - We do get "got VK_TAB" every time tab is pressed.
- If
ES_MULTILINEis present, a tab is inserted into the text (desired behavior). - If
ES_MULTILINEis removed, then nothing happens upon pressing tab (same as before). - Returning
DLGC_WANTMESSAGEinstead of merelyDLGC_WANTTABdoesn't change anything.
Firstly, these observations clash with Raymond's article. As written before, that article suggests that I should get tab characters inserted into the text by default, without doing anything. That's not what happens.
Secondly, WM_GETDLGCODE does in fact work as advertised, we do get WM_KEYDOWN with VK_TAB. I painfully debugged through the disassembly, and found out that:
- There are 2 different
WNDPROCin theEDITcontrol, one for single line and one for multiline. - Upon receiving the
WM_KEYDOWNwithVK_TAB, the multiline version (MLWndProciirc) eventually callsMLKeyDown. That function then sends aWM_NEXTDLGCTLmessage. This is the culprit:
...
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)
So it seems that the EDIT control insists on this behavior.
Something is not right here though. I cannot possibly be the first person to have that problem, and, again, Raymond's article suggests that this should not be happening in the first place.
Lastly:
Using v6 of the Common Controls library (which is used to get a "modern" visual style [for very modest definitions of "modern"]) doesn't change anything:
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")