如何根据内容自动设置静态控件的宽高?

How to automatically set the width and height of static control base on its content?

我正在尝试创建一个 Label class 供以后重用。我所做的是创建一个静态控件,然后使用 GDI+ 库在其上 DrawString

差不多完成了,我只有一个问题需要自动设置静态控件的宽度和高度以适合上面的文本。

/** Call this function to redraw the content of static control **/
void ControlLabel::UpdateLabel() {
    if(LabelHandle != NULL) {
        SetWidthAndHeight();
        SetWindowPos(LabelHandle, nullptr, xPosition, yPosition, width, height,  SWP_NOZORDER | SWP_NOOWNERZORDER);
        InvalidateRect(LabelHandle, NULL, FALSE);
        UpdateWindow(LabelHandle);
    }
}
/** THis function is the callback of the 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 widestr;
            widestr = std::wstring(vFontFamily.begin(), vFontFamily.end());

            FontFamily  theFontFamily(widestr.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. 

            widestr = std::wstring(text.begin(), text.end());  // convert text to std::wstring:
            g.DrawString(widestr.c_str(), -1, &font, pointF, &brush);       // get the C string

            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 specify, this is where the Label will be draw. Unique Label ID must be specify. **/
HWND ControlLabel::Label(int Label_ID, HWND WindowHandle) {
    SetWidthAndHeight();
    LabelHandle = CreateWindowEx(0, L"STATIC", NULL, WS_CHILD | WS_VISIBLE | SS_OWNERDRAW, 0, 0, width, height, WindowHandle, NULL, NULL, NULL); //create the static control.
    SetWindowSubclass(LabelHandle, &LabelProc, LABEL_ID, 0);
    return LabelHandle;
}

我想到了用GetTextExtentPoint32来计算字符串的高度和宽度,不幸的是,由于字体大小和字体系列的原因,我没有这样做。

void ControlLabel::SetWidthAndHeight() {
    std::wstring stemp = StringConverter(vFontFamily);
    LPCWSTR result = stemp.c_str();

    HDC hdc = GetDC(LabelHandle);//static control
    const wchar_t* buf = L"Hello World, this is 25 font size.";
    /*//(font test 1)
       HFONT hFont = CreateFont(vFontSize, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET,
    OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, result);
    */

    //(font test 2)
    FontFamily  theFontFamily(result);
    Font        font(&theFontFamily, vFontSize, FontStyleRegular, UnitPixel);

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

    SelectObject(hdc, oldfont);
    DeleteObject(&font);
    ReleaseDC(LabelHandle, hdc);  
}

我该如何解决?

更新

这是我的 class.

的完整源代码

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
    const wchar_t* buf = L"Hello World, this is 25 font size.";

    HFONT hFont = CreateFont(
          -MulDiv(vFontSize, GetDeviceCaps(hdc, LOGPIXELSX), 90),
          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);
    DeleteObject(hFont);
    ReleaseDC(LabelHandle, hdc);

    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 specify, this is where the Label will be draw. Unique Label ID must be specify. **/
HWND ControlLabel::Label(int Label_ID, HWND WindowHandle) {
    LABEL_ID = Label_ID;
    LabelHandle = CreateWindowEx(0, L"STATIC", NULL, WS_CHILD | WS_VISIBLE | SS_OWNERDRAW, 0, 0, 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();
    }
}

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

Header.h

#pragma once

#pragma comment(linker,"\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") 

#include <stdexcept>
#include <system_error>

#include <Windows.h>
#include <commctrl.h>
#include <windowsx.h>
#include <dwmapi.h>
#include <tchar.h>
#include <string>
#include <thread>
#include <chrono>

#include <objidl.h>
#include <gdiplus.h>
using namespace std;
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
#pragma comment(lib, "dwmapi.lib")
#pragma comment(lib, "comctl32.lib")

然后在我的主window上,我是这样用的

case WM_CREATE:{  
    ControlLabel controlLabel;
    controlLabel.Label(123, hwnd); //create the label.
    controlLabel.SetXPosition(10); //set the x position.
    controlLabel.SetYPosition(10); //set the x position.
    controlLabel.SetText("Hello World, this is 25 font size."); //set the text.
    controlLabel.SetFontSize(20); //set the font size.
    controlLabel.SetFontFamily("Calibri"); //set the font family.
    controlLabel.SetFontColor(15, 86, 209); //set the font color.
    controlLabel.SetBackgroundColor(220, 222, 224, true); //set the background color.
    
    /**
        I'm still planning to add more options like italic, bold, etc.
    **/
}

Polar 的建议解决方案有效,但我不知道那是否是正确的方法。我仍在阅读给定的文档。

有两种方法可以计算指定文本字符串的宽度和高度。您可以不使用 GetTextExtentPoint32 and Graphics::MeasureString 来获得实际大小,具体取决于您绘制文本的方式。

在您的例子中,您使用 GDI+ but you are measuring the width and height of it using the classic GDI which will give you a different result because of scaling. Although you can still use GetTextExtentPoint32 to measure the text. However, you will need to handle the DPI 绘制文本以获得与使用 GDI+ 绘制的文本相同的宽度和高度。 GDI+是对GDI的改进,两者有区别。例如,他们如何绘制字符串的缩放比例。

以下代码显示了如何使用 Graphics::DrawString 绘制字符串并使用 Graphics::MeasureString 计算宽度和高度.使用 GDI+.

