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

问题描述

我正在尝试创建一个 Label 类,以便以后可以重用它。我所做的是创建一个静态控件,然后使用 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",WS_CHILD | WS_VISIBLE | SS_OWNERDRAW,WindowHandle,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,FW_norMAL,FALSE,DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH,result);
    */

    //(font test 2)
    FontFamily  theFontFamily(result);
    Font        font(&theFontFamily,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);  
}

我该如何解决

更新

这是我的类的完整源代码

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,s.c_str(),slength,0);
    wchar_t* buf = new wchar_t[len];
    MultiBytetoWideChar(CP_ACP,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),// normal orientation
          FW_norMAL,// normal weight--e.g.,bold would be FW_BOLD
          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),oldfont);
    DeleteObject(hFont);
    ReleaseDC(LabelHandle,hdc);

    char buffer[100];
    sprintf_s(buffer,"WIDTH: %d | HEIGHT: %d\n",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,FALSE);
        UpdateWindow(LabelHandle);
    }
}

/** This is the callback function of static control. **/
LRESULT CALLBACK ControlLabel::LabelProc(HWND hwnd,DWORD_PTR dwRefData) {
    switch(uMsg) {
        case WM_ERASEBKGND: {
            if(SetBGColor) { //We only want to do this if the SetColor is modified to true,&ps);
            Graphics g(hdc);

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

            FontFamily  theFontFamily(fontFamilyTemp.c_str());
            Font        font(&theFontFamily,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(),&brush); 

            EndPaint(hwnd,HWND WindowHandle) {
    LABEL_ID = Label_ID;
    LabelHandle = CreateWindowEx(0,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 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,bool SetBGColor);

private:
    void UpdateLabel();
    void SetWidthAndHeights();
    static std::wstring StringConverter(const std::string& s);
    static LRESULT CALLBACK LabelProc(HWND hWnd,UINT msg,DWORD_PTR dwRefData);
    static HWND LabelHandle;
    static SolidBrush vFontColor;
    static string text,vFontFamily;
    static bool SetBGColor;
    static int xPosition,B,BckR,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")

然后在我的主窗口上,我是这样使用的。

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 的建议解决方案有效,但我不知道这是否是正确的方法。我仍在阅读给定的文档。

解决方法

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

在您的情况下,您正在使用 GDI+ 绘制文本,但您正在使用经典的 GDI 测量文本的宽度和高度,这会因缩放而产生不同的结果。虽然您仍然可以使用 GetTextExtentPoint32 来测量文本。但是,您需要处理 DPI 以获得与使用 GDI+ 绘制的宽度和高度相同的宽度和高度。 GDI+是对GDI的改进,两者有区别。例如,他们绘制字符串的比例。

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

    import time

    answer = None

    def check():

        #Global Variable Definitions

        global outOfTime
        global answer

        #Variable Fixup

        outOfTime = 2
        timevalue = 0

        #Set Time Dependant On Level

        if difficulty == "EASY":
            timevalue = 300 / level
        elif difficulty == "MEDIUM":
            timevalue = 150 / level
        elif difficulty == "HARD":
            timevalue = 5 / level
        else:
            print("ERROR!")
            time.sleep(2) #Incase Of No Difficulty
            quizStartup()

        print("You Have",round(timevalue),"Seconds")

        time.sleep(timevalue) #Timer
        if answer == None:
            outOfTime = 1

    from threading import Thread 
    t = Thread(target = check).start 

    answer = input("Answer: ")

另一个示例展示了如何使用经典 GDI 绘制字符串并使用 GetTextExtenPoint32 计算宽度和高度。绘图和计算是使用经典的 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));                             // 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,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,&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.
}

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

例如在您的 UpdateLabel();

/** 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,// Normal orientation
         FW_NORMAL,// Normal weight--e.g.,bold would be FW_BOLD,or use integer from 0 to 1000.
         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,200,50};                                   // Rectangle (in logical coordinates) in which the text is to be formatted.
    
    DrawText(hdc,L"Helow World",&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,hFont);                    // Select the new font.
    SIZE size;                                                          // Setup Size to get the width and height.
    GetTextExtentPoint32(hdc,L"Hello World",&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,hdc);                                               // always release dc after using.
}