class 的 C++ 不同实例

C++ different instance of class

我是 C++ 的新手,所以我不确定在这个问题的标题中应该写些什么。无论如何,我创建了一个 class,其目的是创建一个标签,然后用它来一次又一次地创建另一个标签。

CALLBACK MyClassName::WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) noexcept -> LRESULT {
   ....
        switch (msg) {
    ....
        case WM_CREATE:
        {  
            ControlLabel controlLabel, anotherLabel; //declare two control label
            
            controlLabel.Label(123, hwnd);          //Set new id and window handle for label
            controlLabel.SetXPosition(68);          //Set x position
            controlLabel.SetYPosition(110);         //Set y position
            controlLabel.SetText("This is Label");  //Set the text of Label
            controlLabel.SetFontSize(14);           //Set the font size of the text


            anotherLabel.Label(456, hwnd);          //Create and set new id and window handle for another label
            anotherLabel.SetXPosition(68);          //Set x position of another label
            anotherLabel.SetYPosition(140);         //Set y position of another label
            anotherLabel.SetText("This is another Label");  //Set the text of another label
            anotherLabel.SetFontSize(14);           //Set the font size of another label

            break;
        }
        ....
    return ::DefWindowProcW(hwnd, msg, wparam, lparam);
}

我希望它有两个不同标签的输出,例如

This is Label
This is another Label

相反,我得到了两个具有相同文本的标签。

This is another Label
This is another Label

无论如何,这是 class 的完整来源。

ControlLabel.H

#pragma once

#ifndef CONTROLLABEL_H
#define CONTROLLABEL_H
#include "Header.h"

class ControlLabel {

public:
    ControlLabel();
    HWND Label(int Label_ID, HWND WindowHandle);
    void SetXPosition(int xPosition);
    void SetYPosition(int yPosition);
    void SetText(string Text);
    void SetFontFamily(string FontFamily);
    void SetFontSize(int FontSize);
    void SetFontColor(int R, int G, int B);
    void SetBackgroundColor(int Rr, int Gg, int Bb, bool SetBGColor);

private:
    void UpdateLabel();
    void SetWidthAndHeights();
    static std::wstring StringConverter(const std::string& s);
    static LRESULT CALLBACK LabelProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
    static HWND LabelHandle;
    static SolidBrush vFontColor;
    static string text, vFontFamily;
    static bool SetBGColor;
    static int xPosition, yPosition, width, height, LABEL_ID, vFontSize, R, G, B, BckR, BckG, BckB;
};

#endif

ControlLabel.cpp

#include "ControlLabel.h"

HWND ControlLabel::LabelHandle = NULL;
int ControlLabel::xPosition = 0;
int ControlLabel::yPosition = 0;
int ControlLabel::width = 0;
int ControlLabel::height = 0;
int ControlLabel::LABEL_ID = 0;
int ControlLabel::vFontSize = 12;
int ControlLabel::R = 0;
int ControlLabel::G = 0;
int ControlLabel::B = 0;
int ControlLabel::BckR = 0;
int ControlLabel::BckG = 0;
int ControlLabel::BckB = 0;
bool ControlLabel::SetBGColor = FALSE;
string ControlLabel::text = "Label";
string ControlLabel::vFontFamily = "Segoe UI";

ControlLabel::ControlLabel() {}

/** This function is used to convert string into std::wstring. **/
std::wstring ControlLabel::StringConverter(const std::string& s) {
    int len;
    int slength = (int)s.length() + 1;
    len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0);
    wchar_t* buf = new wchar_t[len];
    MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len);
    std::wstring r(buf);
    delete[] buf;
    return r;
}

/** This function is used to automatically set the Width and Height of static control base on the length of the text. **/
void ControlLabel::SetWidthAndHeights() {    
    std::wstring fontFamilyTemp = StringConverter(vFontFamily);
    std::wstring  textTemp = StringConverter(text);
    LPCWSTR textLabel = textTemp.c_str();

    HDC hdc = GetDC(LabelHandle);//static control
    HFONT hFont = CreateFont(
          -MulDiv(vFontSize, GetDeviceCaps(hdc, LOGPIXELSX), 90), //calculate the actual cHeight.
          0, 0, 0, // normal orientation
          FW_NORMAL,   // normal weight--e.g., bold would be FW_BOLD
          false, false, false, // not italic, underlined or strike out
          DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, // select only outline (not bitmap) fonts
          CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, VARIABLE_PITCH | FF_SWISS, fontFamilyTemp.c_str());

    SIZE size;
    HFONT oldfont = (HFONT)SelectObject(hdc, hFont);
    GetTextExtentPoint32(hdc, textLabel, wcslen(textLabel), &size);
    width = size.cx;
    height = size.cy;

    SelectObject(hdc, oldfont); //don't forget to select the old.
    DeleteObject(hFont); //always delete the object after creating it.
    ReleaseDC(LabelHandle, hdc); //alway reelase dc after using.

    /*char buffer[100];
    sprintf_s(buffer, "WIDTH: %d | HEIGHT: %d\n", width, height);
    OutputDebugStringA(buffer);*/
}

