1

I'm new to C++ so I'm not exactly sure what to put into the title of this problem. Anyway, I created a class whose purpose is to create a Label then use it later to create another Label again and again.

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);
}

I'm expecting it to have an output of two different labels, e.g.

This is Label
This is another Label

Instead, I get two Labels with the same text.

This is another Label
This is another Label

Anyway, here's the full source of the 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();
    }
}
Kadir Alan
  • 209
  • 1
  • 13
Papilion
  • 87
  • 2
  • 10

3 Answers3

1

Static class members are shared between all instances of a class. There's a static string text in your class so it is to be expected that all instances share that. If you need to store per-instance data you need to use non-static class members.

Presumably, you've used static class members so that you can put your window procedure inside the class' implementation (which needs to be static). To have a static window procedure access per-instance data has been asked and answered before (like here, here, here, or here).

IInspectable
  • 46,945
  • 8
  • 85
  • 181
0

There's no such thing as "instance" when all your variables are declared static. static means the variable is shared among all instances of the class, making it essentially global. So, when you call:

controlLabel.SetText("This is Label");

You assign your text to the global text variable. Then, calling

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

Assigns the new string to that same global text variable. Your original string was forgotten at this point.

How can you solve this? I can think of multiple ways off the top of my head, maybe you can think of something better. The idea is to somehow bind the text (or the entire controlLabel instance) to a label.

  • Putting the label text directly into the window data (using WM_SETTEXT. Then you can pull it up in LabelProc and draw it, or just let the default STATIC window procedure handle it.
  • Making the labels a custom window class that has some extra space reserved for each window instance. Then use SetWindowLong to put a pointer to a whole controlLabel in there. Raw pointers are generally not a great thing in C++, but then again, Win32 API was made for C. Then pull the instance up when needed with GetWindowLong. Just remember to un-static the text member, so it doesn't get overwritten.
  • Use a global/static std::map<HWND, controlLabel> to associate each label with an instance of controlLabel. Again, if you do this, remember to un-static the text.

Oh, and when you called any controlLabel method that somehow uses the label handle, you just randomly happened to have the handle that you wanted in the LabelHandle variable, since it's also static.

IWonderWhatThisAPIDoes
  • 1,000
  • 1
  • 4
  • 14
0

That doesn't make any sense, you won't be able to have a different instance if all your members are static.

However, since the callback of window procedure needs to be static, then you won't be able to access those non-static members inside that function.

What you can do is use the dwRefData parameter of the subclass to pass the instance of your class into your callback function. You can then cast that parameter to access non-static members.

For example in your ::Label function;

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

Then on your callback;

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

Something like that.

Another problem is you shouldn't declare your callback as private, make it public instead. And do not declare your ControlLabel object inside the WM_CREATE, make it global instead.

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);
}

Anyway, I noticed your class doesn't have destructor to destroy your LabelHandle. Make sure to do that.

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.
}
Polar
  • 3,327
  • 4
  • 42
  • 77