完成绘图和计算
/** Draw the string using GDI+ Graphics::DrawString **/
void DrawString(){
    HDC hdc = GetDC(hwnd);                                              //get dc of your handle.
    Graphics g(hdc);
    
    FontFamily  fontFamily("Arial Black");                              // Set font family.
    Font        font(&fontFamily, 12, FontStyleRegular, UnitPixel);     // Create the font.
    SolidBrush  brush(Color(255, 0, 0, 0));                             // Set font color
    PointF      pointF(0.0f, 0.0f);                                     // Set X and Y position.

    TextRenderingHint hint = g.GetTextRenderingHint();                  // Get the text rendering hint.
    g.SetTextRenderingHint(TextRenderingHintAntiAlias);                 // Make sure the rendering is high quality. 
    g.DrawString(L"Hello World", -1, &font, pointF, &brush);            // Draw the string.
    
    DeleteObject(&font);                                                // always delete the font when done.
    DeleteObject(&brush);                                               // always delete the brush when done.
    ReleaseDC(LabelHandle, hdc);                                        // always release the DC when done.
}

/** Measure the string using GDI+ Graphics::MeasureString **/
void MeasureString(){
    HDC hdc = GetDC(hwnd);                                                      // get dc of your handle.
    Graphics graphics(hdc);                                                     // setup graphics.
    FontFamily  theFontFamily(vFontFamily);                                     // setup your font family.
    Font        font(&theFontFamily, vFontSize, FontStyleRegular, UnitPixel);   // create the font the same way how you do it on your paint message.
    PointF      pointF(0.0f, 0.0f);                                             // use PointF instead of RectF since thats how you paint it.

    RectF boundRect;                                                            // setup boundRect to get the width and height.
    graphics.MeasureString(text, -1, &font, pointF, &boundRect);                // Measure the text of the string

    int width = boundRect.Width;                                                // get the width of text from boundRect.
    in height = boundRect.Height;                                               // get the height of text from boundRect.

    DeleteObject(&font);                                                        // delete the font.
    ReleaseDC(LabelHandle, hdc);                                           // Release the DC.
}

另一个示例显示了如何使用 经典 GDI 绘制字符串并使用 GetTextExtenPoint32 计算宽度和高度。绘图和计算是使用经典的 GDI.

/** Draw the string using the classic GDI DrawText **/
void DrawString(){
    int fontSize = 12;                                                  // Font Size.
    HDC hdc = GetDC(hwnd);                                              // Get DC of your handle.
    HFONT hFont = CreateFont(
         -MulDiv(fontSize, GetDeviceCaps(hdc, LOGPIXELSX), 72),         // Calculate the actual cHeight.
         0, 0, 0,                                                       // Normal orientation
         FW_NORMAL,                                                     // Normal weight--e.g., bold would be FW_BOLD, or use integer from 0 to 1000.
         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,
         TEXT("Arial Black"));                                          // Font family.
   
    HFONT oldfont = (HFONT)SelectObject(hdc, hFont);                    // Select the new font.
    RECT      rect = {0, 0, 200, 50};                                   // Rectangle (in logical coordinates) in which the text is to be formatted.
    
    DrawText(hdc, L"Helow World", -1, &rect, DT_CENTER | DT_VCENTER);   // Draw the text Horizontal and Vertical Center.
   
    SelectObject(hdc, oldfont);                                         // don't forget to select the old.
    DeleteObject(hFont);                                                // always delete the font when done.
    ReleaseDC(hwnd, hdc);                                               // always release dc after using.
}

/** Measure the string using the classic GDI GetTextExtentPoint32 **/
void MeasureString(){
    HDC hdc = GetDC(hwnd);
    HFONT hFont = CreateFont(                                           // Create the font.
    int fontSize = 12;                                                  // Font Size.
    HDC hdc = GetDC(hwnd);                                              // Get DC of your handle.
    HFONT hFont = CreateFont(
         -MulDiv(fontSize, GetDeviceCaps(hdc, LOGPIXELSX), 72),         // Calculate the actual cHeight.
         0, 0, 0,                                                       // Normal orientation
         FW_NORMAL,                                                     // Normal weight--e.g., bold would be FW_BOLD, or use integer from 0 to 1000.
         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,
         TEXT("Arial Black"));                                          // Font family.
   
    HFONT oldfont = (HFONT)SelectObject(hdc, hFont);                    // Select the new font.
    SIZE size;                                                          // Setup Size to get the width and height.
    GetTextExtentPoint32(hdc, L"Hello World", -1, &size);               // Draw the text Horizontal and Vertical Center.

    int width = size.cx;                                                // get the width of text from boundRect.
    int height = size.cy;                                               // get the height of text from boundRect.    

    SelectObject(hdc, oldfont);                                         // don't forget to select the old.
    DeleteObject(hFont);                                                // always delete the font when done.
    ReleaseDC(hwnd, hdc);                                               // always release dc after using.
}

既然你已经在使用GID+绘制文字,那么推荐使用Graphics:: MeasureString 来计算它的大小,这将避免获得正确 DPI 的麻烦。您必须使文本的大小与您绘制它的方式相同。然后用这个方法,你就可以自动设置静态控件的宽度和高度了。

例如在您的 UpdateLabel();

/** Call this function to redraw the content of static control **/
void ControlLabel::UpdateLabel() {
    if(LabelHandle != NULL) {
        MeasureString(); //Measure string to get the width and height of it and adjust the size of static control depending on the size of the text.
        SetWindowPos(LabelHandle, nullptr, xPosition, yPosition, width, height,  SWP_NOZORDER | SWP_NOOWNERZORDER);
        InvalidateRect(LabelHandle, NULL, FALSE);
        UpdateWindow(LabelHandle);
    }
}