So I am working on making a calculator with a GUI for a class project and I am currently working on setting up my input detection so I can filter out what keystokes I want to be included in user input and which I want to exclude. I have been debugging it along the way which has been going smoothly up until about 10 complies ago when the wParam value that gets passed in switched from being a Hex value to being a Dec value. I didn't make any significant changes between when it was sending the code in Hex and now as it sends it in Dec so I can't figure out why it changed, in fact I believe it changed after I finished writing a comment to myself in one of the functions I am filtering the message with so I'm pretty confused as to why it changed. The relevant code is posted below (I can't just undo the changes I made because at some point my IDE closed), if someone could tell me why it changed, how to change it back, and if I'm even filtering keystrokes the correct way that would be much appreciated.
The ShiftPressed variable is stored in the class that is using this WinProc method and is accessible to all of these functions. All the code below was working with the wParam being sent in Hex at one point but after I finished the longer comment in the CheckKeyForOp() method, the wParam started being sent in Dec.
LRESULT CALLBACK EditBoxClass::WinProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_KEYUP:
if( wParam == VK_SHIFT )
ShiftPressed = FALSE;
return 0;
case WM_KEYDOWN:
if( wParam == VK_SHIFT )
{
ShiftPressed = TRUE;
return 1;
}
else if( CheckKeyForNum( wParam ) )
return 1;
else if( CheckKeyForOp( wParam ) )
return 1;
else
return 0;
...
}
BOOL EditBoxClass::CheckKeyForNum( WPARAM wParam )
{
switch( wParam )
{
case 0x30: case VK_NUMPAD0:
case 0x31: case VK_NUMPAD1:
case 0x32: case VK_NUMPAD2:
case 0x33: case VK_NUMPAD3:
case 0x34: case VK_NUMPAD4:
case 0x35: case VK_NUMPAD5:
case 0x36: case VK_NUMPAD6:
case 0x37: case VK_NUMPAD7:
case 0x38: case VK_NUMPAD8:
case 0x39: case VK_NUMPAD9:
default: return FALSE;
}
}
BOOL EditBoxClass::CheckKeyForOp( WPARAM wParam )
{
switch( wParam )
{
case VK_OEM_2: // For both of these keys, VK_OEM_2: "/ and ?" and VL_OEM_MINUS: "- and _" the
case VK_OEM_MINUS: // regular keystroke can be used in the calculator but the "second version"
if( ShiftPressed == TRUE ) return FALSE; // denoted by holding the shift key during the keystroke, cannot be used; so filter.
case VK_OEM_PLUS: // This key acts in the same way the VK_OEM_MINUS key does, having both "+/=" register under the
// VK code VK_OEM_PLUS, but both are operators used by the calculator so allow both
case VK_ADD: case VK_SUBTRACT:
case VK_MULTIPLY: case VK_DIVIDE:
case VK_DECIMAL: case VK_OEM_PERIOD: return TRUE;
default: return FALSE;
}
}
Messages do not give you hex/decimal, they just give you binary numbers, which you can then interpret as hex/decimal as needed.
The only error I see in this code is that CheckKeyForNum() is not returning TRUE for allowed number keys:
BOOL EditBoxClass::CheckKeyForNum( WPARAM wParam )
{
switch( wParam )
{
case 0x30: case VK_NUMPAD0:
case 0x31: case VK_NUMPAD1:
case 0x32: case VK_NUMPAD2:
case 0x33: case VK_NUMPAD3:
case 0x34: case VK_NUMPAD4:
case 0x35: case VK_NUMPAD5:
case 0x36: case VK_NUMPAD6:
case 0x37: case VK_NUMPAD7:
case 0x38: case VK_NUMPAD8:
case 0x39: case VK_NUMPAD9:
return TRUE; // <-- add this
default: return FALSE;
}
}
Related
I want to subclass the edit control into a specific case of a masked edit - something that accepts five characters of user input, and displays colons after the first and third characters. I can imagine two basic approaches to this.
I could have the text that the edit control stores be the text I want displayed. In this case, I would need to set the text to L" : : " to begin with, and override the messages that detect user input so I could copy it into the correct slots in that string. However, I don't know how to be sure which messages those are. I assume WM_KEYDOWN is one, but if there are others, and I don't think of them all, input that triggers the messages I missed would incorrectly defer to the edit control's default handling.
Alternatively, I could have the text that the edit control stores be the text the user enters - no colons. In that case, I would need to override the way the control is displayed so I could to generate a string that includes colons based on the stored text, and draw that when drawing the control. I assume this would mean replacing the WM_PAINT handling. The problem with that is that it would seem to require redefining everything about how the control looks myself, when the text is the only part I want to change. I'm not confident I could do that perfectly, and I would certainly rather not.
How should I approach this?
Edit: I've tried overriding WM_PAINT like this:
INT_PTR CALLBACK MaskedEditProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
if (message == WM_PAINT)
{
WCHAR userInput[6];
Edit_GetText(hwndDlg, userInput, 6);
WCHAR displayString[]{L" : : "};
int userInputLength{ Edit_GetTextLength(hwndDlg) };
switch (userInputLength)
{
case 5:
displayString[6] = userInput[4];
case 4:
displayString[5] = userInput[3];
case 3:
displayString[3] = userInput[2];
case 2:
displayString[2] = userInput[1];
case 1:
displayString[0] = userInput[0];
}
Edit_SetText(hwndDlg, displayString);
DefSubclassProc(hwndDlg, message, wParam, lParam);
Edit_SetText(hwndDlg, userInput);
return TRUE;
}
return DefSubclassProc(hwndDlg, message, wParam, lParam);
}
This seems to basically work, except for some reason it causes the displayed text to flicker.
Edit 2: I set the control's text to L"0:00:00" from its parent window, and gave it the following window procedure:
INT_PTR CALLBACK MaskedEditProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
switch (message)
{
case WM_CUT:
return 0;
case WM_PASTE:
return 0;
case WM_KEYDOWN:
if (wParam == VK_DELETE)
{
WORD caretPosition{ LOWORD(SendMessage(hwndDlg,EM_GETSEL,0,0)) };
switch (caretPosition)
{
case 7:
return 0;
case 1:
case 4:
SendMessage(hwndDlg, EM_SETSEL, caretPosition + 1, caretPosition + 2);
break;
default:
SendMessage(hwndDlg, EM_SETSEL, caretPosition, caretPosition + 1);
}
return DefSubclassProc(hwndDlg, WM_CHAR, '0', 0);
}
case WM_CHAR:
if (wParam == '\b')
{
WORD caretPosition{ LOWORD(SendMessage(hwndDlg,EM_GETSEL,0,0)) };
switch (caretPosition)
{
case 0:
return 0;
case 2:
case 5:
SendMessage(hwndDlg, EM_SETSEL, caretPosition - 2, caretPosition - 1);
break;
default:
SendMessage(hwndDlg, EM_SETSEL, caretPosition - 1, caretPosition);
}
return DefSubclassProc(hwndDlg, WM_CHAR, '0', 0);
}
else if (iswdigit(wParam))
{
WORD caretPosition{ LOWORD(SendMessage(hwndDlg,EM_GETSEL,0,0)) };
switch (caretPosition)
{
case 1:
case 4:
SendMessage(hwndDlg, EM_SETSEL, caretPosition + 1, caretPosition + 2);
break;
default:
SendMessage(hwndDlg, EM_SETSEL, caretPosition, caretPosition + 1);
}
}
}
return DefSubclassProc(hwndDlg, message, wParam, lParam);
}
This seems to work as intended, though my backspace key has always been broken, so I haven't tested that part. The control is also set not to accept non-digit input, so I don't think the fact that I don't handle those here should break anything.
I suggest you handle only WM_CHAR (and possibly custom messages related to your masking). Leave the arrow keys alone, let the edit control handle them and in your WM_CHAR handler query for the caret placement. In your WM_CHAR when the placement would normally reach a colon send a EM_SETSEL that skips to the next character location.
So I'm trying to build a simple input-handler to work against windows message-loop. But for some reason when I test the case of WM_MBUTTONDOWN I get the same wParam as if I press the "Shift-key". The code I get when trying to click my middle mouse button is 16 in decimal or 0x10 in hexadecimal.
When I looked into it I can see that MBUTTON should be 0x04 but I don't get that.
Anyone encountered this before?
This is the code that I'm using in the WndProc function of my Win32 application.
(Ofcourse there is more cases that check if they're up and so on, but didn't feel it was relevant to the question)
case WM_MBUTTONDOWN:
if (wParam < 256)
{
globalInputManager.SetKeyIsDown(static_cast<uint8_t>(wParam));
OutputDebugStringA(std::to_string(wParam).c_str());
}
break;
case WM_KEYDOWN:
if (wParam < 256)
{
globalInputManager.SetKeyIsDown(static_cast<uint8_t>(wParam));
OutputDebugStringA(std::to_string(wParam).c_str());
}
break;
I have an application that already deals with the generating of tooltips. I am modifying a CWnd derived class that has a parent frame. It doesn't implement tooltips.
From this, I can get tooltips to show up by adding the following code:
BEGIN_MESSAGE_MAP(CMyWindow, CWnd)
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify)
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify)
END_MESSAGE_MAP()
BOOL CMyWindow::OnToolTipNotify(UINT id, NMHDR* pNMHDR, LRESULT* pResult)
{
UNREFERENCED_PARAMETER(id);
UNREFERENCED_PARAMETER(pResult);
// need to handle both ANSI and UNICODE versions of the message
TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
CStringA strTipText;
UINT_PTR nID = pNMHDR->idFrom;
if (pNMHDR->code == TTN_NEEDTEXTA && (pTTTA->uFlags & TTF_IDISHWND) ||
pNMHDR->code == TTN_NEEDTEXTW && (pTTTW->uFlags & TTF_IDISHWND))
{
// idFrom is actually the HWND of the tool
nID = ::GetDlgCtrlID((HWND)nID);
}
if (nID != 0) // will be zero on a separator
strTipText.Format("Control ID = %d", nID);
if (pNMHDR->code == TTN_NEEDTEXTA)
{
strncpy_s(pTTTA->szText, sizeof(pTTTA->szText), strTipText,
strTipText.GetLength() + 1);
}
else
{
::MultiByteToWideChar(CP_ACP, 0, strTipText, strTipText.GetLength() + 1,
pTTTW->szText, sizeof(pTTTW->szText) / (sizeof pTTTW->szText[0]));
}
return TRUE; // message was handled
}
I can use GetParentFrame() to get the frame window, so I'd like to leverage the tooltip code that is already in place so that I get a consistent look. Is there some way that I can forward the TTN_NEEDTEXT message so that it gets handled by the frame window?
In your message map, you use ON_NOTIFY_whatever, which should hint that the message that TTN_NEEDTEXT uses is WM_NOTIFY — and indeed this is the case. So you can just produce the WM_NOTIFY yourself and send it to the parent.
The documentation for WM_NOTIFY says wParam is the control's identifier and lParam is the NMHDR pointer. There's an example on the bottom of the page that shows wParam is just the idFrom member of the NMHDR, so you have everything you need to reconstruct the message:
LRESULT lr = this->GetParentFrame()->SendMessage(WM_NOTIFY, pNMHDR->idFrom, (LPARAM) pNMHDR);
When you should issue this call is up to what you need to do. In this case, you would probably want to make it the first call, override your string, and return TRUE. I'm not fully sure, though.
Note: if MFC provides a function to do this, I don't know it; I personally don't use MFC, but the concepts are the same.
So what about the return value from that SendMessage()? Well, you can return it via pResult (for instance, if you're just tweaking the parent's alterations slightly), or you can ignore it and return a custom value. But for TTN_NEEDTEXT, it won't matter; TTN_NEEDTEXT doesn't care what the LRESULT is.
I gave an English explanation of my problem below but it is a visual issue so if you don't want to read it all just look at the picture at the bottom).
I'm working on building a reverse polish notation calculator for my class and I just completed having the button controls on my GUI be able to append their values to the edit control which works fine, but the caret is doing something weird and I can't find any information on it.
I send a custom message to the edit control in which it finds the length of the current text in the control and then places the caret at the end of the text so I can then add what text needs to be added (it is right aligned with ES_RIGHT), which again works just fine, but when the caret is in the right most place it can be, it is placed practically right through the middle of most any number.
This only seems to happen in the right most place the caret can be (i.e. anywhere else the caret sits directly to the right of the preceding char, as it should) and I have tried replacing the caret all the way to the right using code, placing it using my keyboard/mouse, and tried adjusting the dimensions of the window in hopes that it was just an offset of the width I defined for it that caused the last place to be off slightly, but the problem persists and it makes it hard to read the last char in the text field.
Relevant Code:
LRESULT CALLBACK EditBoxClass::WinProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_COMMAND:
break;
case WM_APPEND_EDIT:
/* Get current length of text in the box */
index = new int( GetWindowTextLength (hWnd) );
SetFocus( hWnd );
/* Set the caret to the end of the text in the box */
SendMessage( hWnd, EM_SETSEL, (WPARAM)index, (LPARAM)index );
/* "Replace" the selection (the selection is actually targeting
nothing and just sits at the end of the text in the box)
with the passed in TCHAR* from the button control that
sent the WM_APPEND_EDIT message */
SendMessage( hWnd, EM_REPLACESEL, 0, lParam );
break;
}
return CallWindowProc( EditClassStruct.GetOldProc(), hWnd, msg, wParam, lParam );
}
Picture of problem:
After facing the same problem and presenting my first approach in this answer, I'll now provide two well working solutions. I think there is no other way to fix this glitch properly (unless you're a Microsoft programmer who is responsible for this part of the WinAPI).
I was wondering how to fix this problem on edit controls created with ES_MULTILINE but this glitch seems to be only a problem on single-line edit controls (tested on Windows 7 64-bit). Enabling Visual Styles is also helpful but the problem still remains (the offset is at least not so obvious).
Explanation
Normally, when the caret is at the farthest right position it's x value (provided by GetCaretPos ()) should be equal to the rect.right value provided by EM_GETRECT (when the edit control was created with ES_RIGHT). Due to unknown reasons this is not the case. So you have to check if the caret position is at least in the near of the rect.right value (but not farther away than the last letter is wide). So you have two possibilities to fulfill this task:
You must calculate the width of the outer right character using GetTextExtentPoint32 (), subtract it from the rect.right value provided by calling SendMessage () with EM_GETRECT and check whether the x position of the caret is bigger than the result or not OR
You must calculate the margin between the rect.right value and the outer right caret position (3 in my case) and use this value as a hardcoded offset to do a simple check.
After those steps (regardless which one you have chosen) you have to reposition the caret when necessary.
1. Approach (recommended)
case WM_LBUTTONDOWN: {
TRACKMOUSEEVENT tme = {sizeof (tme), TME_LEAVE, hwnd, HOVER_DEFAULT};
TrackMouseEvent (&tme);
}
break;
case WM_KEYDOWN:
case WM_MOUSELEAVE:
case WM_SETCURSOR: {
DefSubclassProc (hwnd, message, wParam, lParam);
DWORD end;
SendMessage (hwnd, EM_GETSEL, (WPARAM) NULL, (LPARAM) &end);
int len = GetWindowTextLength (hwnd);
if (end < len || len <= 0)
return TRUE;
wchar_t *buffer = new wchar_t[len + 1];
GetWindowText (hwnd, buffer, len + 1);
wchar_t lastChar[] = {buffer[len - 1], '\0'};
delete[] buffer;
SIZE size;
HDC hdc = GetDC (hwnd);
if (hdc == NULL)
return TRUE;
GetTextExtentPoint32 (hdc, lastChar, 1, &size);
ReleaseDC (hwnd, hdc);
POINT pt;
RECT rect;
GetCaretPos (&pt);
SendMessage (hwnd, EM_GETRECT, (WPARAM) 0, (LPARAM) &rect);
if ((rect.right - size.cx) <= pt.x)
SetCaretPos (rect.right, pt.y);
return TRUE;
}
break;
2. Approach (improved original version)
case WM_LBUTTONDOWN: {
TRACKMOUSEEVENT tme = {sizeof (tme), TME_LEAVE, hwnd, HOVER_DEFAULT};
TrackMouseEvent (&tme);
}
break;
case WM_KEYDOWN:
case WM_MOUSELEAVE:
case WM_SETCURSOR: {
DefSubclassProc (hwnd, message, wParam, lParam);
POINT pt;
RECT rect;
GetCaretPos (&pt);
SendMessage (hwnd, EM_GETRECT, (WPARAM) 0, (LPARAM) &rect);
if ((rect.right - pt.x) <= 3)
SetCaretPos (rect.right, pt.y);
return TRUE;
}
break;
You have to subclass the edit controls. Then use this code in their window procedures and enjoy.
In both cases, tracking the mouse event is not absolutely necessary but recommended to completly avoid this glitch. Calling DefSubclassProc () will ensure that the cursor is changed on mouse over.
This may or may not be the cause, but you are misusing EM_SETSEL. You are dynamically allocating (and leaking) an int on the heap and passing a pointer to it as the message parameters, but EM_SETSEL does not expect or use pointers to begin with. So get rid of the dynamic allocation.
Also, the default window proc is not going to know how to handle your WM_APPEND_EDIT message, so there is no point in passing the message to CallWindowProc().
Try this instead:
case WM_APPEND_EDIT:
{
/* Get current length of text in the box */
int index = GetWindowTextLength( hWnd );
SetFocus( hWnd );
/* Set the caret to the end of the text in the box */
SendMessage( hWnd, EM_SETSEL, (WPARAM)index, (LPARAM)index );
/* "Replace" the selection (the selection is actually targeting
nothing and just sits at the end of the text in the box)
with the passed in TCHAR* from the button control that
sent the WM_APPEND_EDIT message */
SendMessage( hWnd, EM_REPLACESEL, 0, lParam );
return 0;
}
That being said, try using EM_GETRECT/EM_SETRECT to expand the right edge of the edit control's formatting rectangle by a few pixels. That should give the caret some extra room to work with.
I can use the SetWindowsHookEx function to hook a custom KeyboardProc. In the hooked procedure I can return 1 to block the keyboard input from reaching the application, however what I'm trying to do is actually modify the data. E.g. the user hits the key A and I'd like to replace that with key B.
But any modifications I make to the WPARAM and LPARAM arguments do not seem to have any effect in the application, it will still receive the original keys. E.g. to show a little pseudocode:
LRESULT KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
memset(&wParam, 0, sizeof(WPARAM));
memset(&lParam, 0, sizeof(LPARAM));
// call the next procedure
return CallNextHookEx(keyHook, nCode, wParam, lParam);
}
No matter what modifications I make to the two parameters it will not have an effect, once the app reads the WM_KEYDOWN message it will receive the original keys as they were hit.
Is there any way I can actually make modifications to the keys?
The WH_KEYBOARD_LL hook enables you to monitor keyboard input events about to be posted in a thread input queue.
From: https://learn.microsoft.com/en-us/windows/win32/winmsg/about-hooks?redirectedfrom=MSDN#wh_debug
This means it is read-only and the params can not be modified.
The solution is to use SendInput, but only when lParam - which should be type-cast to KBDLLHOOKSTRUCT*) - has the LLKHF_INJECTED flag in its flags field.
Here's some D language code copied verbatim from one of my projects:
extern(Windows)
LRESULT LowLevelKeyboardProc(int code, WPARAM wParam, LPARAM lParam)
{
auto kbs = cast(KBDLLHOOKSTRUCT*)lParam;
// generate a new key event only if this key event was user-generated.
if (!(kbs.flags & LLKHF_INJECTED))
{
// Alt == toggle key binding
if (kbs.flags & LLKHF_ALTDOWN)
useKeyMap ^= 1;
if (!useKeyMap)
return CallNextHookEx(keyHook_LL, code, wParam, lParam);
INPUT input;
input.type = INPUT_KEYBOARD;
input.ki.dwFlags = (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) ? 0 : KEYEVENTF_KEYUP;
// replace the key, must be in range 1 to 254
input.ki.wVk = keyMap.get(cast(Key)kbs.vkCode, cast(Key)kbs.vkCode);
SendInput(1, &input, INPUT.sizeof);
return -1;
}
return CallNextHookEx(keyHook_LL, code, wParam, lParam);
}