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_MULTILINE is present, then the keyboard focus goes to the OK button of the About dialog.
If ES_MULTILINE is removed, then nothing happens upon pressing tab.
Returning DLGC_WANTMESSAGE instead of merely DLGC_WANTTAB doesn'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_GETDLGCODE when 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_MULTILINE is present, a tab is inserted into the text (desired behavior).
If ES_MULTILINE is removed, then nothing happens upon pressing tab (same as before).
Returning DLGC_WANTMESSAGE instead of merely DLGC_WANTTAB doesn'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 WNDPROC in the EDIT control, one for single line and one for multiline.
Upon receiving the WM_KEYDOWN with VK_TAB, the multiline version (MLWndProc iirc) eventually calls MLKeyDown. That function then sends a WM_NEXTDLGCTL message. 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='*'\"")
Here is a repository with the entire project
if you want that multi-line edit control process VK_TAB as if it is not inside a dialog - need not pass WM_GETDLGCODE to edit control, but process it by self. so solution can be next -
on WM_INITDIALOG call
SetWindowSubclass(GetDlgItem(hwndDlg, IDC_EDIT1), sSubclassProc, 0, 0);
(of course in place IDC_EDIT1 your actual edit id)
and sSubclassProc can be very simply:
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);
}
after this edit process VK_TAB key.
really it got VK_TAB (inside WM_KEYDOWN and WM_CHAR) in any case. but how process this - depend from some internal flag. it can insert tab-stop positions xor send WM_NEXTDLGCTL message to parent. this flag (you view it in assembly code - this is test dword ptr [edi+68h],40000h line is set during handle WM_GETDLGCODE message (dialog send this message to controls when process WM_ACTIVATE)
so your error was in line
return OriginalEditWndProc(hWnd, message, wParam, lParam) | DLGC_WANTTAB ;
you call OriginalEditWndProc and it set flag (40000 in [this+68h])
Related
Basically, I want to write a program that shows in a small window the color of the pixel currently pointed by the mouse cursor.
Of course, I could poll the mouse cursor position once in a while, but I would like to opt to a mechanism that calls my code when the mouse cursor has moved, regardless whether it's pointing the current window or not.
Is there some WinAPI trickery that could achieve that functionality?
After some search, I found this:
HHOOK mouseHook =
SetWindowsHookExA(
WH_MOUSE_LL,
LowLevelMouseProc,
hInstance,
0);
...
LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
if (wParam == WM_MOUSEMOVE) {
// Notify me.
}
return 0;
}
I'm learning Win32 programming
I used LoadImage(...) to set the small icon for the window but it doesn't seem to be working and I cannot pinpoint the problem
void AddIcons(HWND hw)
{
HICON hi = LoadImage(NULL, "ICON.bmp", IMAGE_BITMAP, 16, 16, LR_LOADFROMFILE);
printf("Icon initialized\n");
if(hi)
{
printf("%x\n", hi);
SendMessage(hw, WM_SETICON, ICON_SMALL, (LPARAM)hi);
}
}
And the windows procedure:
LRESULT CALLBACK WndProc(HWND handle, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_SETICON: printf("REQUEST RECEIVED\n");
}
.
. // AddIcons() is called here
. // DefWindowProc() handles the message at the end
}
I get all three print statements including REQUEST RECEIVED.
However, the icon is still the default. Where is the problem occuring?
I am aware that I can use keep a resource file and use LoadIcon(..) but I am more comfortable doing everything programatically. Which begs a second question:
Can everything that can be done using resource(.rc) files be done programmatically?
If so, is one method inherently better than the other?
As the title says, I would like to move the window only when the user will drag it from a portion of the client area. This will be an imitation of the normal caption bar movement and it's because my form is custom and it doesn't have any title or caption bars. At the moment, I use the code as follows:
...
case WM_NCHITTEST:
return HTCAPTION;
and that works fine for making the user able to move the window no matter where he drags from. I would like to limit this possibility (only the top of the window will allow movement). I haven't tried checking the position of the mouse pressed because I don't know how to do it in the WM_NCHITTEST message.
I use plain Win32 (winapi) C code (no MFC or anything else at the moment) in Visual Studio 2015.
You will run into trouble if you just return HTCAPTION in response to all WM_NCHITTEST messages. You will break things like scrollbars, close buttons, resizing borders, etc. that are all implemented via different HT* values.
You have the right idea, though. You want to make the client area of your window draggable, so you need to trick Windows into thinking that your client area is actually the caption area (which, as you know, is draggable). That code looks like this:
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// ...
case WM_NCHITTEST:
{
// Call the default window procedure for default handling.
const LRESULT result = ::DefWindowProc(hWnd, uMsg, wParam, lParam);
// You want to change HTCLIENT into HTCAPTION.
// Everything else should be left alone.
return (result == HTCLIENT) ? HTCAPTION : result;
}
// ...
}
However, based on the image in your question, you appear to want to restrict this to only a certain region of your window. You will need to define exactly what that area is, and then hit-test to see if the user has clicked in that area. Assuming that rcDraggable is a RECT structure that contains the bounds of the red box shown in your image (in screen coordinates), you can use the following code:
static RECT rcDraggable = ...
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// ...
case WM_NCHITTEST:
{
// Call the default window procedure for default handling.
const LRESULT result = ::DefWindowProc(hWnd, uMsg, wParam, lParam);
// Get the location of the mouse click, which is packed into lParam.
POINT pt;
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
// You want to change HTCLIENT into HTCAPTION for a certain rectangle, rcDraggable.
// Everything else should be left alone.
if ((result == HTCLIENT) && (PtInRect(&rcDraggable, pt))
{
return HTCAPTION;
}
return result;
}
// ...
}
If you define rcDraggable in terms of client coordinates, you will need to convert it to screen coordinates before doing the hit-testing in response to WM_NCHITTEST. To do that, call the MapWindowPoints function, like so:
RECT rc = rcDraggable;
MapWindowPoints(hWnd, /* a handle to your window */
NULL, /* convert to screen coordinates */
reinterpret_cast<POINT*>(&rc),
(sizeof(RECT) / sizeof(POINT)));
You can call some magic code in WM_LBUTTONDOWN handler, AFAIR this:
ReleaseCapture();
SendMessage(yourWindowHandle, WM_SYSCOMMAND, 0xf012, 0) ;
I used this method a few years ago in Delphi and Windows XP. I think it must be similar for c++. Of course, you can check x and y before doing this.
Normally, even when using double buffering, when resizing a window, it seems that it's inevitable that the flickering will happen.
Step 1, the original window.
Step 2, the window is resized, but the extra area hasn't been painted.
Step 3, the window is resized, and the extra area has been painted.
Is it possible somehow to hide setp 2? Can I suspend the resizing process until the painting action is done?
Here's an example:
#include <Windows.h>
#include <windowsx.h>
#include <Uxtheme.h>
#pragma comment(lib, "Uxtheme.lib")
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL MainWindow_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct);
void MainWindow_OnDestroy(HWND hWnd);
void MainWindow_OnSize(HWND hWnd, UINT state, int cx, int cy);
void MainWindow_OnPaint(HWND hWnd);
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wcex = { 0 };
HWND hWnd;
MSG msg;
BOOL ret;
wcex.cbSize = sizeof(wcex);
wcex.lpfnWndProc = WindowProc;
wcex.hInstance = hInstance;
wcex.hIcon = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
wcex.hCursor = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
wcex.lpszClassName = TEXT("MainWindow");
wcex.hIconSm = wcex.hIcon;
if (!RegisterClassEx(&wcex))
{
return 1;
}
hWnd = CreateWindow(wcex.lpszClassName, TEXT("CWin32"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, NULL, hInstance, NULL);
if (!hWnd)
{
return 1;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while ((ret = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (ret == -1)
{
return 1;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
HANDLE_MSG(hWnd, WM_CREATE, MainWindow_OnCreate);
HANDLE_MSG(hWnd, WM_DESTROY, MainWindow_OnDestroy);
HANDLE_MSG(hWnd, WM_SIZE, MainWindow_OnSize);
HANDLE_MSG(hWnd, WM_PAINT, MainWindow_OnPaint);
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
BOOL MainWindow_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)
{
BufferedPaintInit();
return TRUE;
}
void MainWindow_OnDestroy(HWND hWnd)
{
BufferedPaintUnInit();
PostQuitMessage(0);
}
void MainWindow_OnSize(HWND hWnd, UINT state, int cx, int cy)
{
InvalidateRect(hWnd, NULL, FALSE);
}
void MainWindow_OnPaint(HWND hWnd)
{
PAINTSTRUCT ps;
HPAINTBUFFER hpb;
HDC hdc;
BeginPaint(hWnd, &ps);
hpb = BeginBufferedPaint(ps.hdc, &ps.rcPaint, BPBF_COMPATIBLEBITMAP, NULL, &hdc);
FillRect(hdc, &ps.rcPaint, GetStockBrush(DKGRAY_BRUSH));
Sleep(320); // This simulates some slow drawing actions.
EndBufferedPaint(hpb, TRUE);
EndPaint(hWnd, &ps);
}
Is it possible to eliminate the flickering?
When the window is updated during a drag operation, then the OS has to show something in the extended window region. If you can't provide anything then it will show the background until you do. Since you didn't specify any background you get blackness. Surely you ought to be specifying a background brush? Simply adding the following to your code makes the behaviour more palatable:
wcex.hbrBackground = GetStockBrush(DKGRAY_BRUSH);
However, if you take as long as 320ms to respond to a WM_PAINT then you ruin the resize UI for the user. It becomes jerky and unresponsive. The system is designed around the assumption that you can paint the window quickly enough for dragging to feel smooth. The right way to fix your problem is to make WM_PAINT run in a reasonable time.
If you really can't achieve quick enough painting for smooth dragging then I suggest a couple of alternatives:
Disable window updates during dragging. I'm sure this can be done for individual windows, but I can't remember how to do it off the top of my head.
Paint something fake whilst a resize/drag is active, and postpone the real painting until when the resize/drag has completed. Listening for WM_ENTERSIZEMOVE and WM_EXITSIZEMOVE are the keys to this. This Microsoft sample program illustrates how to do that: https://github.com/microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/winui/fulldrag/
Use WM_SIZING instead of WM_SIZE and don't forget about WM_ERASEBKGND.
If you go into the System Properties control panel applet, choose the Advanced tab, and then click Settings... in the Performance group box, you'll see a checkbox setting called Show window contents while dragging. If you uncheck that and try resizing a window, you'll see that only the window frame moves until you complete the drag operation, and then the window repaints just once at the new size. This is how window sizing used to work when we had slow, crufty computers.
Now we don't really want to change the setting globally (which you would do by calling SystemParametersInfo with SPI_SETDRAGFULLWINDOWS, but don't really do that because your users won't like it).
What happens when the user grabs the resize border is that the thread enters a modal loop controlled by the window manager. Your window will get WM_ENTERSIZEMOVE as that loop begins and WM_EXITSIZEMOVE when the operation is complete. At some point you'll also get a WM_GETMINMAXINFO, which probably isn't relevant to what you need to do. You'll also get WM_SIZING, WM_SIZE messages rapidly as the user drags the sizing frame (and the rapid WM_SIZEs often lead to WM_PAINTs).
The global Show window contents while dragging setting is responsible for getting the rapid WM_SIZE messages. If that setting is off, you'll just get one WM_SIZE message when it's all over.
If your window is complicated, you probably have layout code computing stuff (and maybe moving child windows) in the WM_SIZE handler and a lot of painting code in the WM_PAINT handler. If all that code is too slow (as your sample 320 ms delay suggests), then you'll have a flickery, jerky experience.
We really don't want to change the global setting, but it does inspire a solution to your problem:
Do simpler drawing during the resize operation and then do your (slower) complex drawing just once when the operation is over.
Solution:
Set a flag when you see the WM_ENTERSIZEMOVE.
Change your WM_SIZE handler to check the flag and do nothing if it's set.
Change your WM_PAINT handler to check the flag and do a simple, fast fill of the window in a solid color if it's set.
Clear the flag when you see WM_EXITSIZEMOVE, and then trigger your layout code and invalidate your window so that everything gets updated based on the final size.
If your slow window is a child rather than your application's top-level window, you'll have to signal the child window when the top-level window gets the WM_ENTERSIZEMOVE and WM_EXITSIZEMOVE in order to implement steps 1 and 4.
Yes you can entirely delete flickering :)
You can do all the window message handling in one thread, and painting its context in another. Your window always keeps responsive. It works great, can not understand why this is not established best practice.
If you bind a Direct3D context for example, it can have an instant scaling while resizing, completely without having the context updated!
My code looks like this:
int WINAPI wWinMain( HINSTANCE a_hInstance, HINSTANCE a_hPrevInstance, LPWSTR a_lpCmdLine, int a_nCmdShow )
{
Win32WindowRunnable* runnableWindow=new Win32WindowRunnable(a_hInstance, a_nCmdShow);
IThread* threadWindow=new Win32Thread(runnableWindow);
threadWindow->start();
Scene1* scene=new Scene1(runnableWindow->waitForWindowHandle());
IThread* threadRender=new Win32Thread(scene);
threadRender->start();
threadWindow->join();
threadRender->pause();
threadRender->kill();
delete runnableWindow;
return 0;
}
Full source example here:
https://github.com/TheWhiteAmbit/TheWhiteAmbit/blob/master/Win32App/Win32Main.cpp
I am trying to change the keys my keyboard sends to applications. I've already created a global hook and can prevent the keys I want, but I want to now send a new key in place. Here's my hook proc:
LRESULT __declspec (dllexport) HookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
int ret;
if(nCode < 0)
{
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
kbStruct = (KBDLLHOOKSTRUCT*)lParam;
printf("\nCaught [%x]", kbStruct->vkCode);
if(kbStruct->vkCode == VK_OEM_MINUS)
{
printf(" - oem minus!");
keybd_event(VK_DOWN, 72, KEYEVENTF_KEYUP, NULL);
return -1;
}
else if(kbStruct->vkCode == VK_OEM_PLUS)
{
printf(" - oem plus!");
keybd_event(VK_UP, 75, KEYEVENTF_KEYUP, NULL);
return -1;
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
I've tried using SendMessage and PostMessage with GetFocus() and GetForegroudWindow(), but can't figure out how to create the LPARAM for WM_KEYUP or WM_KEYDOWN. I also tried keybd_event(), which does simulate the keys (I know because this hook proc catches the simulated key presses), including 5 or 6 different scan codes, but nothing affects my foreground window.
I am basically trying to turn the zoom bar on my ms3200 into a scroll control, so I may even be sending the wrong keys (UP and DOWN).
Calling keybd_event is correct. If all you're doing is a key up, maybe the window processes the key down message instead. You really need to send a key down followed by a key up:
keybd_event(VK_UP, 75, 0, NULL);
keybd_event(VK_UP, 75, KEYEVENTF_KEYUP, NULL);
Or, better yet, send the key down when the OEM key goes down and a key up when the OEM key goes up. You can tell the down/up state by kbStruct->flags & LLKHF_UP.
You may wish to use SendInput, as keybd_event as has been superseded. The MSDN Magazine article C++ Q&A: Sending Keystrokes to Any App has a useful example.
You might want to try Control-UpArrow and Control-DownArrow instead of Up and Down. However this doesn't seem to work for all applications, and even on application where it does work, it may depend on where the focus is.