Let's say you've just set some text in a spellcheck-enabled rich edit control, and the text has some spelling errors. A split second will go by, spellcheck will kick in, and then the misspelled text will get underlined. But guess what: the rich edit control will actually send an EN_CHANGE notification just for the underlining event (this is assuming you've registered for notifications by doing SendMessage(hwnd, EM_SETEVENTMASK, 0, (LPARAM)ENM_CHANGE)).
Is there a workaround to not get this type of behavior? I've got a dialog with some spellcheck-enabled rich edit controls. And I also want to know when an edit event has taken place, so I know when to enable the "Save" button. Getting an EN_CHANGE notification merely for the spellcheck underlining event is thus a problem.
One option I've considered is disabling EN_CHANGE notifications entirely, and then triggering them on my own in a subclassed rich edit control. For example, when there's a WM_CHAR, it would send the EN_CHANGE notification explicitly, etc. But that seems like a problem, because there are many types of events that should trigger changes, like deletes, copy/pastes, etc., and I'd probably not capture all of them correctly.
Another option I've considered is enabling and disabling EN_CHANGE notifications dynamically. For example, enabling them only when there's focus, and disabling when focus is killed. But that also seems problematic, because a rich edit might already have focus when its text is set. Then the spellcheck underline would occur, and the undesirable EN_CHANGE notification would be sent.
I suppose a timer could be used, too, but I think that would be highly error-prone.
Does anybody have any other ideas?
Here's a reproducible example. Simply run it, and it'll say something changed:
#include <Windows.h>
#include <atlbase.h>
#include <atlwin.h>
#include <atltypes.h>
#include <Richedit.h>
class CMyWindow :
public CWindowImpl<CMyWindow, CWindow, CWinTraits<WS_VISIBLE>>
{
public:
CMyWindow()
{
}
BEGIN_MSG_MAP(CMyWindow)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
COMMAND_CODE_HANDLER(EN_CHANGE, OnChange)
END_MSG_MAP()
private:
LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL& bHandled)
{
bHandled = FALSE;
LoadLibrary(L"Msftedit.dll");
CRect rc;
GetClientRect(&rc);
m_wndRichEdit.Create(MSFTEDIT_CLASS, m_hWnd, &rc,
NULL, WS_VISIBLE | WS_CHILD | WS_BORDER);
INT iLangOpts = m_wndRichEdit.SendMessage(EM_GETLANGOPTIONS, NULL, NULL);
iLangOpts |= IMF_SPELLCHECKING;
m_wndRichEdit.SendMessage(EM_SETLANGOPTIONS, NULL, (LPARAM)iLangOpts);
m_wndRichEdit.SetWindowText(L"sdflajlf adlfjldsfklj dfsl");
m_wndRichEdit.SendMessage(EM_SETEVENTMASK, 0, (LPARAM)ENM_CHANGE);
return 0;
}
LRESULT OnChange(WORD, WORD, HWND, BOOL&)
{
MessageBox(L"changed", NULL, NULL);
return 0;
}
private:
CWindow m_wndRichEdit;
};
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
CMyWindow wnd;
CRect rc(0, 0, 200, 200);
wnd.Create(NULL, &rc);
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
Also, it appears that using EM_SETMODIFY and EM_GETMODIFY don't help. I guess the spellcheck underlining results in a EM_SETMODIFY, so checking that flag in the handler is of no avail.
because documentation about CHANGENOTIFY ( must contains information that is associated with an EN_CHANGE notification code, but not..) is wrong - only research exist.
in my test i view that EN_CHANGE related to Spellcheck received only when rich edit handle WM_TIMER message. so solution is next - subclass richedit and remember (save in class member variable) - when it inside WM_TIMER. than, when we handle EN_CHANGE - check are richedit inside WM_TIMER.
partial POC code. i special show more complex case - if several (more than one) child richedit`s exist in frame or dialog winndow
#include <richedit.h>
class RichFrame : public ZFrameMultiWnd
{
enum { richIdBase = 0x1234 };
bool _bInTimer[2] = {};
public:
protected:
private:
static LRESULT WINAPI SubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
if ((uIdSubclass -= richIdBase) >= _countof(_bInTimer))
{
__debugbreak();
}
bool bTimerMessage = uMsg == WM_TIMER;
if (bTimerMessage)
{
reinterpret_cast<RichFrame*>(dwRefData)->_bInTimer[uIdSubclass] = TRUE;
}
lParam = DefSubclassProc(hWnd, uMsg, wParam, lParam);
if (bTimerMessage)
{
reinterpret_cast<RichFrame*>(dwRefData)->_bInTimer[uIdSubclass] = false;
}
return lParam;
}
virtual BOOL CreateClient(HWND hWndParent, int nWidth, int nHeight, PVOID /*lpCreateParams*/)
{
UINT cy = nHeight / _countof(_bInTimer), y = 0;
UINT id = richIdBase;
ULONG n = _countof(_bInTimer);
do
{
if (HWND hwnd = CreateWindowExW(0, MSFTEDIT_CLASS, 0, WS_CHILD|ES_MULTILINE|WS_VISIBLE|WS_BORDER,
0, y, nWidth, cy, hWndParent, (HMENU)id, 0, 0))
{
SendMessage(hwnd, EM_SETLANGOPTIONS, 0,
SendMessage(hwnd, EM_GETLANGOPTIONS, 0, 0) | IMF_SPELLCHECKING);
SetWindowText(hwnd, L"sdflajlf adlfjldsfklj d");
SendMessage(hwnd, EM_SETEVENTMASK, 0, ENM_CHANGE);
if (SetWindowSubclass(hwnd, SubclassProc, id, reinterpret_cast<ULONG_PTR>(this)))
{
continue;
}
}
return FALSE;
} while (y += cy, id++, --n);
return TRUE;
}
virtual LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_COMMAND:
if (EN_CHANGE == HIWORD(wParam))
{
if ((wParam = LOWORD(wParam) - richIdBase) >= _countof(_bInTimer))
{
__debugbreak();
}
DbgPrint("EN_CHANGE<%x> = %x\n", wParam, _bInTimer[wParam]);
}
break;
case WM_DESTROY:
{
UINT id = richIdBase;
ULONG n = _countof(_bInTimer);
do
{
RemoveWindowSubclass(GetDlgItem(hwnd, id), SubclassProc, id);
} while (id++, --n);
}
break;
case WM_NCDESTROY:
PostQuitMessage(0);
break;
}
return __super::WindowProc(hwnd, uMsg, wParam, lParam);
}
};
Use EM_CANUNDO (maybe also EM_CANREDO) to verify that contents has changed. I hope that spellchecker does't add any undo information.
I recently tried to work around this without subclassing but it was only somewhat successful.
My alternative workaround consists of marking the entire document as CFE_PROTECTED. In the EN_PROTECTED handler ENPROTECTED::msg is WM_NULL when coming from the spell checker and you can set a flag telling yourself to ignore the next EN_CHANGE. To allow actual spelling corrections from the context menu you also need to keep track of menus.
This feels rather fragile but so does tracking WM_TIMER.
Related
I am working on an ImGui project, and am trying to find a way to create custom key binds. I thought of just listing every single VK key code in a massive,
if
statement, but not only would that be bulky, but I would also not take certain keys such as some unique mouse buttons or any other key I may end up missing. I want a function that will store the next mouse or keyboard input into an integer, without the use of a predefined set of available inputs. I want to dynamically recognize any input key.
Minimal example:
const char* cbind0 = "none";
static bool bbind0 = false;
static int ibind0;
if (ImGui::Button(cbind0))
bbind0 = true
if (bbind0 == true)
{
cbind0 = "press any key...";
CopyNextInputTo(ibind0); // Function to copy pressed key to our integer
}
This code would show up as a box in the GUI, and then the integer ibind0 which is containing our now determined keybind, will be used like so:
static bool option = false;
if (GetAsyncKeyState(ibind0) & 1)
{
option =! option;
}
And now we can toggle our option on and off either using a GUI checkbox or by pressing the user-determined key.
The only problem now being I have no clue how to dynamically record all possible inputs! Does anyone know any possible functions or methods? Thanks!
Assuming you are the one creating the window you're using ImGui on, you can handle WM_KEYDOWN events in your WNDPROC callback. Here's a basic example of this in action that will create a pop-up message box whenever you press a key:
#ifndef UNICODE
#define UNICODE
#endif
#include <Windows.h>
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) {
const wchar_t WINDOW_CLASS_NAME[] = L"Keyboard Listener Class";
const wchar_t WINDOW_TITLE[] = L"Keyboard Listener";
WNDCLASS windowClass{};
windowClass.lpfnWndProc = WindowProc;
windowClass.hInstance = hInstance;
windowClass.lpszClassName = WINDOW_CLASS_NAME;
RegisterClass(&windowClass);
HWND hWnd = CreateWindowEx(
0,
WINDOW_CLASS_NAME,
WINDOW_TITLE,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL
);
if (hWnd == NULL) {
return EXIT_FAILURE;
}
ShowWindow(hWnd, nCmdShow);
MSG msg{};
while (GetMessage(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return EXIT_SUCCESS;
}
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
return EXIT_SUCCESS;
case WM_KEYDOWN:
wchar_t keyName[64];
GetKeyNameText(lParam, keyName, sizeof(keyName));
MessageBox(hWnd, keyName, L"Key Pressed!", MB_OK);
return EXIT_SUCCESS;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return EXIT_SUCCESS;
}
Or, if you aren't the one actually creating the Window, you can hook into another Window's keyboard events using SetWindowsHookEx with the idHook parameter set to WH_KEYBOARD and passing in a KeyboardProc callback to the lpfn parameter.
So my program works, all apart from one thing, I would like for my button, 'pushBtn' , aka BTN_PUSH_TALK , to send a BN_PUSHED or BN_UNPUSHED message so I can handle it accordingly.
Following steps online, as well as trial and improvement, right now the only response I ever get is once I am done holding / clicking the button.
pushBtn = CreateWindowEx(0, L"BUTTON", L"TALK", WS_CHILD |
WS_VISIBLE |
BS_DEFPUSHBUTTON , 0 , 290 , 50, 50,
hWnd,(HMENU)BTN_PUSH_TALK, GetModuleHandle(NULL), NULL);
Handler (or at least what matters) :
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
lParam)
{
bool asd;
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case BTN_PUSH_TALK:
switch (HIWORD(wParam))
{
case BN_UNPUSHED:
if (connected && inputChoiceStr == "Push To Talk") {
tplug->setDuck(false);
}
break;
case BN_PUSHED:
if (connected && inputChoiceStr == "Push To Talk") {
tplug->setDuck(true);
}
break;
}
break;
I expected once i clicked and held down the button , that the BN_PUSHED case would be entered, however it is not.
On letting go, I expect the BN_UNPUSHED case to be entered, but this was not the case either.
case BTN_PUSH_TALK is reached, meaning the button is identifiable, however the switch case within this block of code is never reached.
Buttons send WM_COMMAND on click. To achieve a push/release notification you must subclass the button class (SetWindowLongPtr() with GWLP_WNDPROC) and then handle WM_LBUTTONDOWN and WM_LBUTTONUP in your new Window Proc.
If I'm reading the question right, your goal is to get notifications when a standard push button is initially pushed by the user, whereas standard notification behavior of buttons only posts WM_COMMANDs on "clicks" where a click is the whole mouse down plus mouse up sequence.
Historically in order to get the BN_PUSHED and BN_UNPUSHED notifications in your WM_COMMAND handler you had to use the BS_NOTIFY window style when creating the button. However, if you read the documentation for BN_PUSHED or BN_UNPUSHED you will see
This notification code is provided only for compatibility with 16-bit versions of Windows earlier than version 3.0. Applications should use the BS_OWNERDRAW button style and the DRAWITEMSTRUCT structure for this task.
These were very old notifications that from what I can tell are not just deprecated but no longer even supported. You can do, however, as the documentation suggests: use an owner drawn button i.e. a button created with the BS_OWNERDRAW style.
This turns out to be more difficult than just creating the button with BS_NOTIFY turned on, because the button will no longer perform default painting by itself. Given this added chore, I'd recommend not doing it this way unless you want to custom paint your buttons anyway -- unless you happen to want some nonstandard visual look-and-feel for these buttons as well as nonstandard notification behavior. Otherwise, I would probably just do Win32 subclassing as someone else suggested to trap WM_LBUTTONDOWN etc. and then call the standard button WNDPROC after doing some action on the events i cared about.
Anyway the minimal owner drawn button that reports button down and button up events is like the following. (I post the button events as custom messages but you could do whatever you wish there)
#include <windows.h>
#define BTN_ID 101
#define WM_PUSHBUTTONDOWN WM_APP + 1
#define WM_PUSHBUTTONUP WM_APP + 2
HINSTANCE g_instance = 0;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
g_instance = hInstance;
MSG msg = { 0 };
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_BACKGROUND);
wc.lpszClassName = L"owner_draw_btn";
if (!RegisterClass(&wc))
return -1;
if (!CreateWindow(wc.lpszClassName, L"foobar", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 640, 480, 0, 0, hInstance, NULL))
return -1;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT HandleDrawItem(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
auto* dis = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
if (dis->CtlType != ODT_BUTTON)
return 0;
auto style = (dis->itemState & ODS_SELECTED) ?
DFCS_BUTTONPUSH | DFCS_PUSHED :
DFCS_BUTTONPUSH;
auto rect = &dis->rcItem;
DrawFrameControl(dis->hDC, rect, DFC_BUTTON, style);
TCHAR text[512];
auto n = GetWindowText(dis->hwndItem, text, 512);
DrawText(dis->hDC, text, n, rect, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
if (dis->itemAction == ODA_SELECT) {
PostMessage(
hWnd,
(dis->itemState & ODS_SELECTED) ? WM_PUSHBUTTONDOWN : WM_PUSHBUTTONUP,
dis->CtlID,
0
);
}
return 0;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
CreateWindow(
L"button", L"foobar",
BS_OWNERDRAW | WS_CHILD | WS_VISIBLE,
10, 10, 150, 35, hWnd,
(HMENU) BTN_ID,
g_instance,
0
);
return 0;
case WM_DRAWITEM:
return HandleDrawItem(hWnd, wParam, lParam);
case WM_PUSHBUTTONDOWN:
OutputDebugString(L"Button down event\n");
break;
case WM_PUSHBUTTONUP:
OutputDebugString(L"Button up event\n");
break;
case WM_CLOSE:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
I'm having trouble detecting a hover over a static Win32 control.
This is not a duplicate issue because this looks for multiple static controls instead of just looking for a single known handle to a static control at compile time.
While this can be done in seconds in another language, I'm growing a little frustrated after trying things out for some hours. I hope to get an answer here.
First, I created a class called Label. I create a static control window within it. I'll refer to static as label for now on.
// Create the label's handle.
m_handle = CreateWindowEx(NULL, "static", m_text.c_str(),
WS_CHILD | SS_LEFT | SS_NOTIFY, m_x, m_y, m_width, m_height,
m_parentWindow, (HMENU)(UINT_PTR)m_id, m_hInstance, NULL);
if (m_handle == NULL)
return false;
When the mouse hovers over this label, the following method should be called:
void Label::invokeOnMouseHover()
{
if (m_onMouseOver)
m_onMouseOver();
}
This will call my method:
void lblName_onMouseOver()
{
MessageBox::show("Hovering!", "My Console",
MessageBoxButtons::Ok, MessageBoxIcon::Information);
}
This is how I create it from the top level:
Label lblName("This is a label.", 0, 0);
lblName.setVisible(true);
lblName.OnMouseOver(lblName_onMouseOver);
frm.add(lblName);
Admit it, this thin layer is beautiful.
While my callbacks are working fine for my Button and Checkbox controls, I noticed that statics are a little different.
So, let's go down a few levels:
This is in the main window's procedure:
case WM_MOUSEMOVE:
{
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
frm.setMousePos(xPos, yPos);
// Get the static's id
int id = // ?? Which static control id is it out of several?
// Obtain the control associated with the id.
X3D::Windows::Control *ctrl = frm.getControls().find(id)->second;
if (ctrl == NULL)
return 0;
// Check if this is a X3D Label control.
if (typeid(*ctrl) == typeid(X3D::Windows::Label))
{
Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);
int xPos = GET_X_LPARAM(lParam);
int yPos = GET_Y_LPARAM(lParam);
if (xPos >= lbl->getX() &&
yPos >= lbl->getY() &&
(xPos < (lbl->getX() + lbl->getWidth())) &&
(yPos < (lbl->getY() + lbl->getHeight())))
{
if (lbl != NULL)
lbl->invokeOnMouseHover();
}
}
}
break;
What I'm trying to do here is detect what label id was detected, and then call Label::invokeOnMouseOver().
Even though I Understand I need to use TRACKMOUSEEVENT at some point, its field member 'HWND' requires the label's handle. But I can't easily say which handle it will be because the collection may contain one or several labels.
Overall, I'm looking for suggestions on how to either restructure this or see if there's an easy solution here. My guess is I'm overthinking this.
Thanks.
Update:
Here is a code update after reading the first answer with the first solution. While this solves the hover issue, I don't see the label's text on execution.
case WM_MOUSEMOVE:
{
int xPos = LOWORD(lParam);
int yPos = HIWORD(lParam);
// Get the mouse position
frm.setMousePos(xPos, yPos);
// Check for labels
X3D::Windows::Control *ctrl = (X3D::Windows::Control*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
if (ctrl)
{
// Check if this is a X3D Label control.
Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);
if (lbl)
lbl->invokeOnMouseHover();
return CallWindowProc(lbl->getOldProc(), hWnd, msg, wParam, lParam);
}
}
break;
And the control creation:
// Create the label's handle.
m_handle = CreateWindowEx(NULL, TEXT("static"), m_text.c_str(),
WS_CHILD | SS_LEFT | SS_NOTIFY, m_x, m_y, m_width, m_height,
m_parentWindow, (HMENU)(UINT_PTR)m_id, m_hInstance, NULL);
if (!m_handle)
return false;
SetWindowLongPtr(m_handle, GWLP_USERDATA, (LONG_PTR)(X3D::Windows::Control*)this);
m_oldProc = (WNDPROC)SetWindowLongPtr(m_handle, GWLP_WNDPROC, (LONG_PTR)&wndProc);
If it's a paint issue, this is what I have in WndProc.
case WM_PAINT:
{
hDC = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
return 0;
}
break;
Update #2:
The alternative solution fixed the issue.
If anyone has trouble with unresolved external symbols with SetWindowSubclass() in the future, remember to add the following to your project or just #pragma it:
#pragma comment(lib, "comctl32.lib")
The only identifying info that WM_MOUSEMOVE gives you is the HWND that the mouse is moving over (or the HWND that has captured the mouse). The reported X/Y coordinates are relative to that HWND. That is the HWND that you are looking for, so you don't need to hunt for it, the message gives it to you.
If you need access to an HWND's control ID, you can use GetDlgCtrlID() for that. But note that every HWND has its own window procedure, so control IDs are typically only useful for notification messages, like WM_COMMAND and WM_NOTIFY, which are sent to a control's parent window (and even then, such notifications also carry the child's HWND as well).
When the mouse moves over a particular HWND, WM_MOUSEMOVE is posted to the message procedure of that HWND only (or to the HWND that has captured the mouse). It sounds like you are expecting it to be posted to the control's parent window instead, and that is simply not the case. That is why your WM_MOUSEMOVE handler is not being called. You are handling the message at the wrong level. You need to be prepared to handle messages on a per-control basis instead, using the control's own message procedure.
It would be more efficient to store your Control object's this pointer inside its associated HWND itself, via SetWindowLongPtr(GWLP_USERDATA), SetWindowSubClass(), or SetProp(), and then your message handlers can access the Control* pointer of the message's reported HWND when needed, for example:
// Create the label's handle.
m_handle = CreateWindowEx(NULL, TEXT("static"), m_text.c_str(),
WS_CHILD | SS_LEFT | SS_NOTIFY, m_x, m_y, m_width, m_height,
m_parentWindow, (HMENU)(UINT_PTR)m_id, m_hInstance, NULL);
if (!m_handle)
return false;
SetWindowLongPtr(m_handle, GWLP_USERDATA, (LONG_PTR)(X3D::Windows::Control*)this);
m_oldproc = (WNDPROC) SetWindowLongPtr(m_handle, GWL_WNDPROC, (LONG_PTR)&MyWndProc);
...
LRESULT CALLBACK MyWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_MOUSEMOVE:
{
...
X3D::Windows::Control *ctrl = (X3D::Windows::Control*) GetWindowLongPtr(hWnd, GWLP_USERDATA);
if (ctrl)
{
// Check if this is a X3D Label control.
Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);
if (lbl)
lbl->invokeOnMouseHover();
}
break;
}
...
}
return CallWindowProc(m_oldproc, hWnd, uMsg, wParam, lParam);
}
Alternatively:
// Create the label's handle.
m_handle = CreateWindowEx(NULL, TEXT("static"), m_text.c_str(),
WS_CHILD | SS_LEFT | SS_NOTIFY, m_x, m_y, m_width, m_height,
m_parentWindow, (HMENU)(UINT_PTR)m_id, m_hInstance, NULL);
if (!m_handle)
return false;
SetWindowSubclass(m_handle, &MySubclassProc, 1, (DWORD_PTR)(X3D::Windows::Control*)this);
...
LRESULT CALLBACK MySubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
switch (uMsg)
{
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, &MySubclassProc, uIdSubclass);
break;
case WM_MOUSEMOVE:
{
X3D::Windows::Control *ctrl = (X3D::Windows::Control*) dwRefData;
// Check if this is a X3D Label control.
Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);
if (lbl)
lbl->invokeOnMouseHover();
break;
}
...
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
This post doesn't answer the question you asked but you should read it. It is important, and it is posted in a spirit of helpfulness after my rather acid comment above. I hope you find it so.
This class library is going to run into trouble. Code like this (using a dynamic_cast):
case WM_MOUSEMOVE:
{
X3D::Windows::Control *ctrl = (X3D::Windows::Control*) dwRefData;
// Check if this is a X3D Label control.
Label *lbl = dynamic_cast<X3D::Windows::Label*>(ctrl);
if (lbl)
lbl->invokeOnMouseHover();
break;
}
Is almost always wrong.
Why? Well, suppose you want to hover over some other kind of control? What are you going to do now? And these guys are just going to multiply like rabbits, so don't do that.
Instead, for a message that the library understands (such as this one), declare a corresponding virtual method in the base class that derived classes can override if they're interested in processing that message. Then you have the basis of a solid design (and this is pretty basic stuff).
So, in this case you would have:
class Control // Base class
{
...
virtual void onMouseHover (...) { }
...
};
And then:
class Label : public Control // Derived class
{
...
virtual void onMouseHover (...) override { ... }
...
};
Now for my second point: you are going to find that, particularly for dialogs, your application is going to want to handle a lot of messages that the base class doesn't understand (or care about).
How are you going to do this? Are you going to add code to the base class for each new message that your application (or for that matter specific types of control implemented in the class library) becomes interested in? That is not a very attractive prospect.
Now MFC handles this with something it calls a message map, which is essentially a table of message ID's and their corresponding command handlers which you can associate with (in your case) any object ultimately derived from Control and I recommend you do something similar.
But thanks to the magic of the STL, you can do better. I have something like this in my class library (my base class is actually called Window, as I would suggest yours should be):
typedef INT_PTR (Window::*MESSAGE_HANDLER)
(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
// Register a message handler
void Window::RegisterMessageHandler (UINT uMsg, MESSAGE_HANDLER handler);
And what RegisterMessageHandler() actually does is to add handler to a std::unordered_map associated with the Window object using uMsg as a key. Then, when that message subsequently comes in, it can be dispatched to the right handler without the base class knowing anything about the meaning of the message whatsoever, and that's what you're going to need.
So, you might declare the following in class Control (code untested, written in Notepad):
class Control // Base class
{
...
std::unordered_map <UINT, MESSAGE_HANDLER> m_message_map;
...
};
And then RegisterMessageHandler() might look like this:
void Window::RegisterMessageHandler (UINT uMsg, MESSAGE_HANDLER handler)
{
m_message_map.emplace (uMsg, handler);
}
And MySubclassProc() might look like this:
LRESULT CALLBACK MySubclassProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
X3D::Windows::Control *ctrl = (X3D::Windows::Control*) dwRefData;
auto handler ctrl->m_message_map.find (uMsg);
if (handler != ctrl->m_message_map.end ())
return handler.second (hWnd, uMsg, wParam, lParam);
...
}
My own class library is actually more sophisticated than this (I started with something simple but then embellished it over time) but that's the basic idea. You might have to master a few C++ skills to pull this off but trust me, if you implement something like this you'll be very glad you did, down the line.
I want to make the background of a static control that displays text transparent. After 2 days of excessive googling i've come up with this:
class TLABEL : public TWINDOW {
private:
HINSTANCE hInst;
public:
static LRESULT CALLBACK SubclassCallback(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR userdata)
{
TLABEL* pThisWindow = (TLABEL*)userdata;
//look for messages that we want to process ourselves
switch (msg)
{
//make background transparent
case WM_CTLCOLORSTATIC:
{
HDC hDC = (HDC)wParam;
SetBkMode(hDC, TRANSPARENT);
SetTextColor(hDC, RGB(0, 0, 0)); //text color black
MessageBox(NULL, L"WM_CTLCOLORSTATIC", L"Error!",MB_ICONEXCLAMATION|MB_OK);
return (LRESULT)GetStockObject(NULL_BRUSH);
}
case WM_ERASEBKGND:
{
return TRUE;;
}
//remove Subclass if window is destroyed
case WM_NCDESTROY:
{
RemoveWindowSubclass(pThisWindow->handle, &TLABEL::SubclassCallback, uIdSubclass);
return TRUE;
}
}
//deliver all other messages to the original callback-function
return DefSubclassProc(hWnd, msg, wParam, lParam);
}
//standard alignment left
explicit TLABEL(int nx, int ny, int nwidth, int nheight, HWND hParent)
{
hInst = GetModuleHandle(NULL); //i know this is a bad practice
handle = CreateWindowEx(WS_EX_TRANSPARENT, //background transparent
L"STATIC", L"", //class name + text
WS_CHILD | WS_VISIBLE | SS_LEFT, //styles
nx, ny, nwidth, nheight, //dimensions
hParent, NULL, hInst, NULL);
//subclass window to catch messages we want to process ourselves
SetWindowSubclass(this->handle, &TLABEL::SubclassCallback, 0, (DWORD_PTR)this);
}
Somehow the SubclassCallback()-function never receives a WM_CTLCOLORSTATIC message, but i know that the function is called and that it receives WM_ERASEBKGND messages just fine. If i handle the WM_CTLCOLORSTATIC message in the message-handler of the main-window it works, unfortunately that would negate all benefits that come with making a seperate class for such a static control
I have not found any hints why the WM_CTLCOLORSTATIC message is sent to the main window and not my static control since all examples seem to do it just like that.
i have compiled it under VS2010 Express on a WinXP-32 machine
After creating the initial dialog box that works perfectly fine, I create another dialog box when the Join Game button is pressed. The dialog box is created and show successfully, however I am unable to type in the edit box or even press or exit the dialog. Does anyone understand how to fix this or why it happens? I made sure the dialog box itself was not the problem by creating and displaying it from the main loop in the application. It worked fine when I created it that way. So why does it error when being created from another dialog?
My code is below.
This code is for the DLGPROC function that each dialog uses.
#define WIN32_LEAN_AND_MEAN
#include "Windows.h"
#include ".\Controllers\Menu\MenuSystem.h"
#include ".\Controllers\Game Controller\GameManager.h"
#include ".\Controllers\Network\Network.h"
#include "resource.h"
#include "main.h"
using namespace std;
extern GameManager g;
extern bool men;
NET_Socket server;
extern HWND d;
HWND joinDlg;
char ip[64];
void JoinMenu(){
joinDlg = CreateDialog(g_hInstance, MAKEINTRESOURCE(IDD_GETADDRESSINFO), NULL, (DLGPROC)GameJoinDialogPrompt);
SetFocus(joinDlg);
// ShowWindow(joinDlg, SW_SHOW);
ShowWindow(d, SW_HIDE);
}
LRESULT CALLBACK GameJoinDialogPrompt(HWND Dialogwindow, UINT Message, WPARAM wParam, LPARAM lParam){
switch(Message){
case WM_COMMAND:{
switch(LOWORD(wParam)){
case IDCONNECT:{
GetDlgItemText(joinDlg, IDC_IP, ip, 63);
if(server.ConnectToServer(ip, 7890, NET_UDP) == NET_INVALID_SOCKET){
LogString("Failed to connect to server! IP: %s", ip);
MessageBox(NULL, "Failed to connect!", "Error", MB_OK);
ShowWindow(joinDlg, SW_SHOW);
break;
}
}
LogString("Connected!");
break;
case IDCANCEL:
ShowWindow(d, SW_SHOW);
ShowWindow(joinDlg, SW_HIDE);
break;
}
break;
}
case WM_CLOSE:
PostQuitMessage(0);
break;
}
return 0;
}
LRESULT CALLBACK GameMainDialogPrompt(HWND Dialogwindow, UINT Message, WPARAM wParam, LPARAM lParam){
switch(Message){
case WM_PAINT:{
PAINTSTRUCT ps;
RECT rect;
HDC hdc = GetDC(Dialogwindow);
hdc = BeginPaint(Dialogwindow, &ps);
GetClientRect (Dialogwindow, &rect);
FillRect(hdc, &rect, CreateSolidBrush(RGB(0, 0, 0)));
EndPaint(Dialogwindow, &ps);
break;
}
case WM_COMMAND:{
switch(LOWORD(wParam)){
case IDC_HOST:
if(!NET_Initialize()){
break;
}
if(server.CreateServer(7890, NET_UDP) != 0){
MessageBox(NULL, "Failed to create server.", "Error!", MB_OK);
PostQuitMessage(0);
return -1;
}
ShowWindow(d, SW_HIDE);
break;
case IDC_JOIN:{
JoinMenu();
}
break;
case IDC_EXIT:
PostQuitMessage(0);
break;
default:
break;
}
break;
}
return 0;
}
}
I call the first dialog using the below code
void EnterMenu(){
// joinDlg = CreateDialog(g_hInstance, MAKEINTRESOURCE(IDD_GETADDRESSINFO), g_hWnd, (DLGPROC)GameJoinDialogPrompt);//
d = CreateDialog(g_hInstance, MAKEINTRESOURCE(IDD_SELECTMENU), g_hWnd, (DLGPROC)GameMainDialogPrompt);
}
The dialog boxes are not DISABLED by default, and they are visible by default. Everything is set to be active on creation and no code deactivates the items on the dialog or the dialog itself.
First, make sure you write the correct signature for the dialog procedures:
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg,
WPARAM wParam, LPARAM lParam);
(See http://msdn.microsoft.com/en-us/library/ms645469(v=VS.85).aspx)
Your dialog procedures should therefore look like this:
INT_PTR CALLBACK GameJoinDialogPrompt(HWND Dialogwindow, UINT Message,
WPARAM wParam, LPARAM lParam)
{ /* ... */ }
INT_PTR CALLBACK GameMainDialogPrompt(HWND Dialogwindow, UINT Message,
WPARAM wParam, LPARAM lParam)
{ /* ... */ }
Then you should be able to do this without warnings or errors:
void EnterMenu()
{
d = CreateDialog(g_hInstance, MAKEINTRESOURCE(IDD_SELECTMENU),
g_hWnd, &GameMainDialogPrompt);
// Note the ampersand. Also note that no cast is needed. You should
// not need to use a cast to pass in the address of the function.
}
See http://blogs.msdn.com/oldnewthing/archive/2004/01/15/58973.aspx for why it's extremely important to get the function signature right.
That being said, your joinDlg should be a modal dialog box, since it is requesting information from the user:
void JoinMenu()
{
// DialogBox() creates a modal dialog box. It "blocks" its owner until
// it closes. On the other hand, CreateDialog() creates a non-modal
// dialog box.
joinDlg = DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_GETADDRESSINFO),
d, &GameJoinDialogPrompt);
// Again, note the ampersand and the lack of a cast when passing in
// the address of the dialog procedure. Also, the main dialog box is
// serving as the owner of this dialog box.
}
Also note that dialog box procedures are different from windows procedures in that they return either TRUE or FALSE: TRUE if your dialog procedure processed the message, FALSE otherwise. (There are some "weird" messages that violate this rule, but you're not handling for those messages)
So your dialog procedures should look something like this:
INT_PTR CALLBACK GameMainDialogPrompt(HWND Dialogwindow, UINT Message,
WPARAM wParam, LPARAM lParam)
{
switch(Message)
{
case WM_PAINT:
/* Do painting */
return TRUE; // We handled the paint message
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDC_HOST:
/* Do command */
return TRUE; // We handled this particular command.
case IDC_JOIN:
/* Do command */
return TRUE; // We handled this particular command.
case IDC_EXIT:
/* Do command */
return TRUE; // We handled this particular command.
}
return FALSE; // The command wasn't handled.
}
return FALSE; // The message wasn't handled.
}
Dialog procedures do not call DefWindowProc() nor return 0!
In addiiton to the other excellent post you are also doing silly things like:
if(server.CreateServer(7890, NET_UDP) != 0){
MessageBox(NULL, "Failed to create server.", "Error!", MB_OK);
PostQuitMessage(0);
in a WM_COMMAND handler. This is a horrible piece of code as it stalls the dialogs modal loop without disabling it, or popping up the message box.
If you call a modal window from a different window (or dialogs) message proc you MUST disable the stalled window. Practically speaking, pass the windows HWND to the MessageBox call.
If all else fails, start fresh with this:
In resource.h:
#define IDD_DIALOG1 101
#define IDD_DIALOG2 102
#define ID_OPEN 1001
#define ID_MESSAGE 1002
In a resource file:
#include <winres.h>
#include "resource.h"
IDD_DIALOG1 DIALOGEX 0, 0, 300, 200
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER |
WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Main Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "Open Secondary Dialog", ID_OPEN, 73 ,49, 133, 64
END
IDD_DIALOG2 DIALOGEX 0, 0, 200, 150
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER |
WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Secondary Dialog"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "Message Box", ID_MESSAGE, 50, 49, 88, 50
END
In a source file:
#include <windows.h>
#include "resource.h"
INT_PTR CALLBACK SecondaryDialogProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_MESSAGE:
// Show a message box. Note that we're passing in our own HWND into
// the function, so we "block" this dialog box until the user
// dismisses this message box.
::MessageBox(hwnd, "Hello World!", "Greetings", MB_OK);
return TRUE;
}
return FALSE;
case WM_CLOSE:
// Because this is a modal dialog box (we used ::DialogBox()), we
// use ::EndDialog() instead of ::DestroyWindow() to destroy this
// dialog box.
::EndDialog(hwnd, 0);
return TRUE;
}
return FALSE;
}
INT_PTR CALLBACK MainDialogProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case ID_OPEN:
// Open a modal dialog box. This will block the main dialog box
// until the secondary dialog box is closed.
::DialogBox(::GetModuleHandle(NULL),
MAKEINTRESOURCE(IDD_DIALOG2), hwnd, &SecondaryDialogProc);
return TRUE;
}
return FALSE;
case WM_CLOSE:
// We close this dialog box with ::DestroyWindow(). This causes the
// WM_DESTROY message to be sent.
::DestroyWindow(hwnd);
return TRUE;
case WM_DESTROY:
// Since the main dialog box is being destroyed, we quit
// the application.
::PostQuitMessage(0);
return TRUE;
}
return FALSE;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)
{
// Open a non-modal dialog box using ::CreateDialog().
HWND mainDlg = ::CreateDialog(::GetModuleHandle(NULL),
MAKEINTRESOURCE(IDD_DIALOG1), NULL, &MainDialogProc);
// The first ::ShowWindow() call should use nShowCmd.
::ShowWindow(mainDlg, nShowCmd);
MSG msg;
while (::GetMessage(&msg, NULL, 0, 0) > 0)
{
// So our main dialog behaves properly.
if(!::IsDialogMessage(mainDlg, &msg))
{
::TranslateMessage( & msg );
::DispatchMessage( & msg );
}
}
return msg.wParam;
}
Here, this is just the bare bones code to open up a main dialog, with a button to open another dialog. See if this works first, then add your business logic.