首页 > 解决方案 > 为什么我的子类按钮程序会弄乱我的程序?

问题描述

我对 C++ 比较陌生。我有一个问题,为什么下面发布的程序在 main.cpp 和 button_class.h 中将常量 DEMOMETHOD 设置为 1 时的行为方式。

该程序演示了对由单个 c++ 类创建的几个按钮进行子类化 - 该类有 4 个实例(4 个按钮)和两个(标记为 button1 和 button2)的子类。

我希望学习:

  1. 为什么返回0;在 button_class.cpp 中的 wm_lbuttondown 消息的处理程序中需要多实例子类化才能正常工作(设置 DEMOMETHOD = 0 以便正确的程序操作)。
  2. 为什么在单击两个子类按钮中的任何一个后,其他子类按钮和非子类按钮工作不正确。示例:如果在 DEMOMETHOD = 1 时首先单击按钮 2(或 1),则退出按钮不起作用。

我发布了可编译的(gcc 编译器,Windows 7)代码,以便更容易观察我看到的行为,并希望能帮助其他偶然发现这篇文章的新 C++ 程序员。

提前致谢。

//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
//stackoverflow 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

标签: c++winapisubclassing

解决方案


其实这两个问题是同一个问题造成的。使用的时候return 0直接返回,不用调用DefSubclassProc外面的函数switch

如果你使用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

推荐阅读