win32自绘控件:实现静态文本自动换行

最近想着用win32自己绘制一些控件,当我在画静态文本的时候想到了一个自动换行的办法,我觉得值得记录一下

因为是完全自绘,在这里先介绍一些我自己写的函数防止看不懂

函数介绍

/*
功能:这是一个事件触发函数,当窗口需要重绘时会触发该函数

id:不需要管
pen:这是一个painter类,内部实现了各种图形绘制函数,在OnPaint里使用pen绘制的所有东西都会出现在指定窗口上
*/
virtual void OnPaint(DWORD id, painter& pen);
/*
功能:获取字符串的宽度和高度(像素)

str:UnicodeString类型(我自己实现的一个存储字符串的类),要计算的字符串

返回值:成功返回文本尺寸,失败返回{ 0, 0 }
*/
SIZE painter::get_string_size(UnicodeString str);

/*
功能:绘制文本

str:要绘制的文本
beginpos:文本左上角坐标
size:文本框大小
bkg_color:背景色
text_color:文本颜色
transparent_bk:背景是否透明
format:显示类型

返回值:失败返回0,成功返回文本高度(即DrawText返回值)
*/
int painter::drawtext(UnicodeString str, 
    const POINT& beginpos, 
    const POINT& size, 
    COLORREF bkg_color, 
    COLORREF text_color, 
    bool transparent_bk, 
    UINT format);
/*
功能:切割从某一位置到末尾的字符串

begin:起始位置索引

返回值:截取的字符串
*/
UnicodeString UnicodeString::Cut(uint begin);

/*
功能:切割字符串

begin:起始位置索引
len:要切割的长度

返回值:截取的字符串
*/
UnicodeString UnicodeString::Cut(uint begin, uint len);
/*
功能:获取文本长度

返回值:文本长度
*/
uint UnicodeString::Length();

代码实现

我们先拿出代码再来讲原理

void XXX::Text::OnPaint(DWORD id, painter& pen)
{

    ///...

    if (mIsShowText)
    {
        if (mIsAutoLine)
        {
            SIZE textSize = pen.get_string_size(mWindowTitle);
            int averLine= textSize.cx / mWidth + 1, averIndex = mWindowTitle.Length() / averLine;

            int lastIndex = 0, averNum = averIndex, height = 0;

            for (;;)
            {
                if ((lastIndex + averNum) >= (int)mWindowTitle.Length())
                {
                    if(mWindowTitle.Length() != lastIndex)
                        pen.drawtext(mWindowTitle.Cut(lastIndex), { 0, height }, { mWidth, mHeight }, mBkgColor, mTextColor, true, DT_LEFT);
                    break;
                }

                while (pen.get_string_size(mWindowTitle.Cut(lastIndex, averNum)).cx < mWidth)
                    averNum++;
                while (pen.get_string_size(mWindowTitle.Cut(lastIndex, averNum)).cx > mWidth)
                    averNum--;

                pen.drawtext(mWindowTitle.Cut(lastIndex, averNum), { 0, height }, { mWidth, mHeight }, mBkgColor, mTextColor, true, DT_LEFT);
                
                height += textSize.cy + 1;
                lastIndex += averNum;
                averNum = averIndex;
            }
        }
        else
            pen.drawtext(mWindowTitle, { 0, 0 }, { mWidth, mHeight }, mBkgColor, mTextColor, true, DT_LEFT);
    }
        
}

mIsShowText变量是控制静态文本是否显示文字(因为这个静态文本还能设置图片),mIsAutoLine控制文本是否自动换行,两个都是bool类型,我们主要研究第二个if里面的原理

首先先获取文本尺寸,再通过文本宽度除以文本框宽度计算平均需要几行存入averLine中,再通过将文本长度除以averLine获取平均每一行可以容纳多少字符存入averIndex里

lastIndex记录上一个索引,averNum确定一行实际可以容纳多少字符,height记录当前行数,这三个变量可能一开始不清楚是干什么的,这里不用管,继续往下看

首先进入一个死循环(也可以用while),我们先不看第一个判断条件,看向第一个循环

while (pen.get_string_size(mWindowTitle.Cut(lastIndex, averNum)).cx < mWidth)
    averNum++;

这里判断当前文本实际长度是否小于文本框宽度,若小于就将averNum加一,即多读取一个字符继续判断,当大于等于文本框宽度时跳出循环

while (pen.get_string_size(mWindowTitle.Cut(lastIndex, averNum)).cx > mWidth)
    averNum--;

第二个循环与第一个恰恰相反,判断当前文本实际长度是否大于文本框宽度,若大于就将averNum减一,即少读取一个字符继续判断,当小于等于文本框宽度时跳出循环

这时要读取的文本实际长度就恰好等于文本框宽度或比文本框宽度小一点点,没有多一个字符也没有少一个字符,确保每一行都能显示最长的文本,这时就可以绘制文本了

pen.drawtext(mWindowTitle.Cut(lastIndex, averNum), { 0, height }, { mWidth, mHeight }, mBkgColor, mTextColor, true, DT_LEFT);

结束后将高度增加,即换行操作

再将当前读取长度赋值到lastIndex里记录上一个索引,averNum重新赋值为averNum,开始第二次循环

当要读取完字符串或剩下的字符串不足以填满一行时,下面的判断代码就发挥了作用

if ((lastIndex + averNum) >= (int)mWindowTitle.Length())
{
    if(mWindowTitle.Length() != lastIndex)
        pen.drawtext(mWindowTitle.Cut(lastIndex), { 0, height }, { mWidth, mHeight }, mBkgColor, mTextColor, true, DT_LEFT);
    break;
}

当(lastIndex + averNum)大于总文本长度时,说明字符串已读取完或剩下的字符串不足以填满一行,这是再进一步判断是否是文本已经读取完了,若没有读取完则将剩下的文本直接绘制上去然后跳出循环,否则直接跳出循环

以上就是我的思路了,乍一看这个函数时间复杂度起码有O(n^2),但实际上比传统方法要快的多,因为每一次都能定位到要截取的字符串附近,而不用将字符串遍历一遍,节省了大量时间

这些代码虽然不能被直接使用,但思路是通用的,如果有和我一样在自绘控件的朋友可以参考一下

效果如下

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值