Related
I recently tried to subclass a control using the SetWindowSubclass() function. And to my surprise, it worked smoothly at first. I created the control procedure, but it doesn't receive messages like WM_LBUTTONDOWN or WM_KEYDOWN, and I don't understand why! I searched all my time yesterday and I confess that I am losing hope.
Here is my code, simplified (very long in normal times!):
HWND button = CreateWindow("BUTTON", NULL, WS_VISIBLE | WS_CHILD | BS_OWNERDRAW, 100, 200, 250, 50, hwnd, (HMENU)ID_B_JOUER, instance, NULL);
SetWindowSubclass(boutton, ControlProc, ID_B_JOUER, 0);
And here is the control procedure:
LRESULT CALLBACK ControlProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
switch(msg)
{
case WM_...:
{
MessageBox(hwnd, "Bonjour!", "Message", MB_ICONINFORMATION); // Here it does not work!!! Something be the message.
break;
}
default:
{
DefSubclassProc(hwnd, msg, wParam, lParam);
}
}
return 0;
}
I want to clarify that I am using the GetLastError() function and it returned 0 (no problem). I hope someone has a solution because it's very weird.
You are creating a new BUTTON control and assigning it to a variable named button, but you are subclassing using a different variable named boutton instead.
Assuming that is just a typo, your ControlProc() is returning 0 for every message, it is ignoring the return value of DefSubclassProc(), which may not be 0. You MUST return what DefSubclassProc() returns for unhandled messages.
HWND button = CreateWindow("BUTTON", NULL, WS_VISIBLE | WS_CHILD | BS_OWNERDRAW, 100, 200, 250, 50, hwnd, (HMENU)ID_B_JOUER, instance, NULL);
SetWindowSubclass(button, &ControlProc, ID_B_JOUER, 0);
...
LRESULT CALLBACK ControlProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
switch (msg)
{
case WM_LBUTTONDOWN:
case WM_KEYDOWN:
{
MessageBox(hwnd, "Bonjour!", "Message", MB_ICONINFORMATION);
break;
}
default:
{
return DefSubclassProc(hwnd, msg, wParam, lParam);
}
}
return 0;
}
While using C#, it was easy before to create an event handler at runtime like:
Button button1 = new button1();
button1.click += Button_Click(); //Create handler
button1.click -= Button_Click(); //Remove handler
public void Button_Click()
{
//Button clicked
}
But in win32 I am stuck with WndProc callback where I have to handle all events. I want to create a handler for specific message and attach it to specific void.
Currently, I am using WndProc to catch WM_CREATE message and draw controls:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
Draw(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
void Draw(HWND hWnd)
{
HWND button4 = CreateWindow(L"button", L"button4", WS_CHILD | WS_VISIBLE , 329, 118, 112, 67, hWnd, (HMENU)1001, hInst, NULL);
HWND button3 = CreateWindow(L"button", L"button3", WS_CHILD | WS_VISIBLE , 212, 118, 112, 67,
...
}
But I want to create or remove event handler instead of using WndProc at runtime something like:
AddHandler WM_CREATE , Draw(hWnd);
DelHandler WM_CREATE , Draw(hWnd);
What I have tried ?
The problem with SetWindowsHookEx is that its handles entire messages like WndProc. I don't want an handler that handles entire window messages and skip some of them. May be this can create performance or memory leak issue's.
Edit: Implemented example from answer:
#include <unordered_map>
using msgHandler = LRESULT(*)(HWND, UINT, WPARAM, LPARAM);
std::unordered_map<UINT, msgHandler> messageHandlers;
LRESULT handleCreate(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
//Draw some buttons to see whether event WM_CREATE called or not
HWND button4 = CreateWindow(L"button", L"button4", WS_CHILD | WS_VISIBLE, 329, 118, 112, 67, hWnd, (HMENU)1001, hInst, NULL);
HWND button3 = CreateWindow(L"button", L"button3", WS_CHILD | WS_VISIBLE, 212, 118, 112, 67, hWnd, (HMENU)1002, hInst, NULL);
HWND button2 = CreateWindow(L"button", L"button2", WS_CHILD | WS_VISIBLE, 329, 46, 112, 67, hWnd, (HMENU)1003, hInst, NULL);
HWND button1 = CreateWindow(L"button", L"button1", WS_CHILD | WS_VISIBLE, 212, 46, 112, 67, hWnd, (HMENU)1004, hInst, NULL);
return 0;
}
LRESULT handleClose(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
//Quit form
PostQuitMessage(0);
return 0;
}
void AddHandler()
{
messageHandlers[WM_CREATE] = handleCreate;
messageHandlers[WM_DESTROY] = handleClose;
}
void DelHandler()
{
messageHandlers.erase(WM_CREATE);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
auto handler = messageHandlers.find(msg);
if (handler != messageHandlers.end()) return handler->second(hWnd, msg, wParam, lParam);
return DefWindowProc(hWnd, msg, wParam, lParam);
}
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow){
AddHandler();
//DelHandler();
...
The message ID is just an unsigned integer, so there's not really anything special about it. Although the giant switch statement is one common way to handle messages, you can do things quite differently if you choose. To support dynamic insertion/deletion of handlers, one possibility would be to use an std::unordered_map:
// a message handler receives the normal parameters:
using msgHandler = LRESULT(*)(HWND, UINT, WPARAM, LPARAM);
// a map from message numbers to the handler functions:
std::unordered_map<UINT, msgHandler> messageHandlers;
// A couple of message handler functions:
LRESULT handleCreate(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
// ...
}
LRESULT handleDraw(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
// ...
}
// register them to handle the appropriate messages:
messageHandlers[WM_CREATE] = handleCreate;
messageHandlers[WM_PAINT] = handleDraw;
// and then our (now really tiny) window proc that uses those:
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
auto handler = messageHandlers.find(msg);
if (handler != messageHandlers.end())
return handler->second(hWnd, msg, wParam, lParam);
return DefWindowProc(hWnd, msg, wParam, lParam);
}
Since we're just storing pointers to functions in an std::unordered_map, adding, finding, or deleting a handler all just use the normal operations for adding, finding, or deleting something in an std::unodered_map (e.g., messageHandlers.erase(WM_CREATE); to erase the WM_CREATE handler from the map).
If you want to get more elaborate with this, you can create specific types for handling different messages, so (for example) one that doesn't receive anything meaningful in its lParam simply won't receive an lParam at all, while another that receives two things "smooshed" together, one in the low word of lParam, and the other in the high word of lParam can receive them broken apart into two separate parameters. But that's a lot more work.
You might also want to look for WindowsX.h, a header Microsoft provides (or at least used to provide) in the SDK that handles mapping a little like I've outlined above (the latter version, where each handler receives parameters that represent the logical data it receives, instead of the WPARAM and LPARAM used to encode that data.
You could process messages dynamically like this:
typedef void (*FHANDLE)();
std::vector<FHANDLE> handles;
void AddHandler(FHANDLE handle)
{
handles.push_back(handle);
}
void DelHandler(FHANDLE handle)
{
auto it = std::find(handles.begin(), handles.end(), handle);
handles.erase(it);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
for (int i = 0; i < handles.size(); i++)
{
handles[i]();
}
break;
...
}
}
And add/del the handle:
void myclick1()
{
MessageBox(0, L"test1", L"message", 0);
}
void myclick2()
{
MessageBox(0, L"test2", L"message", 0);
}
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // Store instance handle in our global variable
AddHandler(myclick1);
AddHandler(myclick2);
//DelHandler(myclick2);
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
...
}
I have a crazy issue. I subclassed buttons, richedits, checkboxes,.. and all seems working fine. But after I subclassed a trackbar I’m having some trouble now. The problem is that my subclassed messagehandler don’t receive the WM_VSCROLL / WM_HSCROLL messages. They are still sent to the parent's messagehandler. WM_PAINT message and some others are successfully sent to the subclassed messagehanlder.
Someone knows what I’m doing wrong? …And maybe knows how to solve this problem? I created a clean project with the following needed code:
#include <windows.h>
#include <CommCtrl.h>
#pragma comment(lib,"comctl32.lib")
//Prototyps
HWND CreateMainWindow(HINSTANCE hInstance);
LRESULT CALLBACK MessageHandler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
LRESULT CALLBACK SubMessageHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
WNDPROC oldWndProc;
HWND hWnd = 0;
HWND hTrackBar = 0;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){
WNDCLASSEXA wndClass = {sizeof(WNDCLASSEX), CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW, MessageHandler, 0,0, hInstance, LoadIcon(NULL, IDI_WINLOGO),
LoadCursor(NULL, IDC_ARROW),(HBRUSH)GetStockObject(WHITE_BRUSH), NULL, "WindowClass", LoadIcon(NULL, IDI_WINLOGO)};
RegisterClassExA(&wndClass);
//Creat MainWindow
hWnd = CreateWindowExA(NULL, "WindowClass", "Test Windows", WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN,
100, 100, 400, 300, NULL, NULL, hInstance, NULL);
//Creat Trackbar
INITCOMMONCONTROLSEX initCtrlEx;
initCtrlEx.dwSize = sizeof(INITCOMMONCONTROLSEX);
initCtrlEx.dwICC = ICC_BAR_CLASSES;
if (InitCommonControlsEx(&initCtrlEx)){
hTrackBar = CreateWindowExA(NULL,TRACKBAR_CLASSA, "TrackBar_Test", WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | TBS_AUTOTICKS |
TBS_ENABLESELRANGE | TBS_VERT | TBS_BOTH, 10, 10, 50, 200, hWnd, NULL, hInstance, NULL);
oldWndProc = (WNDPROC)SetWindowLongPtrA(hTrackBar, GWLP_WNDPROC, (LONG_PTR)SubMessageHandler); //Subclassing messagehandler
}
//Message loop
MSG msg;
while (GetMessageA(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessageA(&msg);
}
return 0;
}
LRESULT CALLBACK SubMessageHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){
switch (msg){
case WM_VSCROLL: //callback is subclassed but WM_VSCROLL is not send. why?
MessageBoxA(hWnd, "WM_VSCROLL sent (to SubMessageHandler)", "Test", MB_OK);
break;
}
if (oldWndProc != 0)
return CallWindowProcA(oldWndProc, hwnd, msg, wParam, lParam);
else
return DefWindowProcA(hwnd, msg, wParam, lParam);
}
LRESULT CALLBACK MessageHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){
switch (msg){
case WM_VSCROLL: //Why the hell is the Trackbar WM_VSCROLL still sent here to the parent callback!?!?
MessageBoxA(hWnd, "WM_VSCROLL sent (to Parent)", "Test", MB_OK);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
}
return DefWindowProcA(hwnd, msg, wParam, lParam);
}
From the MSDN documentation on Trackbar controls:
A trackbar notifies its parent window of user actions by sending the
parent a WM_HSCROLL or WM_VSCROLL message.
The Trackbar's contract is to notify the parent window with WM_HSCROLL/WM_VSCROLL. The Trackbar control generates and sends those messages; it does not receive them.
Also note that the Default Trackbar Message Processing section does not list WM_HSCROLL/WM_VSCROLL (but does list WM_LBUTTONDOWN, WM_MOUSEMOVE, WM_LBUTTONUP, WM_KEYDOWN, WM_KEYUP, which are the raw messages it would need to handle to handle interaction).
As for what to do about it, it probably depends on exactly what you want to do. You could try subclassing and intercepting all of the user input messages, but that seems like a lot of work and is potentially brittle. My recommendation would be to have the parent window explicitly reflect WM_HSCROLL/WM_VSCROLL back to your custom Trackbar control.
What I am trying to do, is to create a edit control and procedure within a class. I have tried various things, and tried using parts of a similar question I asked: Win32 C++ Create a Window and Procedure Within a Class.
At the moment, I took them apart.
main_class.h
class MainClass {
private:
HWND hwndMain; // main windows handle
HINSTANCE hInstanceMain; // main windows instance
HWND hTextarea;
public:
bool init(HWND _hwnd, HINSTANCE _hInstance);
bool ShowInfoTextarea();
};
main_class.cpp
// Heres the question
bool MainClass::ShowInfoTextarea() {
if (hTextarea != NULL) return true; // if it is not null, the textarea is likely already displayed.
// Creating the EDIT textarea
hTextarea = CreateWindowEx(0, L"EDIT", L"",
WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL,
SCREEN_WIDTH+5, 0, WINDOW_WIDTH-SCREEN_WIDTH-10, WINDOW_HEIGHT-30, hwndMain, (HMENU)IDC_CTRL_EDIT, GetModuleHandle(NULL), NULL);
if (hTextarea == NULL) { MessageBox(NULL, L"Could not create the test text control. The program will now close.", NULL, MB_OK | MB_ICONEXCLAMATION); return false; }
// Dozens of attempts with something like:
// lpEditWndProc = (WNDPROC)SetWindowLongPtr(hTextarea, GWLP_WNDPROC, (LONG_PTR)EditControlProc);
// lpEditWndProc = (WNDPROC)SetWindowLongPtr(hTextarea, GWLP_WNDPROC, (LONG_PTR)MainClass::EditControlProc);
// tried static callback functions, etc.
//Every try the compiler said: Are there missing braces ( ) with EditControlProc
}
// To get working, I separated:
WNDPROC lpEditWndProc;
LRESULT CALLBACK EditControlProc(HWND hwndEdit, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
default:
return CallWindowProc(lpEditWndProc, hwndEdit, uMsg, wParam, lParam);
}
return 0; // DONE
}
With my (various) attempts the compiler said: Are there missing braces ( ) with EditControlProc, or said type mismatch when trying to define lpEditWndProc.
I am probably missing out on something simple?
Useage:
main.cpp
MainClass mainclass;
mainclass.ShowInfoTextarea();
Thanks. If missing any info let me know.
Jonathan Potter's comment was the solution.
For anyone who reads:
mainclass.h
class MainClass {
private:
HWND hwndMain;
HINSTANCE hInstanceMain;
HWND hTextarea;
static LRESULT CALLBACK EditControlProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
public:
bool init(HWND _hwnd, HINSTANCE _hInstance);
bool ShowInfoTextarea();
};
mainclass.cpp
// To Show the Textarea
bool MainClass::ShowInfoTextarea() {
if (hTextarea != NULL) return true; // if it is not null, the textarea is likely already displayed.
// Creating the EDIT textarea
hTextarea = CreateWindowEx(0, L"EDIT", L"",
WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL,
SCREEN_WIDTH+5, 0, WINDOW_WIDTH-SCREEN_WIDTH-10, WINDOW_HEIGHT-30, hwndMain, (HMENU)IDC_CTRL_EDIT, GetModuleHandle(NULL), NULL);
if (hTextarea == NULL) { MessageBox(NULL, L"Could not create the test text control. The program will now close.", NULL, MB_OK | MB_ICONEXCLAMATION); return false; }
SetWindowSubclass(hTextarea, EditControlProc, 0, 0);
}
// Now Works
LRESULT CALLBACK MainClass::EditControlProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) {
switch (msg) {
case WM_KEYDOWN:
MessageBox(0,0,0,0);
break;
default:
return DefSubclassProc(hWnd, msg, wParam, lParam);
break;
}
return 0; // DONE
}
If I create a child window (In this case window "About") to the main window, the dialog box for some reason is not called. If you do not the child window is a dialog box called normal and works fine. GetLastError returns the error number 1812 (The specified image file did not contain a resource section.). But from a resource file everything is fine. And as I said, if you do not create a child window then everything works fine. What's the problem?
#include <windows.h>
#include "resource.h"
LONG WINAPI WndProc(HWND, UINT, WPARAM,LPARAM);
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK PointsProc(HWND hPoints, UINT message, WPARAM wParam,LPARAM lParam);
HINSTANCE hInst;
HWND hPoints;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
hInst = hInstance;
HWND hwnd;
MSG msg;
WNDCLASS w;
memset(&w,0,sizeof(WNDCLASS));
w.style = CS_HREDRAW | CS_VREDRAW;
w.lpfnWndProc = WndProc;
w.hInstance = hInstance;
w.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
w.lpszClassName = L"My Class";
RegisterClass(&w);
hwnd = CreateWindow(L"My Class", L"My title", WS_OVERLAPPEDWINDOW,
300, 200, 200, 180, NULL, NULL, hInstance, NULL);
HMENU main_menu = CreateMenu();
AppendMenu(main_menu, MF_STRING, 1111, L"Box");
WNDCLASS w2;
memset(&w2, 0, sizeof(WNDCLASS));
w2.lpfnWndProc = (WNDPROC)PointsProc;
w2.hInstance = hInst;
w2.lpszClassName = L"About";
w2.hCursor = LoadCursor(NULL, IDC_ARROW);
w2.hbrBackground = (HBRUSH)CreateSolidBrush(RGB(111, 111, 111));
RegisterClass(&w2);
hPoints = CreateWindowEx(0, L"About", (LPCTSTR) NULL,
WS_CHILD | WS_BORDER | WS_VISIBLE | WS_DISABLED, 10, 10,
100, 100, hwnd, (HMENU)1112, hInst, NULL);
ShowWindow(hPoints,SW_NORMAL);
UpdateWindow(hPoints);
SetMenu(hwnd, main_menu);
ShowWindow(hwnd,nCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LONG WINAPI WndProc(HWND hwnd, UINT Message, WPARAM wparam, LPARAM lparam)
{
switch (Message)
{
case WM_COMMAND:
switch(LOWORD(wparam))
{
case 1111:
DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hwnd, About);
return 0;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, Message, wparam, lparam);
}
return 0;
}
LRESULT CALLBACK PointsProc(HWND hPoints, UINT message, WPARAM wParam,LPARAM lParam)
{
switch(message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_PAINT:
break;
default:
return DefWindowProc(hPoints, message, wParam, lParam);
}
return 0;
}
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
The dialog box is called, only it takes a long time because PointsProc() is looping...
The problem was the break; while handling WM_PAINT in PointsProc() -- it then skips calling DefWindowProc(), so the window keeps getting WM_PAINT messages because the window remains invalid.
// case WM_PAINT:
// break;