/** This function will be called when new option is set. For example, fontSize is set. **/
void ControlLabel::UpdateLabel() {
    if(LabelHandle != NULL) {
        SetWidthAndHeights();
        SetWindowPos(LabelHandle, nullptr, xPosition, yPosition, width, height, SWP_NOZORDER | SWP_NOOWNERZORDER);
        InvalidateRect(LabelHandle, NULL, FALSE);
        UpdateWindow(LabelHandle);
    }
}

/** This is the callback function of static control. **/
LRESULT CALLBACK ControlLabel::LabelProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) {
    switch(uMsg) {
        case WM_ERASEBKGND: {
            if(SetBGColor) { //We only want to do this if the SetColor is modified to true, meaning we want to set the color of background.
                RECT rect;
                GetClientRect(hwnd, &rect);
                FillRect((HDC)wParam, &rect, CreateSolidBrush(RGB(BckR, BckG, BckB))); //set titlebar background color.
                return 1; //return 1, meaning we take care of erasing the background.
            }
            return 0;
        }case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            Graphics g(hdc);

            std::wstring fontFamilyTemp = StringConverter(vFontFamily);
            std::wstring  textTemp = StringConverter(text);

            FontFamily  theFontFamily(fontFamilyTemp.c_str());
            Font        font(&theFontFamily, vFontSize, FontStyleRegular, UnitPixel);
            SolidBrush  brush(Color(255, R, G, B));
            PointF      pointF(0.0f, 0.0f);

            TextRenderingHint hint = g.GetTextRenderingHint(); // Get the text rendering hint.
            g.SetTextRenderingHint(TextRenderingHintAntiAlias); // Set the text rendering hint to TextRenderingHintAntiAlias. 
            g.DrawString(textTemp.c_str(), -1, &font, pointF, &brush); 

            EndPaint(hwnd, &ps);
            return TRUE;
        }case WM_NCDESTROY: {
            RemoveWindowSubclass(hwnd, LabelProc, uIdSubclass);
            return 0;
        }
    }
    return DefSubclassProc(hwnd, uMsg, wParam, lParam);
}

/** Use this function to create a Label. Parent or WindowHandle must be specified, this is where the Label will be drawn. Unique Label ID must be specified. **/
HWND ControlLabel::Label(int Label_ID, HWND WindowHandle) {
    LABEL_ID = Label_ID;
    LabelHandle = CreateWindowEx(0, L"STATIC", NULL, WS_CHILD | WS_VISIBLE | SS_OWNERDRAW, xPosition, yPosition, width, height, WindowHandle, NULL, NULL, NULL); //create the static control.
    SetWindowSubclass(LabelHandle, &LabelProc, LABEL_ID, 0);
    return LabelHandle;
}

/** Use this function to set the X Position of the Label. **/
void ControlLabel::SetXPosition(int xxPosition) {
    if(LabelHandle != NULL) {
        xPosition = xxPosition; //set xposition
        UpdateLabel();
    }
}

/** Use this function to set the Y Position of the Label. **/
void ControlLabel::SetYPosition(int yyPosition) {
    if(LabelHandle != NULL) {
        yPosition = yyPosition; //set xposition
        UpdateLabel();
    }
}

/** Use this function to set the text of the Label. **/
void ControlLabel::SetText(string ttext) {
    if(LabelHandle != NULL) {
        text = ttext; //set text
        UpdateLabel();
    }
}

/** Use this function to set the font family of the Label. **/
void ControlLabel::SetFontFamily(string font_family) {
    if(LabelHandle != NULL) {
        vFontFamily = font_family; //set font family
        UpdateLabel();
    }
}

/** Use this function to set the font size of the Label. **/
void ControlLabel::SetFontSize(int size) {
    if(LabelHandle != NULL) {
        vFontSize = size; //set font size
        UpdateLabel();
    }
}

