前言
在上一篇文章中:
我们实现了仿微信的布局和简单的聊天列表:
但是,因为网易开源 NimDuiLib 的版本落后于其提供的Demo,同样的写法在开源DuiLib中就会出现问题,比如RichEdit在同时设置width、height为auto时,会断错误。所以需要我们自己进行修复。
至于为何要设置RichEdit的width和height属性为auto?因为文本的宽度和高度都是动态的,根据文本的内容需要自行计算。
网易DuiLib中控件如何计算宽高?
我们从网易DuiLib中自带的"doc\duilib属性列表.xml",查看RichEdit是继承自Box,而Box又是继承自Control,Control是顶级控件。由此查看Control.cpp的源码,发现如下代码:
/**
* @brief 计算控件大小
* @param[in] szAvailable 暂无意义
* @return szAvailable 控件实际大小(如果设置了图片并设置 width 或 height 任意一项为 auto,将根据图片来计算最终大小)
*/
virtual CSize EstimateSize(CSize szAvailable);
所以我们可以确定,高度和宽度应该是各个子控件重写了EstimateSize函数动态计算而成。
CSize Control::EstimateSize(CSize szAvailable) {
CSize imageSize = m_cxyFixed;
if (GetFixedWidth() == DUI_LENGTH_AUTO || GetFixedHeight() == DUI_LENGTH_AUTO) {
if (!m_bReEstimateSize) {
return m_szEstimateSize;
}
Image* image = GetEstimateImage();
if (image) {
auto imageAttribute = image->imageAttribute;
if (imageAttribute.rcSource.left != DUI_NOSET_VALUE && imageAttribute.rcSource.top != DUI_NOSET_VALUE
&& imageAttribute.rcSource.right != DUI_NOSET_VALUE && imageAttribute.rcSource.bottom != DUI_NOSET_VALUE) {
if ((GetFixedWidth() != imageAttribute.rcSource.right - imageAttribute.rcSource.left)) {
SetFixedWidth(imageAttribute.rcSource.right - imageAttribute.rcSource.left);
}
if ((GetFixedHeight() != imageAttribute.rcSource.bottom - imageAttribute.rcSource.top)) {
SetFixedHeight(imageAttribute.rcSource.bottom - imageAttribute.rcSource.top);
}
return m_cxyFixed;
}
GetImage(*image);
if (image->imageCache) {
if (GetFixedWidth() == DUI_LENGTH_AUTO) {
int image_width = image->imageCache->nX;
DpiManager::GetInstance()->ScaleInt(image_width);
imageSize.cx = image_width;
}
if (GetFixedHeight() == DUI_LENGTH_AUTO) {
int image_height = image->imageCache->nY;
DpiManager::GetInstance()->ScaleInt(image_height);
imageSize.cy = image_height;
}
}
}
m_bReEstimateSize = false;
CSize textSize = EstimateText(szAvailable, m_bReEstimateSize);
// 宽度为Auto时,自动计算
if (GetFixedWidth() == DUI_LENGTH_AUTO && imageSize.cx < textSize.cx) {
imageSize.cx = textSize.cx;
}
// 高度为Auto时,自动计算
if (GetFixedHeight() == DUI_LENGTH_AUTO && imageSize.cy < textSize.cy) {
imageSize.cy = textSize.cy;
}
m_szEstimateSize = imageSize;
}
return imageSize;
}
解决RichEdit无法同时设置宽高为Auto的BUG
思路
- 调试 RichEdit::EstimateSize(),打断点。
- 发现 m_pTwh->GetTextServices()->TxGetNaturalSize() 函数会根据文本计算高度,其中iWidth非常关键,如果是0或负数,则无法计算文本高度。
再看这句代码:
LONG iWidth = size.cx;
LONG iHeight = size.cy;
if (size.cx == DUI_LENGTH_AUTO) {
ASSERT(size.cy != DUI_LENGTH_AUTO);
iWidth = 0;
}
else if (size.cy == DUI_LENGTH_AUTO) {
ASSERT(size.cx != DUI_LENGTH_AUTO);
iHeight = 0;
}
本身就没有处理iWidth和iiHeight 同时为DUI_LENGTH_AUTO的情况。
修复
因为 TxGetNaturalSize() 需要知道显示区域的宽度,才能计算出文本的高度。故我们用了一个取巧的办法,设置控件的 MaxWidth熟悉 ,这样当文本需要换行的时候,**TxGetNaturalSize()**函数会返回文本的总高度,然后我们更新一下RichEdit的高度即可。
修复后的关键代码如下:
CSize RichEdit::EstimateSize(CSize szAvailable) {
CSize size(GetFixedWidth(), GetFixedHeight());
if (size.cx == DUI_LENGTH_AUTO || size.cy == DUI_LENGTH_AUTO) {
LONG iWidth = size.cx;
LONG iHeight = size.cy;
if (size.cx == DUI_LENGTH_AUTO && size.cy == DUI_LENGTH_AUTO) {
// fixed_by xmcy0011@sina.com 2021-04-22 设置width=auto && height=auto时,RichEdit无法自动计算宽高的问题
// 此时要设置最大宽度,否则无法计算。
ASSERT(GetMaxWidth() != 9999999);
iWidth = GetMaxWidth();
} else if (size.cx == DUI_LENGTH_AUTO) {
iWidth = 0;
} else if (size.cy == DUI_LENGTH_AUTO) {
iHeight = 0;
}
SIZEL szExtent = { -1, -1 };
m_pTwh->GetTextServices()->TxGetNaturalSize(
DVASPECT_CONTENT,
GetWindow()->GetPaintDC(),
NULL,
NULL,
TXTNS_FITTOCONTENT,
&szExtent,
&iWidth,
&iHeight);
if (size.cx == DUI_LENGTH_AUTO) {
size.cx = iWidth + m_pLayout->GetPadding().left + m_pLayout->GetPadding().right;
}
if (size.cy == DUI_LENGTH_AUTO) {
size.cy = iHeight + m_pLayout->GetPadding().top + m_pLayout->GetPadding().bottom;
}
}
return size;
}
效果:
完整代码在:GitHub