为什么我的子类按钮过程会弄乱我的程序?
Why does my subclassed button procedure mess up my program?
我对 C++ 比较陌生。我有一个问题,为什么下面的 posted 程序会在 main.cpp 和 button_class.h.
中将常量 DEMOMETHOD 设置为 1 时的行为方式
该程序演示了子class由单个 c++ class 创建的几个按钮 - class 有 4 个实例(4 个按钮)和两个(标记为 button1 和button2) 是 subclassed.
我希望学习:
- 为什么 return 0;在 button_class.cpp 中 wm_lbuttondown 消息的处理程序中需要多实例子 classing 才能正常工作(为正确的程序操作设置 DEMOMETHOD = 0)。
- 为什么单击两个子classed 按钮中的任何一个后,其他子classed 按钮和非subclassed 按钮无法正常工作。示例:如果您在 DEMOMETHOD = 1 时首先单击按钮 2(或 1),退出按钮将不起作用。
我 post 编辑了可编译(gcc 编译器,Windows 7)代码,以便更容易观察我看到的行为,并希望能帮助其他偶然发现此问题的新 c++ 程序员 post.
提前致谢。
//best way to use a defined constant in two cpp files is to use a header (.h) file that is shared by the two cpp files.
//I want to minimize how many files I post, so I define DEMOMETHOD in two separate files
//also set to 1 or 0 in button_class.h
#define DEMOMETHOD 1 //SET TO 1 to make program break, set to 0 to make program work (do this in the button_class.h file too)
#if defined(UNICODE) && !defined(_UNICODE)
#define _UNICODE
#elif defined(_UNICODE) && !defined(UNICODE)
#define UNICODE
#endif
#include <tchar.h> //for _T
//windows 7 flags
#define WINVER 0x0601
#define _WIN32_WINNT 0x0601 // Windows 7 https://docs.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt?view=vs-2019
#define _WIN32_IE 0x0700 //windows 7
#define WIN32_LEAN_AND_MEAN //results in smaller exe
#include <windows.h>
#include <iostream>
#include "button_class.h"//my button class
using namespace std;
//forward declares
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
std::string str(long data);
//declares-pre
#define IDC_CHECKBOX101 101
#define IDC_BUTTON1 102
#define IDC_BUTTON2 103
#define IDC_STATIC 104
//globals, create 4 buttons using our button class, 2 will be subclassed
Button bt,cbt,bt1,bt2;//button and checkbox button
HWND hDlg,hstatic; //main window and static label
TCHAR szClassName[ ] = _T("MyWindowsApp");
HINSTANCE hInstance;
//main window, starts the program
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpszArgument,int nCmdShow)
{
HWND hwnd;
MSG messages;
std::string tmp;
hInstance = hInst;//save instance to global variable
//create and register our main window class
WNDCLASSEX wincl; // Data structure for the windowclass
wincl.hInstance = hInst;
wincl.lpszClassName = szClassName;
wincl.lpfnWndProc = WndProc; // This function is called by windows
wincl.style = CS_DBLCLKS; // Catch double-clicks
wincl.cbSize = sizeof (WNDCLASSEX);
// Use default icon and mouse-pointer
wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
wincl.lpszMenuName = NULL; // No menu
wincl.cbClsExtra = 0; // No extra bytes after the window class
wincl.cbWndExtra = 0; // structure or the window instance
wincl.hbrBackground = (HBRUSH) (COLOR_3DFACE+1);
// Register the window class, and if it fails quit the program
if (!RegisterClassEx (&wincl)) {
MessageBox(NULL,"Failed to register WNDCLASSEX.","Error",MB_OK | MB_ICONINFORMATION);
return 1;//exit program on failure
}
//class is registered, now create main window
hwnd = CreateWindowEx (
0,//extended style
szClassName,
_T("Demo Multi Instance of Button Class"),
WS_OVERLAPPEDWINDOW, // styles - default window
CW_USEDEFAULT, // Windows decides the position
CW_USEDEFAULT, // where the window ends up on the screen
777, // The program's width (hardcoded for this demo)
411, // and height in pixels (hardcoded for this demo)
HWND_DESKTOP, // The window is a child-window to desktop
NULL, // No menu
hInst, // Program Instance handler
NULL // No Window Creation data
);
if (!hwnd) {
MessageBox(NULL,"Failed to create main window.","Error",MB_OK | MB_ICONINFORMATION);
return 1;//exit program on failure
}
hDlg = hwnd;//main window
// Make the window visible on the screen
ShowWindow (hwnd, nCmdShow);
UpdateWindow (hwnd);
while (GetMessage (&messages, NULL, 0, 0)) {
// Translate virtual-key messages into character messages
TranslateMessage(&messages);
// Send message to WindowProcedure (WndProc)
DispatchMessage(&messages);
}
// The program return-value is 0 - The value that PostQuitMessage() gave
return messages.wParam;
}
//handle messages intended for main window
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
std::string tmp;
LRESULT checked;
DWORD dwstyles,dwstylesex;
switch (message) { //wndproc handle the messages
case WM_CREATE://create controls
//always on top checkbox button (not subclassed)
dwstyles = WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX;
dwstylesex = 0;
cbt.t.hbutton = cbt.Create(hwnd,cbt,dwstylesex,dwstyles,277,8,122,26,(long) IDC_CHECKBOX101,hInstance,"Always on top",false);
//quit button (not subclassed)
dwstyles = WS_CHILD | WS_VISIBLE | WS_TABSTOP;
dwstylesex = 0;
bt.t.hbutton = bt.Create(hwnd,bt,dwstylesex,dwstyles,691,7,65,30,(long) IDCANCEL,hInstance,"&Quit",false);
//button1 (subclassed)
dwstyles = WS_CHILD | WS_VISIBLE | WS_TABSTOP;
dwstylesex = 0;
bt1.t.hbutton = bt1.Create(hwnd,bt1,dwstylesex,dwstyles,50,50,65,30,(long) IDC_BUTTON1,hInstance,"Button1",true);
if (bt1.t.hbutton == NULL) {
MessageBox(NULL,"button1 failed","error",0);
}
//button2 (subclassed)
dwstyles = WS_CHILD | WS_VISIBLE | WS_TABSTOP;
dwstylesex = 0;
bt2.t.hbutton = bt2.Create(hwnd,bt2,dwstylesex,dwstyles,50,90,65,30,(long) IDC_BUTTON2,hInstance,"Button2",true);
if (bt2.t.hbutton == NULL) {
MessageBox(NULL,"button2 failed","error",0);
}
//create a statio control and display information about DEMOMETHOD
hstatic = CreateWindowEx(0,"Static",
#if (DEMOMETHOD == 0)
"Program should work correctly since DEMOMETHOD is set to 0",
#else
"Program should mess up since DEMOMETHOD is set to 1",
#endif
WS_CHILD | WS_VISIBLE,
50, 140,
450, 30,
hwnd,
(HMENU) IDC_STATIC,
hInstance,
NULL);
break;
case WM_SIZE:
break;
case WM_DESTROY: {
PostQuitMessage(0); // send a WM_QUIT to the message queue
break;
}
case WM_CLOSE: {
DestroyWindow(hwnd);
break;
}
case WM_COMMAND: {
switch (LOWORD(wParam)) {// LOWORD ctrlid. The HIWORD specifies the notification code.
case IDC_CHECKBOX101: { // always on top
//leftover functionality from another app
if (HIWORD(wParam) == BN_CLICKED) {
checked = SendMessage((HWND)lParam, BM_GETCHECK, 0, 0);
if (checked) { // Force the program to stay always on top
SetWindowPos(hDlg, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
} else { // else no more topmost program state
SetWindowPos(hDlg, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
}
break;
}
case IDCANCEL: {//quit button
if (HIWORD(wParam) == BN_CLICKED) {
PostMessage(hDlg,WM_CLOSE,0,0);
return 0;
}
break;
}//idcancel
}// loword switch
break;
}//wm_command
default: // for messages that we don't deal with
return DefWindowProc (hwnd, message, wParam, lParam);
} //for switch(msg)
return 0;
}//end wndproc function
std::string str(long data)//convert non decimnal numeric to string
{
try {
return std::to_string((long) data);
} catch(int x) {
return std::to_string((long) data);
}
return "error in str function";
}
button_class.h
#ifndef BUTTONCLASSGUARD
#define BUTTONCLASSGUARD
//best way to use a defined constant in two cpp files is to use a header (.h) file that is shared by the two cpp files.
//I want to minimize how many files I post, so I define DEMOMETHOD in two separate files
//also set to 1/0 in main.cpp
#define DEMOMETHOD 1 //SET TO 1 to make program break (do this in main.cpp file too) set to 0 to make program work
#include <iostream>
//windows 7 flags
#define WINVER 0x0601 //windows 7
#define _WIN32_WINNT 0x0601 // Windows 7 https://docs.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt?view=vs-2019
#define _WIN32_IE 0x0700 //windows 7
#define WIN32_LEAN_AND_MEAN //results in smaller exe
#include <windows.h>
//#include <windowsx.h> //for get_x_lparam
#include <commctrl.h> //for subclass safer also make sure linker calls libcomctl32.a (gcc compiler)
class Button //define the Button class
{
private:
public:
struct T { //seems like a more convenient way to allow sets/gets for a class that only I will use
long x;
long xx;
long y;
long yy;
HWND hparent;
HINSTANCE hinstance;
long ctrlid;
HWND hbutton;
} t;
Button() //constructor
{
}
~Button() //destructor
{
}
//forward declares
HWND Create(HWND hparent, Button &tbt,DWORD dwstylesex,DWORD dwstyles, int x,int y, int xx, int yy, long ctrlid, HINSTANCE hinst, std::string Caption, bool SubClassTF);
//next is our callback function for safe subclassing using setwindowsubclass
// https://docs.microsoft.com/en-us/windows/win32/controls/subclassing-overview
//Whosebug article said static is needed and my testing confirmed this
static LRESULT CALLBACK OnEvent_Button(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
};//Button class end
#endif//buttonclassguard
button_class.cpp
//custom button class
#include "button_class.h"
extern std::string str(long data);//for demo I use extern keyword to give access to str function, normally that function is in a utilities class
HWND Button::Create(HWND hparent, Button &tbt, DWORD dwstylesex, DWORD dwstyles, int x,int y, int xx, int yy, long ctrlid, HINSTANCE hinst, std::string Caption, bool SubClassTF)
{
HWND result;
HGDIOBJ hFont = GetStockObject(ANSI_VAR_FONT);
bool tbool;
static long InstCount = 0;
std::string tmp;
tbt.t.hinstance = hinst;
tbt.t.x = x;
tbt.t.xx = xx;
tbt.t.y = y;
tbt.t.yy = yy;
tbt.t.hparent = hparent;
tbt.t.ctrlid = ctrlid;
//now create a button
tbt.t.hbutton = (HWND) NULL;
if (dwstyles == 0) {
dwstyles = WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON | WS_CLIPSIBLINGS | BS_NOTIFY;
}
if (Caption == "") {
Caption = "&Ok";
}
result = CreateWindowEx(dwstylesex,"Button",
Caption.c_str(),
dwstyles,
tbt.t.x,tbt.t.y,
tbt.t.xx,tbt.t.yy,
hparent,
(HMENU) ctrlid,
hinst,
NULL);
if (!result) {
MessageBox(NULL, "Button creation Failed.", "Error", MB_OK | MB_ICONERROR);
return result;
}
if (hFont > 0) {
SendMessage(result, WM_SETFONT,(WPARAM) hFont, 0);
}
if (SubClassTF == true) {
//subclass if here
InstCount++;//instantiation count
//now subclass using safer method https://devblogs.microsoft.com/oldnewthing/20110506-00/?p=10723 (raymond chen) (safer subclass)
//the 'this' keyword seems to change its stripes depending on whether onevent_button is static or not
// if static it seems to track the instance of the class &tbt)
// if not static (remove static keyword from .h forward declare) it seems to point to the class not an instance of the class
//static is correct for this exercise
tbool = SetWindowSubclass(
result,//window being subclassed
reinterpret_cast<SUBCLASSPROC>(this->OnEvent_Button), //&tbt.OnEvent_Button), //&OnEvent_Button), //or &tbt.onevent_button worked too sort of onevent_button can be outside the class
InstCount, //id of this subclass, my choice
reinterpret_cast<DWORD_PTR>(this) //&tbt)//was: (&tbt) ken suspects use of this is ng since, for mult instances this points to the class not the instance of the class imho semi tested
);//returns bool with result
if (tbool == false) { //subclass failed if false
tmp = "subclass failed for " + Caption;
MessageBox(NULL,tmp.c_str(),"error",0);
}
} //subclass
tbt.t.hbutton = result;
return result;//return hwnd to caller
}//::create funtion end
LRESULT CALLBACK Button::OnEvent_Button(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
//Button *tp = (Button *) dwRefData;//I believe this is the C way, seems to work
Button *tp = reinterpret_cast<Button *>(dwRefData);//c++ way, works
std::string tmp;
switch(uMsg) {
case WM_LBUTTONDOWN: // onevent_button message
tmp = "I got clicked: uidsubclass: " + str(uIdSubclass) + " tp->t.y: " + str(tp->t.y) + " dwrefdata: " + str((long) dwRefData) + " hwnd: " + str((long)hwnd) + " click ypos: " + str((long)HIWORD(lParam));
MessageBox(NULL,tmp.c_str(),"clicked",0);
//question: why is return 0; needed (why do I need to eat the message?)
//if I break; instead, I call defwindowproc and that messes up how the class works if another subclassed button is clicked 2nd
//the nonsubclassed buttons also mess up if DEMOMETHOD is set to 1 and a subclassed button (1 or 2) is clicked before
//a nonsubclassed button. why is that?
#if (DEMOMETHOD == 0)//set demomethod to 1 to break the program
//if here the program/class should work well
MessageBox(NULL,"return 0 next - should work when you click on another button next","message",0);
return 0;//needed else 2nd call by a different subclassed control doesn't work
#else
MessageBox(NULL,"break is next - doesn't work if you click on another button next","message",0);
#endif
break;//calls defwindowproc -> messes up the ability of the class to distinguish between a button1 or button2 click
case WM_DESTROY: {//onevent_button message
//remove the subclass
//raymond says: https://devblogs.microsoft.com/oldnewthing/20031111-00/?p=41883
RemoveWindowSubclass(hwnd,
reinterpret_cast<SUBCLASSPROC>(&tp->OnEvent_Button), //thisfunctionname
reinterpret_cast<UINT_PTR>(uIdSubclass) //uidsubclass
);
break; //or return DefSubclassProc(hwnd,wMsg,lParam,wParam); //per raymond chen
} //wm_destroy
default:
//for regular subclassing: return CallWindowProc(OldButtonWndProc, hwnd, wm, wParam, lParam);
//for safer subclassing do this per: https://docs.microsoft.com/en-us/windows/win32/controls/subclassing-overview
break;//return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}
//I'm a c++ beginner. I notice that most samples by experienced c++ programmers just return 0 next, however
//I find that I was using break in the switch(umsg) section and a call to defsubclassproc was needed so I am doing it this way
//note: for regular subclassing we return CallWindowProc(OldButtonWndProc...
return DefSubclassProc(hwnd, uMsg, wParam, lParam);//need this if you plan to use break for any case item
} //onevent_button
其实这两个问题是同一个问题引起的。当你使用return 0
时,你会直接return而不调用switch
之外的DefSubclassProc
函数。
如果使用break
,会跳出switch
,再次调用DefSubclassProc
函数。这就是为什么当 DEMOMETHOD
值为 1 时会连续触发同一个按钮。
所以解决方法很简单,只需要修改以下代码即可:
LRESULT CALLBACK Button::OnEvent_Button(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
//Button *tp = (Button *) dwRefData;//I believe this is the C way, seems to work
Button* tp = reinterpret_cast<Button*>(dwRefData);//c++ way, works
std::string tmp;
switch (uMsg) {
case WM_LBUTTONDOWN: // onevent_button message
tmp = "I got clicked: uidsubclass: " + str(uIdSubclass) + " tp->t.y: " + str(tp->t.y) + " dwrefdata: " + str((long)dwRefData) + " hwnd: " + str((long)hwnd) + " click ypos: " + str((long)HIWORD(lParam));
MessageBox(NULL, tmp.c_str(), "clicked", 0);
//question: why is return 0; needed (why do I need to eat the message?)
//if I break; instead, I call defwindowproc and that messes up how the class works if another subclassed button is clicked 2nd
//the nonsubclassed buttons also mess up if DEMOMETHOD is set to 1 and a subclassed button (1 or 2) is clicked before
//a nonsubclassed button. why is that?
#if (DEMOMETHOD == 0)//set demomethod to 1 to break the program
//if here the program/class should work well
MessageBox(NULL, "return 0 next - should work when you click on another button next", "message", 0);
return 0;//needed else 2nd call by a different subclassed control doesn't work
#else
MessageBox(NULL, "break is next - doesn't work if you click on another button next", "message", 0);
#endif
break;//calls defwindowproc -> messes up the ability of the class to distinguish between a button1 or button2 click
case WM_DESTROY: {//onevent_button message
//remove the subclass
//raymond says: https://devblogs.microsoft.com/oldnewthing/20031111-00/?p=41883
RemoveWindowSubclass(hwnd,
reinterpret_cast<SUBCLASSPROC>(&tp->OnEvent_Button), //thisfunctionname
uIdSubclass //uidsubclass
);
break; //or return DefSubclassProc(hwnd,wMsg,lParam,wParam); //per raymond chen
} //wm_destroy
default:
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
//for regular subclassing: return CallWindowProc(OldButtonWndProc, hwnd, wm, wParam, lParam);
//for safer subclassing do this per: https://docs.microsoft.com/en-us/windows/win32/controls/subclassing-overview
}
//I'm a c++ beginner. I notice that most samples by experienced c++ programmers just return 0 next, however
//I find that I was using break in the switch(umsg) section and a call to defsubclassproc was needed so I am doing it this way
//note: for regular subclassing we return CallWindowProc(OldButtonWndProc...
return 0; //need this if you plan to use break for any case item
} //onevent_button
我对 C++ 比较陌生。我有一个问题,为什么下面的 posted 程序会在 main.cpp 和 button_class.h.
中将常量 DEMOMETHOD 设置为 1 时的行为方式该程序演示了子class由单个 c++ class 创建的几个按钮 - class 有 4 个实例(4 个按钮)和两个(标记为 button1 和button2) 是 subclassed.
我希望学习:
- 为什么 return 0;在 button_class.cpp 中 wm_lbuttondown 消息的处理程序中需要多实例子 classing 才能正常工作(为正确的程序操作设置 DEMOMETHOD = 0)。
- 为什么单击两个子classed 按钮中的任何一个后,其他子classed 按钮和非subclassed 按钮无法正常工作。示例:如果您在 DEMOMETHOD = 1 时首先单击按钮 2(或 1),退出按钮将不起作用。
我 post 编辑了可编译(gcc 编译器,Windows 7)代码,以便更容易观察我看到的行为,并希望能帮助其他偶然发现此问题的新 c++ 程序员 post.
提前致谢。
//best way to use a defined constant in two cpp files is to use a header (.h) file that is shared by the two cpp files.
//I want to minimize how many files I post, so I define DEMOMETHOD in two separate files
//also set to 1 or 0 in button_class.h
#define DEMOMETHOD 1 //SET TO 1 to make program break, set to 0 to make program work (do this in the button_class.h file too)
#if defined(UNICODE) && !defined(_UNICODE)
#define _UNICODE
#elif defined(_UNICODE) && !defined(UNICODE)
#define UNICODE
#endif
#include <tchar.h> //for _T
//windows 7 flags
#define WINVER 0x0601
#define _WIN32_WINNT 0x0601 // Windows 7 https://docs.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt?view=vs-2019
#define _WIN32_IE 0x0700 //windows 7
#define WIN32_LEAN_AND_MEAN //results in smaller exe
#include <windows.h>
#include <iostream>
#include "button_class.h"//my button class
using namespace std;
//forward declares
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
std::string str(long data);
//declares-pre
#define IDC_CHECKBOX101 101
#define IDC_BUTTON1 102
#define IDC_BUTTON2 103
#define IDC_STATIC 104
//globals, create 4 buttons using our button class, 2 will be subclassed
Button bt,cbt,bt1,bt2;//button and checkbox button
HWND hDlg,hstatic; //main window and static label
TCHAR szClassName[ ] = _T("MyWindowsApp");
HINSTANCE hInstance;
//main window, starts the program
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpszArgument,int nCmdShow)
{
HWND hwnd;
MSG messages;
std::string tmp;
hInstance = hInst;//save instance to global variable
//create and register our main window class
WNDCLASSEX wincl; // Data structure for the windowclass
wincl.hInstance = hInst;
wincl.lpszClassName = szClassName;
wincl.lpfnWndProc = WndProc; // This function is called by windows
wincl.style = CS_DBLCLKS; // Catch double-clicks
wincl.cbSize = sizeof (WNDCLASSEX);
// Use default icon and mouse-pointer
wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
wincl.lpszMenuName = NULL; // No menu
wincl.cbClsExtra = 0; // No extra bytes after the window class
wincl.cbWndExtra = 0; // structure or the window instance
wincl.hbrBackground = (HBRUSH) (COLOR_3DFACE+1);
// Register the window class, and if it fails quit the program
if (!RegisterClassEx (&wincl)) {
MessageBox(NULL,"Failed to register WNDCLASSEX.","Error",MB_OK | MB_ICONINFORMATION);
return 1;//exit program on failure
}
//class is registered, now create main window
hwnd = CreateWindowEx (
0,//extended style
szClassName,
_T("Demo Multi Instance of Button Class"),
WS_OVERLAPPEDWINDOW, // styles - default window
CW_USEDEFAULT, // Windows decides the position
CW_USEDEFAULT, // where the window ends up on the screen
777, // The program's width (hardcoded for this demo)
411, // and height in pixels (hardcoded for this demo)
HWND_DESKTOP, // The window is a child-window to desktop
NULL, // No menu
hInst, // Program Instance handler
NULL // No Window Creation data
);
if (!hwnd) {
MessageBox(NULL,"Failed to create main window.","Error",MB_OK | MB_ICONINFORMATION);
return 1;//exit program on failure
}
hDlg = hwnd;//main window
// Make the window visible on the screen
ShowWindow (hwnd, nCmdShow);
UpdateWindow (hwnd);
while (GetMessage (&messages, NULL, 0, 0)) {
// Translate virtual-key messages into character messages
TranslateMessage(&messages);
// Send message to WindowProcedure (WndProc)
DispatchMessage(&messages);
}
// The program return-value is 0 - The value that PostQuitMessage() gave
return messages.wParam;
}
//handle messages intended for main window
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
std::string tmp;
LRESULT checked;
DWORD dwstyles,dwstylesex;
switch (message) { //wndproc handle the messages
case WM_CREATE://create controls
//always on top checkbox button (not subclassed)
dwstyles = WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX;
dwstylesex = 0;
cbt.t.hbutton = cbt.Create(hwnd,cbt,dwstylesex,dwstyles,277,8,122,26,(long) IDC_CHECKBOX101,hInstance,"Always on top",false);
//quit button (not subclassed)
dwstyles = WS_CHILD | WS_VISIBLE | WS_TABSTOP;
dwstylesex = 0;
bt.t.hbutton = bt.Create(hwnd,bt,dwstylesex,dwstyles,691,7,65,30,(long) IDCANCEL,hInstance,"&Quit",false);
//button1 (subclassed)
dwstyles = WS_CHILD | WS_VISIBLE | WS_TABSTOP;
dwstylesex = 0;
bt1.t.hbutton = bt1.Create(hwnd,bt1,dwstylesex,dwstyles,50,50,65,30,(long) IDC_BUTTON1,hInstance,"Button1",true);
if (bt1.t.hbutton == NULL) {
MessageBox(NULL,"button1 failed","error",0);
}
//button2 (subclassed)
dwstyles = WS_CHILD | WS_VISIBLE | WS_TABSTOP;
dwstylesex = 0;
bt2.t.hbutton = bt2.Create(hwnd,bt2,dwstylesex,dwstyles,50,90,65,30,(long) IDC_BUTTON2,hInstance,"Button2",true);
if (bt2.t.hbutton == NULL) {
MessageBox(NULL,"button2 failed","error",0);
}
//create a statio control and display information about DEMOMETHOD
hstatic = CreateWindowEx(0,"Static",
#if (DEMOMETHOD == 0)
"Program should work correctly since DEMOMETHOD is set to 0",
#else
"Program should mess up since DEMOMETHOD is set to 1",
#endif
WS_CHILD | WS_VISIBLE,
50, 140,
450, 30,
hwnd,
(HMENU) IDC_STATIC,
hInstance,
NULL);
break;
case WM_SIZE:
break;
case WM_DESTROY: {
PostQuitMessage(0); // send a WM_QUIT to the message queue
break;
}
case WM_CLOSE: {
DestroyWindow(hwnd);
break;
}
case WM_COMMAND: {
switch (LOWORD(wParam)) {// LOWORD ctrlid. The HIWORD specifies the notification code.
case IDC_CHECKBOX101: { // always on top
//leftover functionality from another app
if (HIWORD(wParam) == BN_CLICKED) {
checked = SendMessage((HWND)lParam, BM_GETCHECK, 0, 0);
if (checked) { // Force the program to stay always on top
SetWindowPos(hDlg, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
} else { // else no more topmost program state
SetWindowPos(hDlg, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
}
break;
}
case IDCANCEL: {//quit button
if (HIWORD(wParam) == BN_CLICKED) {
PostMessage(hDlg,WM_CLOSE,0,0);
return 0;
}
break;
}//idcancel
}// loword switch
break;
}//wm_command
default: // for messages that we don't deal with
return DefWindowProc (hwnd, message, wParam, lParam);
} //for switch(msg)
return 0;
}//end wndproc function
std::string str(long data)//convert non decimnal numeric to string
{
try {
return std::to_string((long) data);
} catch(int x) {
return std::to_string((long) data);
}
return "error in str function";
}
button_class.h
#ifndef BUTTONCLASSGUARD
#define BUTTONCLASSGUARD
//best way to use a defined constant in two cpp files is to use a header (.h) file that is shared by the two cpp files.
//I want to minimize how many files I post, so I define DEMOMETHOD in two separate files
//also set to 1/0 in main.cpp
#define DEMOMETHOD 1 //SET TO 1 to make program break (do this in main.cpp file too) set to 0 to make program work
#include <iostream>
//windows 7 flags
#define WINVER 0x0601 //windows 7
#define _WIN32_WINNT 0x0601 // Windows 7 https://docs.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt?view=vs-2019
#define _WIN32_IE 0x0700 //windows 7
#define WIN32_LEAN_AND_MEAN //results in smaller exe
#include <windows.h>
//#include <windowsx.h> //for get_x_lparam
#include <commctrl.h> //for subclass safer also make sure linker calls libcomctl32.a (gcc compiler)
class Button //define the Button class
{
private:
public:
struct T { //seems like a more convenient way to allow sets/gets for a class that only I will use
long x;
long xx;
long y;
long yy;
HWND hparent;
HINSTANCE hinstance;
long ctrlid;
HWND hbutton;
} t;
Button() //constructor
{
}
~Button() //destructor
{
}
//forward declares
HWND Create(HWND hparent, Button &tbt,DWORD dwstylesex,DWORD dwstyles, int x,int y, int xx, int yy, long ctrlid, HINSTANCE hinst, std::string Caption, bool SubClassTF);
//next is our callback function for safe subclassing using setwindowsubclass
// https://docs.microsoft.com/en-us/windows/win32/controls/subclassing-overview
//Whosebug article said static is needed and my testing confirmed this
static LRESULT CALLBACK OnEvent_Button(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
};//Button class end
#endif//buttonclassguard
button_class.cpp
//custom button class
#include "button_class.h"
extern std::string str(long data);//for demo I use extern keyword to give access to str function, normally that function is in a utilities class
HWND Button::Create(HWND hparent, Button &tbt, DWORD dwstylesex, DWORD dwstyles, int x,int y, int xx, int yy, long ctrlid, HINSTANCE hinst, std::string Caption, bool SubClassTF)
{
HWND result;
HGDIOBJ hFont = GetStockObject(ANSI_VAR_FONT);
bool tbool;
static long InstCount = 0;
std::string tmp;
tbt.t.hinstance = hinst;
tbt.t.x = x;
tbt.t.xx = xx;
tbt.t.y = y;
tbt.t.yy = yy;
tbt.t.hparent = hparent;
tbt.t.ctrlid = ctrlid;
//now create a button
tbt.t.hbutton = (HWND) NULL;
if (dwstyles == 0) {
dwstyles = WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON | WS_CLIPSIBLINGS | BS_NOTIFY;
}
if (Caption == "") {
Caption = "&Ok";
}
result = CreateWindowEx(dwstylesex,"Button",
Caption.c_str(),
dwstyles,
tbt.t.x,tbt.t.y,
tbt.t.xx,tbt.t.yy,
hparent,
(HMENU) ctrlid,
hinst,
NULL);
if (!result) {
MessageBox(NULL, "Button creation Failed.", "Error", MB_OK | MB_ICONERROR);
return result;
}
if (hFont > 0) {
SendMessage(result, WM_SETFONT,(WPARAM) hFont, 0);
}
if (SubClassTF == true) {
//subclass if here
InstCount++;//instantiation count
//now subclass using safer method https://devblogs.microsoft.com/oldnewthing/20110506-00/?p=10723 (raymond chen) (safer subclass)
//the 'this' keyword seems to change its stripes depending on whether onevent_button is static or not
// if static it seems to track the instance of the class &tbt)
// if not static (remove static keyword from .h forward declare) it seems to point to the class not an instance of the class
//static is correct for this exercise
tbool = SetWindowSubclass(
result,//window being subclassed
reinterpret_cast<SUBCLASSPROC>(this->OnEvent_Button), //&tbt.OnEvent_Button), //&OnEvent_Button), //or &tbt.onevent_button worked too sort of onevent_button can be outside the class
InstCount, //id of this subclass, my choice
reinterpret_cast<DWORD_PTR>(this) //&tbt)//was: (&tbt) ken suspects use of this is ng since, for mult instances this points to the class not the instance of the class imho semi tested
);//returns bool with result
if (tbool == false) { //subclass failed if false
tmp = "subclass failed for " + Caption;
MessageBox(NULL,tmp.c_str(),"error",0);
}
} //subclass
tbt.t.hbutton = result;
return result;//return hwnd to caller
}//::create funtion end
LRESULT CALLBACK Button::OnEvent_Button(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
//Button *tp = (Button *) dwRefData;//I believe this is the C way, seems to work
Button *tp = reinterpret_cast<Button *>(dwRefData);//c++ way, works
std::string tmp;
switch(uMsg) {
case WM_LBUTTONDOWN: // onevent_button message
tmp = "I got clicked: uidsubclass: " + str(uIdSubclass) + " tp->t.y: " + str(tp->t.y) + " dwrefdata: " + str((long) dwRefData) + " hwnd: " + str((long)hwnd) + " click ypos: " + str((long)HIWORD(lParam));
MessageBox(NULL,tmp.c_str(),"clicked",0);
//question: why is return 0; needed (why do I need to eat the message?)
//if I break; instead, I call defwindowproc and that messes up how the class works if another subclassed button is clicked 2nd
//the nonsubclassed buttons also mess up if DEMOMETHOD is set to 1 and a subclassed button (1 or 2) is clicked before
//a nonsubclassed button. why is that?
#if (DEMOMETHOD == 0)//set demomethod to 1 to break the program
//if here the program/class should work well
MessageBox(NULL,"return 0 next - should work when you click on another button next","message",0);
return 0;//needed else 2nd call by a different subclassed control doesn't work
#else
MessageBox(NULL,"break is next - doesn't work if you click on another button next","message",0);
#endif
break;//calls defwindowproc -> messes up the ability of the class to distinguish between a button1 or button2 click
case WM_DESTROY: {//onevent_button message
//remove the subclass
//raymond says: https://devblogs.microsoft.com/oldnewthing/20031111-00/?p=41883
RemoveWindowSubclass(hwnd,
reinterpret_cast<SUBCLASSPROC>(&tp->OnEvent_Button), //thisfunctionname
reinterpret_cast<UINT_PTR>(uIdSubclass) //uidsubclass
);
break; //or return DefSubclassProc(hwnd,wMsg,lParam,wParam); //per raymond chen
} //wm_destroy
default:
//for regular subclassing: return CallWindowProc(OldButtonWndProc, hwnd, wm, wParam, lParam);
//for safer subclassing do this per: https://docs.microsoft.com/en-us/windows/win32/controls/subclassing-overview
break;//return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}
//I'm a c++ beginner. I notice that most samples by experienced c++ programmers just return 0 next, however
//I find that I was using break in the switch(umsg) section and a call to defsubclassproc was needed so I am doing it this way
//note: for regular subclassing we return CallWindowProc(OldButtonWndProc...
return DefSubclassProc(hwnd, uMsg, wParam, lParam);//need this if you plan to use break for any case item
} //onevent_button
其实这两个问题是同一个问题引起的。当你使用return 0
时,你会直接return而不调用switch
之外的DefSubclassProc
函数。
如果使用break
,会跳出switch
,再次调用DefSubclassProc
函数。这就是为什么当 DEMOMETHOD
值为 1 时会连续触发同一个按钮。
所以解决方法很简单,只需要修改以下代码即可:
LRESULT CALLBACK Button::OnEvent_Button(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
//Button *tp = (Button *) dwRefData;//I believe this is the C way, seems to work
Button* tp = reinterpret_cast<Button*>(dwRefData);//c++ way, works
std::string tmp;
switch (uMsg) {
case WM_LBUTTONDOWN: // onevent_button message
tmp = "I got clicked: uidsubclass: " + str(uIdSubclass) + " tp->t.y: " + str(tp->t.y) + " dwrefdata: " + str((long)dwRefData) + " hwnd: " + str((long)hwnd) + " click ypos: " + str((long)HIWORD(lParam));
MessageBox(NULL, tmp.c_str(), "clicked", 0);
//question: why is return 0; needed (why do I need to eat the message?)
//if I break; instead, I call defwindowproc and that messes up how the class works if another subclassed button is clicked 2nd
//the nonsubclassed buttons also mess up if DEMOMETHOD is set to 1 and a subclassed button (1 or 2) is clicked before
//a nonsubclassed button. why is that?
#if (DEMOMETHOD == 0)//set demomethod to 1 to break the program
//if here the program/class should work well
MessageBox(NULL, "return 0 next - should work when you click on another button next", "message", 0);
return 0;//needed else 2nd call by a different subclassed control doesn't work
#else
MessageBox(NULL, "break is next - doesn't work if you click on another button next", "message", 0);
#endif
break;//calls defwindowproc -> messes up the ability of the class to distinguish between a button1 or button2 click
case WM_DESTROY: {//onevent_button message
//remove the subclass
//raymond says: https://devblogs.microsoft.com/oldnewthing/20031111-00/?p=41883
RemoveWindowSubclass(hwnd,
reinterpret_cast<SUBCLASSPROC>(&tp->OnEvent_Button), //thisfunctionname
uIdSubclass //uidsubclass
);
break; //or return DefSubclassProc(hwnd,wMsg,lParam,wParam); //per raymond chen
} //wm_destroy
default:
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
//for regular subclassing: return CallWindowProc(OldButtonWndProc, hwnd, wm, wParam, lParam);
//for safer subclassing do this per: https://docs.microsoft.com/en-us/windows/win32/controls/subclassing-overview
}
//I'm a c++ beginner. I notice that most samples by experienced c++ programmers just return 0 next, however
//I find that I was using break in the switch(umsg) section and a call to defsubclassproc was needed so I am doing it this way
//note: for regular subclassing we return CallWindowProc(OldButtonWndProc...
return 0; //need this if you plan to use break for any case item
} //onevent_button