/** Use this Function to set the font color of the Label using RGB. **/
void ControlLabel::SetFontColor(int Rr, int Gg, int Bb) {
    if(LabelHandle != NULL) {
        R = Rr; 
        G = Gg; 
        B = Bb; 
        UpdateLabel();
    }
}

/** Use this Function to set the background color of the Label using RGB. Last parameter must be TRUE if you want to set your own background color. **/
void ControlLabel::SetBackgroundColor(int Rr, int Gg, int Bb, bool setColor) {
    if(LabelHandle != NULL) {
        SetBGColor = setColor;
        BckR = Rr;
        BckG = Gg;
        BckB = Bb;
        UpdateLabel();
    }
}

Static class members 在 class 的所有实例之间共享。您的 class 中有一个 static string text,因此可以预期所有实例都共享它。如果您需要存储每个实例的数据,您需要使用非静态 class 成员。

据推测,您使用了静态 class 成员,这样您就可以将 window 过程放在 class' 实现中(需要是静态的)。要让静态 window 过程访问每个实例数据,之前已经询问并回答过(如 here, here, here, or )。

当声明了所有变量时,就没有“实例”这样的东西 staticstatic 表示该变量在 class 的所有实例之间共享,使其本质上是全局的。所以,当你打电话时:

controlLabel.SetText("This is Label");

您将文本分配给全局 text 变量。然后,调用

anotherLabel.SetText("This is another Label");

将新字符串分配给同一个全局 text 变量。此时忘记了您的原始字符串。

你怎么解决这个问题?我可以想到多种方法,也许您可​​以想到更好的方法。这个想法是以某种方式将文本(或整个 controlLabel 实例)绑定到标签。

  • 将标签文字直接放入window数据中(使用WM_SETTEXT。然后可以在LabelProc中拉上来绘制,或者直接让默认STATIC window 程序处理它。
  • 将标签设为自定义 window class,为每个 window 实例保留一些额外的 space。然后使用 SetWindowLong 将指向整个 controlLabel 的指针放在那里。原始指针在 C++ 中通常不是什么好东西,但话又说回来,Win32 API 是为 C 设计的。然后在需要时使用 GetWindowLong 拉起实例。请记住取消 static text 成员,这样它就不会被覆盖。
  • 使用 global/static std::map<HWND, controlLabel> 将每个标签与 controlLabel 的实例相关联。同样,如果您这样做,请记住取消 static text.

哦,当你调用任何以某种方式使用标签句柄的 controlLabel 方法时,你只是随机碰巧在 LabelHandle 变量中拥有你想要的句柄,因为它也是 static.

这没有任何意义,如果您的所有成员都是静态的,您将无法拥有不同的实例。

但是,由于 window 过程的回调需要是静态的,那么您将无法访问该函数内的那些非静态成员。

你可以做的是使用 subclass 的 dwRefData 参数将你的 class 的实例传递到你的回调函数中。然后您可以强制转换该参数以访问非静态成员。

例如在您的 ::Label 函数中;

SetWindowSubclass(LabelHandle, &LabelProc, LABEL_ID, <DWORD_PTR>(this)); //notice the last parameter, pass `this` instance so you can use `dwRefData`.

然后在您的回电中;

ControlLabel* controlLabel = reinterpret_cast<ControlLabel*>(dwRefData); //type cast the value of dwRefData.
controlLabel->text //you can now access your non-static variable text
controlLabel->vFontFamily //you can now access your non-static variable vFontFamily

类似的东西。

另一个问题是您不应该将回调声明为私有,而应将其设为 public。并且不要在 WM_CREATE 中声明您的 ControlLabel 对象,而是将其设为全局对象。

ControlLabel controlLabel, anotherLabel; //declare two control label as Global.

CALLBACK MyClassName::WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) noexcept -> LRESULT {
   ....
        switch (msg) {
    ....
        case WM_CREATE:
        {  
            controlLabel.Label(123, hwnd);          //Set new id and window handle for label
            ...
            anotherLabel.Label(456, hwnd);          //Create and set new id and window handle for another label
            ...
            break;
        }
        ....
    return ::DefWindowProcW(hwnd, msg, wparam, lparam);
}

无论如何,我注意到你的 class 没有析构函数来销毁你的 LabelHandle。确保这样做。

ControlLabel.h   //In your ControlLabel.h
~ControlLabel(); //should be in public

ControlLabel.cpp  //In your ControlLabel.cpp
ControlLabel::~ControlLabel() {
    if(LabelHandle) DestroyWindow(LabelHandle); //destroy when done.
}