目录
1、使用CEditUI编辑框
2、使用CLabelUI或CTextUI的Html文本效果
3、使用CTextUI控件对文字宽度自适应的特性
4、CRichEditUI富文本控件使用注意点
4.1、指定CRichEditUI加在2.0版本的Riched20.dll库
4.2、解决向CRichEditUI中插入文字后显示空白的问题
5、设置窗口透明度的接口CPaintManagerUI::SetTransparent的问题
5.1、窗口透明度设置为255(不透明),之前添加的WS_EX_LAYERED风格被删除了
5.2、 先调用了SetLayeredWindowAttributes接口,导致后面调用UpdateLayeredWindow失败
6、弹出一个dui窗口的模态框时要将自销毁标记置为false
6.1、dui窗口对象的自销毁实现说明
6.2、自销毁特性引发的崩溃问题
7、CMenuWnd菜单窗口中的菜单项相关实现细节
7.1、设置菜单窗口属性以及构建菜单窗口中的菜单项
7.2、操作菜单项的其他接口
8、在格式化CStdString字符串变量时要注意的问题
8.1、将CStdString::Format接口中不支持浮点的wvsprintf接口换成_vstprintf_s
8.2、在格式化CStdString对象时遇到的崩溃问题
9、常用布局CVerticalLayOutUI、CHorizontalLayoutUI和CTileLayoutUI的使用技巧
9.1、常用布局特性说明
9.2、使用布局去构建UI界面的实例
10、为了给字体设置提高文字清晰度的ClearType属性,_WIN32_WINNT宏的值从0x500修改成0x501,导致ToolTip窗口不显示的问题
C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++基础入门与实战进阶(专栏文章已更新到270篇,还在持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.htmlWindows C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_2276111.html 项目中使用开源的duilib界面库已经好几年了,在使用的过程中发现了一些问题和缺陷,对duilib库进行部分优化和改进,并根据业务需求,新增了一些自定义的控件。本文罗列出使用duilib的若干细节和实战技巧,以供大家借鉴或参考。duilib界面库使用细节与实战技巧总结
1、使用CEditUI编辑框
CEditUI编辑框支持设置有效字符串过滤,比如用于输入端口号的编辑框,只能输入数字,则可以将有效字符过滤串设置为_T("0123456789")。再比如输入密码的编辑框,只允许输入个别特殊字符以及数字、大小写字母等,可以将有效字符过滤串设置为_T("_.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")。
不属于这个有效字符过滤串之内的字符,将无法输入,这是CEditUI内部代码控制的。
可以直接在xml中设置allowinputfilter属性,如下所示:
也可以在代码中调用CEditUI的SetAllowInputFilter接口去设置:
m_pEditPwd->SetAllowInputFilter( _T("_.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") );
另外,对于密码编辑框,我们要设置成*号或者圆点这种不可见的模式,防止输入密码时被别人看到。需要做两个设置,一个是将编辑框设置成密码模式,可以在xml中设置属性password="true",也可以调用接口SetPasswordMode去设置。
设置用于遮盖密码的固定字符,可以设置为*号,可以设置为实心圆点●,可以调用SetPasswordChar接口设置(也可以在xml中设置属性)。设置*号的代码如下:
m_pEditPwd->SetPasswordChar( _T('*') );
显示效果如下:
显示效果不是很好,现在很多软件都将掩藏字符设置为实心圆点●了,比如QQ的密码框:
设置实心圆点的代码如下:
m_pEditPwd->SetPasswordChar( _T('●') );
实心圆点的效果如下:
对比看,还是实心圆点●的效果要更好一些的!
这个实心圆点是个特殊字符,从哪里获取呢?可以在搜狗输入法的符号大全窗口中的特殊字符栏中可以找到,如下:
2、使用CLabelUI或CTextUI的Html文本效果
比如要实现类似如下效果的文字块:
文字有不同大小,有不同颜色,使用CLabelUI或CTextUI的showhtml属性就可以实现这样的效果,将控件的showhtml属性设置为true即可,即showhtml="true"。
要实现上述效果,要按文字大小和颜色对文字串分块处理,使用html属性对每一块文字设置不同的大小和颜色,上述截图中的文字效果的实现代码如下:
// 1、姓名(c-设置颜色,f-设置字体)
CString strName;
// ...(获取人员的代码省略)
CString strContactInfo;
strContactInfo = _T("<c #323232><f 4>");
strContactInfo += CopyUtf8ToCStringT( strName );
strContactInfo += _T("</f></c>");
// 2、部门信息(x-设置水平方向上的offset偏移,c-设置颜色,f-设置字体)
CString strDepartmenName;
// ...(获取部门名称的代码省略)
CString strDepartmentInfo;
strDepartmentInfo = _T("| ") + strDepartmenName;
strDepartmentInfo = _T("<x 14><c #969696><f 1>") + strDepartmentInfo + _T("</f></c>");
strContactInfo += strDepartmentInfo;
// 3、邮箱(n-设置换行,c-设置颜色,f-设置字体)
CString strMail;
// ...(获取邮箱的代码省略)
strMail = _T("<n><c #969696><f 3>") + strMail + _T("</f></c>");
strContactInfo += strMail;
m_pContaceInfo->SetText( strContactInfo );
至于html支持哪些属性设置,可以查看绘制html文字的接口CRenderEngine::DrawHtmlText入口处的注释:
void CRenderEngine::DrawHtmlText( HDC hDC, CPaintManagerUI* pManager, RECT& rc, LPCTSTR pstrText
, DWORD dwTextColor, RECT* prcLinks, CStdString* sLinks, int& nLinkRects, UINT uStyle )
{
// 考虑到在xml编辑器中使用<>符号不方便,可以使用{}符号代替
// 支持标签嵌套(如<l><b>text</b></l>),但是交叉嵌套是应该避免的(如<l><b>text</l></b>)
// The string formatter supports a kind of "mini-html" that consists of various short tags:
//
// Bold: <b>text</b>
// Color: <c #xxxxxx>text</c> where x = RGB in hex
// Font: <f x>text</f> where x = font id
// Italic: <i>text</i>
// Image: <i x y z> where x = image name and y = imagelist num and z(optional) = imagelist id
// Link: <a x>text</a> where x(optional) = link content, normal like app:notepad or http:www.xxx.com
// NewLine <n>
// Paragraph: <p x>text</p> where x = extra pixels indent in p
// Raw Text: <r>text</r>
// Selected: <s>text</s>
// Underline: <u>text</u>
// X Indent: <x i> where i = hor indent in pixels
// Y Indent: <y i> where i = ver indent in pixels
ASSERT(::GetObjectType(hDC)==OBJ_DC || ::GetObjectType(hDC)==OBJ_MEMDC);
if( NULL == pstrText || NULL == pManager )
{
return;
}
if( ::IsRectEmpty(&rc) )
{
return;
}
bool bDraw = (uStyle & DT_CALCRECT) == 0;
CStdPtrArray aFontArray(10);
CStdPtrArray aColorArray(10);
CStdPtrArray aPIndentArray(10);
RECT rcClip = { 0 };
::GetClipBox( hDC, &rcClip );
HRGN hOldRgn = ::CreateRectRgnIndirect( &rcClip );
HRGN hRgn = ::CreateRectRgnIndirect( &rc );
if( bDraw )
{
::ExtSelectClipRgn( hDC, hRgn, RGN_AND );
}
TEXTMETRIC* pTm = &pManager->GetDefaultFontInfo()->tm;
HFONT hOldFont = (HFONT) ::SelectObject( hDC, pManager->GetDefaultFontInfo()->hFont );
::SetBkMode( hDC, TRANSPARENT );
::SetTextColor( hDC, RGB(GetBValue(dwTextColor), GetGValue(dwTextColor), GetRValue(dwTextColor)) );
DWORD dwBkColor = pManager->GetDefaultSelectedBkColor();
::SetBkColor( hDC, RGB(GetBValue(dwBkColor), GetGValue(dwBkColor), GetRValue(dwBkColor)) );
// If the drawstyle include a alignment, we'll need to first determine the text-size so
// we can draw it at the correct position...
if( (uStyle & DT_SINGLELINE) != 0 && (uStyle & DT_VCENTER) != 0 && (uStyle & DT_CALCRECT) == 0 )
{
RECT rcText = { 0, 0, 9999, 100 };
int nLinks = 0;
DrawHtmlText(hDC, pManager, rcText, pstrText, dwTextColor, NULL, NULL, nLinks, uStyle | DT_CALCRECT);
rc.top = rc.top + ((rc.bottom - rc.top) / 2) - ((rcText.bottom - rcText.top) / 2);
rc.bottom = rc.top + (rcText.bottom - rcText.top);
}
if( (uStyle & DT_SINGLELINE) != 0 && (uStyle & DT_CENTER) != 0 && (uStyle & DT_CALCRECT) == 0 )
{
RECT rcText = { 0, 0, 9999, 100 };
int nLinks = 0;
DrawHtmlText(hDC, pManager, rcText, pstrText, dwTextColor, NULL, NULL, nLinks, uStyle | DT_CALCRECT);
rc.left = rc.left + ((rc.right - rc.left) / 2) - ((rcText.right - rcText.left) / 2);
rc.right = rc.left + (rcText.right - rcText.left);
}
if( (uStyle & DT_SINGLELINE) != 0 && (uStyle & DT_RIGHT) != 0 && (uStyle & DT_CALCRECT) == 0 )
{
RECT rcText = { 0, 0, 9999, 100 };
int nLinks = 0;
DrawHtmlText(hDC, pManager, rcText, pstrText, dwTextColor, NULL, NULL, nLinks, uStyle | DT_CALCRECT);
rc.left = rc.right - (rcText.right - rcText.left);
}
bool bHoverLink = false;
CStdString sHoverLink;
POINT ptMouse = pManager->GetMousePos();
for( int i = 0; !bHoverLink && i < nLinkRects; i++ )
{
if( ::PtInRect(prcLinks + i, ptMouse) )
{
sHoverLink = *(CStdString*)(sLinks + i);
bHoverLink = true;
}
}
POINT pt = { rc.left, rc.top };
int iLinkIndex = 0;
int cyLine = pTm->tmHeight + pTm->tmExternalLeading + (int)aPIndentArray.GetAt(aPIndentArray.GetSize() - 1);
int cyMinHeight = 0;
int cxMaxWidth = 0;
POINT ptLinkStart = { 0 };
bool bLineEnd = false; // 一行结束
bool bInRaw = false; // 原始文本,标记了之后这段文字不被解析,原样输出
bool bInLink = false;
bool bInSelected = false;
int iLineLinkIndex = 0;
// 排版习惯是图文底部对齐,所以每行绘制都要分两步,先计算高度,再绘制
CStdPtrArray aLineFontArray; // 所以,第二次解析的时候要把aFontArray保存起来,行解析结束在还原到回去。这三个结构体起着备份作用。
CStdPtrArray aLineColorArray;
CStdPtrArray aLinePIndentArray;
LPCTSTR pstrLineBegin = pstrText;
bool bLineInRaw = false;
bool bLineInLink = false;
bool bLineInSelected = false;
int cyLineHeight = 0;
bool bLineDraw = false; // 行的第二阶段:绘制
while( *pstrText != _T('\0') )
{
if( pt.x >= rc.right || *pstrText == _T('\n') || bLineEnd )
{
if( *pstrText == _T('\n') ) pstrText++;
if( bLineEnd ) bLineEnd = false;
if( !bLineDraw )
{
if( bInLink && iLinkIndex < nLinkRects )
{
::SetRect(&prcLinks[iLinkIndex++], ptLinkStart.x, ptLinkStart.y, MIN(pt.x, rc.right), pt.y + cyLine);
CStdString *pStr1 = (CStdString*)(sLinks + iLinkIndex - 1);
CStdString *pStr2 = (CStdString*)(sLinks + iLinkIndex);
*pStr2 = *pStr1;
}
for( int i = iLineLinkIndex; i < iLinkIndex; i++ )
{
prcLinks[i].bottom = pt.y + cyLine;
}
if( bDraw )
{
bInLink = bLineInLink;
iLinkIndex = iLineLinkIndex;
}
}
else
{
if( bInLink && iLinkIndex < nLinkRects ) iLinkIndex++;
bLineInLink = bInLink;
iLineLinkIndex = iLinkIndex;
}
if( (uStyle & DT_SINGLELINE) != 0 && (!bDraw || bLineDraw) )
{
break;
}
if( bDraw )
{
bLineDraw = !bLineDraw; // !
}
pt.x = rc.left;
if( !bLineDraw )
{
pt.y += cyLine;
}
if( pt.y > rc.bottom && bDraw )
{
break;
}
ptLinkStart = pt;
cyLine = pTm->tmHeight + pTm->tmExternalLeading + (int)aPIndentArray.GetAt(aPIndentArray.GetSize() - 1);
if( pt.x >= rc.right )
{
break;
}
}
else if( !bInRaw && ( *pstrText == _T('<') || *pstrText == _T('{') )
&& ( pstrText[1] >= _T('a') && pstrText[1] <= _T('z') )
&& ( pstrText[2] == _T(' ') || pstrText[2] == _T('>') || pstrText[2] == _T('}') ) )
{
pstrText++;
LPCTSTR pstrNextStart = NULL;
switch( *pstrText )
{
case _T('a'): // Link
{
pstrText++;
while( *pstrText > _T('\0') && *pstrText <= _T(' ') ) pstrText = ::CharNext(pstrText);
if( iLinkIndex < nLinkRects && !bLineDraw )
{
CStdString *pStr = (CStdString*)(sLinks + iLinkIndex);
pStr->Empty();
while( *pstrText != _T('\0') && *pstrText != _T('>') && *pstrText != _T('}') )
{
LPCTSTR pstrTemp = ::CharNext(pstrText);
while( pstrText < pstrTemp)
{
*pStr += *pstrText++;
}
}
}
DWORD clrColor = pManager->GetDefaultLinkFontColor();
if( bHoverLink && iLinkIndex < nLinkRects )
{
CStdString *pStr = (CStdString*)(sLinks + iLinkIndex);
if( sHoverLink == *pStr )
{
clrColor = pManager->GetDefaultLinkHoverFontColor();
}
}
aColorArray.Add( (LPVOID)clrColor );
::SetTextColor( hDC, RGB(GetBValue(clrColor), GetGValue(clrColor), GetRValue(clrColor)) );
TFontInfo* pFontInfo = pManager->GetDefaultFontInfo();
if( aFontArray.GetSize() > 0 )
{
pFontInfo = (TFontInfo*)aFontArray.GetAt(aFontArray.GetSize() - 1);
}
if( false == pFontInfo->bUnderline )
{
HFONT hFont = pManager->GetFont( pFontInfo->sFontName, pFontInfo->iSize, pFontInfo->bBold
, true, pFontInfo->bItalic);
if( NULL == hFont )
{
hFont = pManager->AddFont( pFontInfo->sFontName, pFontInfo->iSize, pFontInfo->bBold, true, pFontInfo->bItalic );
}
pFontInfo = pManager->GetFontInfo( hFont );
aFontArray.Add( pFontInfo );
pTm = &pFontInfo->tm;
::SelectObject( hDC, pFontInfo->hFont );
cyLine = MAX( cyLine, pTm->tmHeight + pTm->tmExternalLeading + (int)aPIndentArray.GetAt(aPIndentArray.GetSize() - 1));
}
ptLinkStart = pt;
bInLink = true;
}
break;
case _T('b'): // Bold
{
pstrText++;
TFontInfo* pFontInfo = pManager->GetDefaultFontInfo();
if( aFontArray.GetSize() > 0 ) pFontInfo = (TFontInfo*)aFontArray.GetAt(aFontArray.GetSize() - 1);
if( false == pFontInfo->bBold )
{
HFONT hFont = pManager->GetFont( pFontInfo->sFontName, pFontInfo->iSize
, true, pFontInfo->bUnderline, pFontInfo->bItalic );
if( NULL == hFont )
{
hFont = pManager->AddFont( pFontInfo->sFontName, pFontInfo->iSize
, true, pFontInfo->bUnderline, pFontInfo->bItalic );
}
pFontInfo = pManager->GetFontInfo( hFont );
aFontArray.Add( pFontInfo );
pTm = &pFontInfo->tm;
::SelectObject( hDC, pFontInfo->hFont );
cyLine = MAX( cyLine, pTm->tmHeight + pTm->tmExternalLeading + (int)aPIndentArray.GetAt(aPIndentArray.GetSize() - 1));
}
}
break;
case _T('c'): // Color
{
pstrText++;
while( *pstrText > _T('\0') && *pstrText <= _T(' ') )
{
pstrText = ::CharNext(pstrText);
}
if( *pstrText == _T('#'))
{
pstrText++;
}
DWORD clrColor = _tcstol( pstrText, const_cast<LPTSTR*>(&pstrText), 16 );
aColorArray.Add( (LPVOID)clrColor );
::SetTextColor( hDC, RGB(GetBValue(clrColor), GetGValue(clrColor), GetRValue(clrColor)) );
}
break;
case _T('f'): // Font
{
pstrText++;
while( *pstrText > _T('\0') && *pstrText <= _T(' ') )
{
pstrText = ::CharNext(pstrText);
}
LPCTSTR pstrTemp = pstrText;
int iFont = (int) _tcstol( pstrText, const_cast<LPTSTR*>(&pstrText), 10 );
if( pstrTemp != pstrText )
{
TFontInfo* pFontInfo = pManager->GetFontInfo( iFont );
aFontArray.Add( pFontInfo );
pTm = &pFontInfo->tm;
::SelectObject( hDC, pFontInfo->hFont );
}
else
{
CStdString sFontName;
int iFontSize = 10;
CStdString sFontAttr;
bool bBold = false;
bool bUnderline = false;
bool bItalic = false;
while( *pstrText != _T('\0') && *pstrText != _T('>') && *pstrText != _T('}') && *pstrText != _T(' ') )
{
pstrTemp = ::CharNext(pstrText);
while( pstrText < pstrTemp)
{
sFontName += *pstrText++;
}
}
while( *pstrText > _T('\0') && *pstrText <= _T(' ') )
{
pstrText = ::CharNext(pstrText);
}
if( isdigit(*pstrText) )
{
iFontSize = (int) _tcstol( pstrText, const_cast<LPTSTR*>(&pstrText), 10 );
}
while( *pstrText > _T('\0') && *pstrText <= _T(' ') )
{
pstrText = ::CharNext(pstrText);
}
while( *pstrText != _T('\0') && *pstrText != _T('>') && *pstrText != _T('}') )
{
pstrTemp = ::CharNext( pstrText );
while( pstrText < pstrTemp)
{
sFontAttr += *pstrText++;
}
}
sFontAttr.MakeLower();
if( sFontAttr.Find(_T("bold")) >= 0 ) bBold = true;
if( sFontAttr.Find(_T("underline")) >= 0 ) bUnderline = true;
if( sFontAttr.Find(_T("italic")) >= 0 ) bItalic = true;
HFONT hFont = pManager->GetFont( sFontName, iFontSize, bBold, bUnderline, bItalic );
if( NULL == hFont )
{
hFont = pManager->AddFont( sFontName, iFontSize, bBold, bUnderline, bItalic );
}
TFontInfo* pFontInfo = pManager->GetFontInfo( hFont );
aFontArray.Add( pFontInfo );
pTm = &pFontInfo->tm;
::SelectObject( hDC, pFontInfo->hFont );
}
cyLine = MAX(cyLine, pTm->tmHeight + pTm->tmExternalLeading + (int)aPIndentArray.GetAt(aPIndentArray.GetSize() - 1));
}
break;
case _T('i'): // Italic or Image
{
pstrNextStart = pstrText - 1;
pstrText++;
CStdString sImageString = pstrText;
int iWidth = 0;
int iHeight = 0;
while( *pstrText > _T('\0') && *pstrText <= _T(' ') )
{
pstrText = ::CharNext(pstrText);
}
const TImageInfo* pImageInfo = NULL;
CStdString sName;
while( *pstrText != _T('\0') && *pstrText != _T('>') && *pstrText != _T('}') && *pstrText != _T(' ') )
{
LPCTSTR pstrTemp = ::CharNext( pstrText );
while( pstrText < pstrTemp)
{
sName += *pstrText++;
}
}
if( sName.IsEmpty() ) // Italic
{
pstrNextStart = NULL;
TFontInfo* pFontInfo = pManager->GetDefaultFontInfo();
if( aFontArray.GetSize() > 0 )
{
pFontInfo = (TFontInfo*)aFontArray.GetAt(aFontArray.GetSize() - 1);
}
if( pFontInfo->bItalic == false )
{
HFONT hFont = pManager->GetFont( pFontInfo->sFontName, pFontInfo->iSize, pFontInfo->bBold
, pFontInfo->bUnderline, true );
if( NULL == hFont )
{
hFont = pManager->AddFont( pFontInfo->sFontName, pFontInfo->iSize
, pFontInfo->bBold, pFontInfo->bUnderline, true );
}
pFontInfo = pManager->GetFontInfo( hFont );
aFontArray.Add( pFontInfo );
pTm = &pFontInfo->tm;
::SelectObject( hDC, pFontInfo->hFont );
cyLine = MAX(cyLine, pTm->tmHeight + pTm->tmExternalLeading + (int)aPIndentArray.GetAt(aPIndentArray.GetSize() - 1));
}
}
else
{
while( *pstrText > _T('\0') && *pstrText <= _T(' ') )
{
pstrText = ::CharNext(pstrText);
}
int iImageListNum = (int) _tcstol(pstrText, const_cast<LPTSTR*>(&pstrText), 10);
if( iImageListNum <= 0 ) iImageListNum = 1;
while( *pstrText > _T('\0') && *pstrText <= _T(' ') ) pstrText = ::CharNext(pstrText);
int iImageListIndex = (int) _tcstol(pstrText, const_cast<LPTSTR*>(&pstrText), 10);
if( iImageListIndex < 0 || iImageListIndex >= iImageListNum ) iImageListIndex = 0;
if( _tcsstr(sImageString.GetData(), _T("file=\'")) != NULL || _tcsstr(sImageString.GetData(), _T("res=\'")) != NULL )
{
CStdString sImageResType;
CStdString sImageName;
LPCTSTR pStrImage = sImageString.GetData();
CStdString sItem;
CStdString sValue;
while( *pStrImage != _T('\0') )
{
sItem.Empty();
sValue.Empty();
while( *pStrImage > _T('\0') && *pStrImage <= _T(' ') ) pStrImage = ::CharNext(pStrImage);
while( *pStrImage != _T('\0') && *pStrImage != _T('=') && *pStrImage > _T(' ') )
{
LPTSTR pstrTemp = ::CharNext(pStrImage);
while( pStrImage < pstrTemp)
{
sItem += *pStrImage++;
}
}
while( *pStrImage > _T('\0') && *pStrImage <= _T(' ') ) pStrImage = ::CharNext(pStrImage);
if( *pStrImage++ != _T('=') ) break;
while( *pStrImage > _T('\0') && *pStrImage <= _T(' ') ) pStrImage = ::CharNext(pStrImage);
if( *pStrImage++ != _T('\'') ) break;
while( *pStrImage != _T('\0') && *pStrImage != _T('\'') )
{
LPTSTR pstrTemp = ::CharNext(pStrImage);
while( pStrImage < pstrTemp)
{
sValue += *pStrImage++;
}
}
if( *pStrImage++ != _T('\'') ) break;
if( !sValue.IsEmpty() )
{
if( sItem == _T("file") || sItem == _T("res") )
{
sImageName = sValue;
}
else if( sItem == _T("restype") )
{
sImageResType = sValue;
}
}
if( *pStrImage++ != _T(' ') ) break;
}
pImageInfo = pManager->GetImageEx((LPCTSTR)sImageName, sImageResType);
}
else
{
pImageInfo = pManager->GetImageEx((LPCTSTR)sName);
}
if( pImageInfo )
{
iWidth = pImageInfo->nX;
iHeight = pImageInfo->nY;
if( iImageListNum > 1 ) iWidth /= iImageListNum;
if( pt.x + iWidth > rc.right && pt.x > rc.left && (uStyle & DT_SINGLELINE) == 0 )
{
bLineEnd = true;
}
else
{
pstrNextStart = NULL;
if( bDraw && bLineDraw )
{
CDirectRect rcImage(pt.x, pt.y + cyLineHeight - iHeight, pt.x + iWidth, pt.y + cyLineHeight);
if( iHeight < cyLineHeight )
{
rcImage.bottom -= (cyLineHeight - iHeight) / 2;
rcImage.top = rcImage.bottom - iHeight;
}
CDirectRect rcBmpPart(0, 0, iWidth, iHeight);
rcBmpPart.left = iWidth * iImageListIndex;
rcBmpPart.right = iWidth * (iImageListIndex + 1);
CDirectRect rcCorner(0, 0, 0, 0);
DrawImage(hDC, pImageInfo->hBitmap, rcImage, rcImage, rcBmpPart, rcCorner, \
pImageInfo->alphaChannel, 255);
}
cyLine = MAX(iHeight, cyLine);
pt.x += iWidth;
cyMinHeight = pt.y + iHeight;
cxMaxWidth = MAX(cxMaxWidth, pt.x);
}
}
else pstrNextStart = NULL;
}
}
break;
case _T('n'): // Newline
{
pstrText++;
if( (uStyle & DT_SINGLELINE) != 0 ) break;
bLineEnd = true;
}
break;
case _T('p'): // Paragraph
{
pstrText++;
if( pt.x > rc.left ) bLineEnd = true;
while( *pstrText > _T('\0') && *pstrText <= _T(' ') )
{
pstrText = ::CharNext(pstrText);
}
int cyLineExtra = (int)_tcstol(pstrText, const_cast<LPTSTR*>(&pstrText), 10);
aPIndentArray.Add((LPVOID)cyLineExtra);
cyLine = MAX(cyLine, pTm->tmHeight + pTm->tmExternalLeading + cyLineExtra);
}
break;
case _T('r'): // Raw Text
{
pstrText++;
bInRaw = true;
}
break;
case _T('s'): // Selected text background color
{
pstrText++;
bInSelected = !bInSelected;
if( bDraw && bLineDraw )
{
if( bInSelected )
{
::SetBkMode( hDC, OPAQUE );
}
else
{
::SetBkMode( hDC, TRANSPARENT );
}
}
}
break;
case _T('u'): // Underline text
{
pstrText++;
TFontInfo* pFontInfo = pManager->GetDefaultFontInfo();
if( aFontArray.GetSize() > 0 )
{
pFontInfo = (TFontInfo*)aFontArray.GetAt(aFontArray.GetSize() - 1);
}
if( false == pFontInfo->bUnderline )
{
HFONT hFont = pManager->GetFont( pFontInfo->sFontName, pFontInfo->iSize
, pFontInfo->bBold, true, pFontInfo->bItalic );
if( NULL == hFont )
{
hFont = pManager->AddFont( pFontInfo->sFontName, pFontInfo->iSize
, pFontInfo->bBold, true, pFontInfo->bItalic );
}
pFontInfo = pManager->GetFontInfo( hFont );
aFontArray.Add( pFontInfo );
pTm = &pFontInfo->tm;
::SelectObject( hDC, pFontInfo->hFont );
cyLine = MAX( cyLine, pTm->tmHeight + pTm->tmExternalLeading + (int)aPIndentArray.GetAt(aPIndentArray.GetSize() - 1));
}
}
break;
case _T('x'): // X Indent
{
pstrText++;
while( *pstrText > _T('\0') && *pstrText <= _T(' ') )
{
pstrText = ::CharNext(pstrText);
}
int iWidth = (int) _tcstol( pstrText, const_cast<LPTSTR*>(&pstrText), 10 );
pt.x += iWidth;
cxMaxWidth = MAX(cxMaxWidth, pt.x);
}
break;
case _T('y'): // Y Indent
{
pstrText++;
while( *pstrText > _T('\0') && *pstrText <= _T(' ') )
{
pstrText = ::CharNext(pstrText);
}
cyLine = (int) _tcstol(pstrText, const_cast<LPTSTR*>(&pstrText), 10);
}
break;
}
if( pstrNextStart != NULL )
{
pstrText = pstrNextStart;
}
else
{
while( *pstrText != _T('\0') && *pstrText != _T('>') && *pstrText != _T('}') )
{
pstrText = ::CharNext(pstrText);
}
pstrText = ::CharNext(pstrText);
}
}
else if( !bInRaw && ( *pstrText == _T('<') || *pstrText == _T('{') ) && pstrText[1] == _T('/') )
{
pstrText++;
pstrText++;
switch( *pstrText )
{
case _T('c'):
{
pstrText++;
aColorArray.Remove(aColorArray.GetSize() - 1);
DWORD clrColor = dwTextColor;
if( aColorArray.GetSize() > 0 )
{
clrColor = (int)aColorArray.GetAt(aColorArray.GetSize() - 1);
}
::SetTextColor( hDC, RGB(GetBValue(clrColor), GetGValue(clrColor), GetRValue(clrColor)) );
}
break;
case _T('p'):
{
pstrText++;
if( pt.x > rc.left )
{
bLineEnd = true;
}
aPIndentArray.Remove(aPIndentArray.GetSize() - 1);
cyLine = MAX(cyLine, pTm->tmHeight + pTm->tmExternalLeading + (int)aPIndentArray.GetAt(aPIndentArray.GetSize() - 1));
break;
}
case _T('s'):
{
pstrText++;
bInSelected = !bInSelected;
if( bDraw && bLineDraw )
{
if( bInSelected )
{
::SetBkMode( hDC, OPAQUE );
}
else
{
::SetBkMode( hDC, TRANSPARENT );
}
}
}
break;
case _T('a'):
{
if( iLinkIndex < nLinkRects )
{
if( !bLineDraw )
{
::SetRect( &prcLinks[iLinkIndex], ptLinkStart.x, ptLinkStart.y
, MIN(pt.x, rc.right), pt.y + pTm->tmHeight + pTm->tmExternalLeading );
}
iLinkIndex++;
}
aColorArray.Remove( aColorArray.GetSize() - 1 );
DWORD clrColor = dwTextColor;
if( aColorArray.GetSize() > 0 )
{
clrColor = (int)aColorArray.GetAt(aColorArray.GetSize() - 1);
}
::SetTextColor( hDC, RGB(GetBValue(clrColor), GetGValue(clrColor), GetRValue(clrColor)) );
bInLink = false;
}
case _T('b'):
case _T('f'):
case _T('i'):
case _T('u'):
{
pstrText++;
aFontArray.Remove(aFontArray.GetSize() - 1);
TFontInfo* pFontInfo = (TFontInfo*)aFontArray.GetAt(aFontArray.GetSize() - 1);
if( NULL == pFontInfo )
{
pFontInfo = pManager->GetDefaultFontInfo();
}
if( pTm->tmItalic && false == pFontInfo->bItalic )
{
ABC abc;
::GetCharABCWidths(hDC, _T(' '), _T(' '), &abc);
pt.x += abc.abcC / 2; // 简单修正一下斜体混排的问题, 正确做法应该是http://support.microsoft.com/kb/244798/en-us
}
pTm = &pFontInfo->tm;
::SelectObject(hDC, pFontInfo->hFont);
cyLine = MAX(cyLine, pTm->tmHeight + pTm->tmExternalLeading + (int)aPIndentArray.GetAt(aPIndentArray.GetSize() - 1));
}
break;
}
while( *pstrText != _T('\0') && *pstrText != _T('>') && *pstrText != _T('}') )
{
pstrText = ::CharNext( pstrText );
}
pstrText = ::CharNext( pstrText );
}
else if( !bInRaw && *pstrText == _T('<') && pstrText[2] == _T('>') && (pstrText[1] == _T('{') || pstrText[1] == _T('}')) )
{
SIZE szSpace = { 0 };
::GetTextExtentPoint32( hDC, &pstrText[1], 1, &szSpace );
if( bDraw && bLineDraw )
{
::TextOut( hDC, pt.x, pt.y + cyLineHeight - pTm->tmHeight - pTm->tmExternalLeading, &pstrText[1], 1 );
}
pt.x += szSpace.cx;
cxMaxWidth = MAX(cxMaxWidth, pt.x);
pstrText++;pstrText++;pstrText++;
}
else if( !bInRaw && *pstrText == _T('{') && pstrText[2] == _T('}') && (pstrText[1] == _T('<') || pstrText[1] == _T('>')) )
{
SIZE szSpace = { 0 };
::GetTextExtentPoint32(hDC, &pstrText[1], 1, &szSpace);
if( bDraw && bLineDraw )
{
::TextOut( hDC, pt.x, pt.y + cyLineHeight - pTm->tmHeight - pTm->tmExternalLeading, &pstrText[1], 1 );
}
pt.x += szSpace.cx;
cxMaxWidth = MAX(cxMaxWidth, pt.x);
pstrText++;pstrText++;pstrText++;
}
else if( !bInRaw && *pstrText == _T(' ') )
{
SIZE szSpace = { 0 };
::GetTextExtentPoint32(hDC, _T(" "), 1, &szSpace);
// Still need to paint the space because the font might have
// underline formatting.
if( bDraw && bLineDraw )
{
::TextOut( hDC, pt.x, pt.y + cyLineHeight - pTm->tmHeight - pTm->tmExternalLeading, _T(" "), 1 );
}
pt.x += szSpace.cx;
cxMaxWidth = MAX(cxMaxWidth, pt.x);
pstrText++;
}
else
{
POINT ptPos = pt;
int cchChars = 0;
int cchSize = 0;
int cchLastGoodWord = 0;
int cchLastGoodSize = 0;
LPCTSTR p = pstrText;
LPCTSTR pstrNext;
SIZE szText = { 0 };
if( !bInRaw && *p == _T('<') || *p == _T('{') ) p++, cchChars++, cchSize++;
while( *p != _T('\0') && *p != _T('\n') )
{
// This part makes sure that we're word-wrapping if needed or providing support
// for DT_END_ELLIPSIS. Unfortunately the GetTextExtentPoint32() call is pretty
// slow when repeated so often.
// TODO: Rewrite and use GetTextExtentExPoint() instead!
if( bInRaw )
{
if( ( *p == _T('<') || *p == _T('{') ) && p[1] == _T('/')
&& p[2] == _T('r') && ( p[3] == _T('>') || p[3] == _T('}') ) )
{
p += 4;
bInRaw = false;
break;
}
}
else
{
if( *p == _T('<') || *p == _T('{') ) break;
}
pstrNext = ::CharNext(p);
cchChars++;
cchSize += (int)(pstrNext - p);
szText.cx = cchChars * pTm->tmMaxCharWidth;
if( pt.x + szText.cx >= rc.right )
{
::GetTextExtentPoint32(hDC, pstrText, cchSize, &szText);
}
if( pt.x + szText.cx > rc.right )
{
if( pt.x + szText.cx > rc.right && pt.x != rc.left)
{
cchChars--;
cchSize -= (int)(pstrNext - p);
}
if( (uStyle & DT_WORDBREAK) != 0 && cchLastGoodWord > 0 )
{
cchChars = cchLastGoodWord;
cchSize = cchLastGoodSize;
}
if( (uStyle & DT_END_ELLIPSIS) != 0 && cchChars > 0 )
{
cchChars -= 1;
LPCTSTR pstrPrev = ::CharPrev(pstrText, p);
if( cchChars > 0 )
{
cchChars -= 1;
pstrPrev = ::CharPrev(pstrText, pstrPrev);
cchSize -= (int)(p - pstrPrev);
}
else
{
cchSize -= (int)(p - pstrPrev);
}
pt.x = rc.right;
}
bLineEnd = true;
cxMaxWidth = MAX(cxMaxWidth, pt.x);
break;
}
if (!( ( p[0] >= _T('a') && p[0] <= _T('z') ) || ( p[0] >= _T('A') && p[0] <= _T('Z') ) ))
{
cchLastGoodWord = cchChars;
cchLastGoodSize = cchSize;
}
if( *p == _T(' ') )
{
cchLastGoodWord = cchChars;
cchLastGoodSize = cchSize;
}
p = ::CharNext( p );
}
::GetTextExtentPoint32( hDC, pstrText, cchSize, &szText );
if( bDraw && bLineDraw )
{
::TextOut( hDC, ptPos.x, ptPos.y + cyLineHeight - pTm->tmHeight - pTm->tmExternalLeading, pstrText, cchSize );
if( pt.x >= rc.right && (uStyle & DT_END_ELLIPSIS) != 0 )
{
//英文或者日文环境下字体比预设的小两号,所以控件原先的位置将不能正确反应文字的绘制区域 SDM-00049935 by luanchao
if ( _tcsicmp( GetLanPtr()->GetCurrentLan(), LANGTYPE_JAP ) == 0 || _tcsicmp( GetLanPtr()->GetCurrentLan(), LANGTYPE_ENG ) == 0 )
{
::TextOut(hDC, ptPos.x + szText.cx , ptPos.y + cyLineHeight - pTm->tmHeight - pTm->tmExternalLeading, _T("..."), 3);
}
else
{
::TextOut(hDC, ptPos.x + szText.cx , ptPos.y , _T("..."), 3);
}
}
}
pt.x += szText.cx;
cxMaxWidth = MAX(cxMaxWidth, pt.x);
pstrText += cchSize;
}
if( pt.x >= rc.right || *pstrText == _T('\n') || *pstrText == _T('\0') )
{
bLineEnd = true;
}
if( bDraw && bLineEnd )
{
if( !bLineDraw )
{
aFontArray.Resize( aLineFontArray.GetSize() );
::CopyMemory( aFontArray.GetData(), aLineFontArray.GetData(), aLineFontArray.GetSize() * sizeof(LPVOID) );
aColorArray.Resize( aLineColorArray.GetSize() );
::CopyMemory( aColorArray.GetData(), aLineColorArray.GetData(), aLineColorArray.GetSize() * sizeof(LPVOID) );
aPIndentArray.Resize( aLinePIndentArray.GetSize() );
::CopyMemory( aPIndentArray.GetData(), aLinePIndentArray.GetData(), aLinePIndentArray.GetSize() * sizeof(LPVOID) );
cyLineHeight = cyLine;
pstrText = pstrLineBegin;
bInRaw = bLineInRaw;
bInSelected = bLineInSelected;
DWORD clrColor = dwTextColor;
if( aColorArray.GetSize() > 0 )
{
clrColor = (int)aColorArray.GetAt(aColorArray.GetSize() - 1);
}
::SetTextColor( hDC, RGB(GetBValue(clrColor), GetGValue(clrColor), GetRValue(clrColor)) );
TFontInfo* pFontInfo = (TFontInfo*)aFontArray.GetAt(aFontArray.GetSize() - 1);
if( NULL == pFontInfo )
{
pFontInfo = pManager->GetDefaultFontInfo();
}
pTm = &pFontInfo->tm;
::SelectObject( hDC, pFontInfo->hFont );
if( bInSelected )
{
::SetBkMode( hDC, OPAQUE );
}
}
else
{
aLineFontArray.Resize( aFontArray.GetSize() );
::CopyMemory( aLineFontArray.GetData(), aFontArray.GetData(), aFontArray.GetSize() * sizeof(LPVOID) );
aLineColorArray.Resize( aColorArray.GetSize() );
::CopyMemory( aLineColorArray.GetData(), aColorArray.GetData(), aColorArray.GetSize() * sizeof(LPVOID) );
aLinePIndentArray.Resize( aPIndentArray.GetSize() );
::CopyMemory( aLinePIndentArray.GetData(), aPIndentArray.GetData(), aPIndentArray.GetSize() * sizeof(LPVOID) );
pstrLineBegin = pstrText;
bLineInSelected = bInSelected;
bLineInRaw = bInRaw;
}
}
ASSERT( iLinkIndex<=nLinkRects );
}
nLinkRects = iLinkIndex;
// Return size of text when requested
if( (uStyle & DT_CALCRECT) != 0 )
{
rc.bottom = MAX( cyMinHeight, pt.y + cyLine );
rc.right = MIN( rc.right, cxMaxWidth );
}
if( bDraw ) ::SelectClipRgn( hDC, hOldRgn );
::DeleteObject( hOldRgn );
::DeleteObject( hRgn );
::SelectObject( hDC, hOldFont );
}
3、使用CTextUI控件对文字宽度自适应的特性
CTextUI控件比较特别,会根据当前给其设置的文字宽度,自动调整自己控件的宽度,刚好能将文字显示全,也不占用多余的宽度。
这种自适应的特性可以巧妙地用在一些场合。比如IM群组的聊天框,在聊天框的右侧会显示当前的讨论组成员列表,如果成员是管理员,我们需要在成员名称后面添加一个小图标,以标识该成员是管理员,如下:
一般的做法是,利用CContainer的自动布局特性,我们将不变宽度的控件设置固定宽度值,位于右边的管理员图标宽度也设置为固定值,这样中间的成员名称是文本,可长可短,不设置成固定值,使用CLabelUI控件即可。这样,中间的名称宽度就是可变的,但是这样做有个问题,管理员图标会固定在每个成员item的最右边,不能跟随名称紧贴名称后面,显得不太美观。后来考虑到CTextUI的对文字长度能自适应,就改用CTextUI,能很好的实现管理员图标能紧跟在成员名称后面。
之前在项目中使用CTextUI空间控件时遇到过给该控件设置新的文字后控件显示的文字并没有更新,显示的还是老的文字,这个问题的排查与解决,专门写了一篇文章,感兴趣可以去看一下:
duilib中CTextUI控件更新文字后不显示最新文字(不刷新显示)的问题排查https://blog.csdn.net/chenlycly/article/details/140444416
4、CRichEditUI富文本控件使用注意点
4.1、指定CRichEditUI加在2.0版本的Riched20.dll库
相关的代码在CTxtWinHost::Init接口中,原先是直接调用CreateTextServices接口,现在改成去动态加载2.0版本的Riched20.dll库,调用该库中的CreateTextServices接口去创建Text服务,这样指定使用2.0版本的Riched20.dll库,相关代码如下:
BOOL CTxtWinHost::Init(CRichEditUI *re, const CREATESTRUCT *pcs)
{
IUnknown *pUnk;
HRESULT hr;
m_re = re;
// Initialize Reference count
cRefs = 1;
// Create and cache CHARFORMAT for this control
if(FAILED(InitDefaultCharFormat(re, &cf, NULL)))
goto err;
// Create and cache PARAFORMAT for this control
if(FAILED(InitDefaultParaFormat(re, &pf)))
goto err;
// edit controls created without a window are multiline by default
// so that paragraph formats can be
dwStyle = ES_MULTILINE;
// edit controls are rich by default
fRich = re->IsRich();
cchTextMost = re->GetLimitText();
if (pcs )
{
dwStyle = pcs->style;
if ( !(dwStyle & (ES_AUTOHSCROLL | WS_HSCROLL)) )
{
fWordWrap = TRUE;
}
}
if( !(dwStyle & ES_LEFT) )
{
if(dwStyle & ES_CENTER)
pf.wAlignment = PFA_CENTER;
else if(dwStyle & ES_RIGHT)
pf.wAlignment = PFA_RIGHT;
}
fInplaceActive = TRUE;
// 此处修改代码,明确指定加载riched20.dll库
//CreateTextServices()函数
typedef HRESULT(_stdcall *CTSFunc)(IUnknown *punkOuter, ITextHost *pITextHost, IUnknown **ppUnk);
CTSFunc ctsFunc = NULL;
auto hRiched20 = LoadLibrary(_T("Riched20.dll"));
if (NULL == hRiched20)
goto err;
else
{
ctsFunc = (CTSFunc)GetProcAddress(hRiched20, "CreateTextServices");
if (NULL == ctsFunc)
goto err;
}
if (FAILED(ctsFunc(NULL, this, &pUnk)))
goto err;
// Create Text Services component
//if(FAILED(CreateTextServices(NULL, this, &pUnk)))
// goto err;
hr = pUnk->QueryInterface(IID_ITextServices,(void **)&pserv);
// Whether the previous call succeeded or failed we are done
// with the private interface.
pUnk->Release();
if(FAILED(hr))
{
goto err;
}
hr = pserv->TxSendMessage(EM_GETOLEINTERFACE, 0, (LPARAM)&m_pRichEditOle, NULL);
if(FAILED(hr))
{
goto err;
}
m_pIRichEditOleCallback = new IRichEditOleCallbackEx;
pserv->TxSendMessage(EM_SETOLECALLBACK, 0, (LPARAM)m_pIRichEditOleCallback, 0);
// Set window text
if(pcs && pcs->lpszName)
{
#ifdef _UNICODE
if(FAILED(pserv->TxSetText((TCHAR *)pcs->lpszName)))
goto err;
#else
size_t iLen = _tcslen(pcs->lpszName);
LPWSTR lpText = new WCHAR[iLen + 1];
::ZeroMemory(lpText, (iLen + 1) * sizeof(WCHAR));
::MultiByteToWideChar(CP_ACP, 0, pcs->lpszName, -1, (LPWSTR)lpText, iLen) ;
if(FAILED(pserv->TxSetText((LPWSTR)lpText))) {
delete[] lpText;
goto err;
}
delete[] lpText;
#endif
}
return TRUE;
err:
return FALSE;
}
4.2、解决向CRichEditUI中插入文字后显示空白的问题
我们在项目中使用CRichEditUI控件时,遇到一个较严重的问题,即向CRichEditUI控件插入文字后,控件显示空白,如果手动操作鼠标在richedit中滚动一下就能显示出文字了,这个问题我们遇到很多次了。针对手动滚动一下鼠标就能显示出来文字,我们搞出了一个规避的办法,可以将滚动条向上或向下滚动一下,然后再滚动回原来的位置。这里分两种场景:
1)插入文字后,将滚动条默认滚动到最上面(让用户看到CRichEditUI控件中最开始的文字)
先向下滚动一下(发送SB_LINEDOWN),然后再回到顶端(发送SB_TOP),相关代码如下:
// 默认是滚到顶部,为了解决显示空白的问题,先向下滚动一下,然后再滚动到顶部
m_pRichEditVerInfo->TxSendMessage(WM_VSCROLL, SB_LINEDOWN, 0L, 0);
m_pRichEditVerInfo->TxSendMessage(WM_VSCROLL, SB_TOP, 0L, 0);
m_pRichEditVerInfo->Invalidate();
2)插入文字后,将滚动条默认滚动到最下面(让用户看到CRichEditUI控件中最下面最新的文字)
先向上滚动一下(发送SB_LINEUP),然后再回到顶端(发送SB_BOTTOM),相关代码如下:
// 默认是滚到顶部,为了解决显示空白的问题,先向上滚动一下,然后再滚动到底部
m_pRichEditVerInfo->TxSendMessage(WM_VSCROLL, SB_LINEUP, 0L, 0);
m_pRichEditVerInfo->TxSendMessage(WM_VSCROLL, SB_BOTTOM, 0L, 0);
m_pRichEditVerInfo->Invalidate();
在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)
专栏1:(该精品技术专栏的订阅量已达到490多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!欢迎订阅!)
C++软件调试与异常排查从入门到精通系列文章汇总https://blog.csdn.net/chenlycly/article/details/125529931
本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的项目问题实战分析实例(很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!
考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!
专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!
专栏2:
C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795
常用的C++软件辅助分析工具有PE工具、Dependency Walker、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!
专栏3: (本专栏文章已经更新到270多篇,还在持续更新中,欢迎订阅)
C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html
以多年的开发实战为基础,总结并讲解一些的C/C++基础与进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域的多个方面的内容,同时给出C/C++及网络方面的常见笔试面试题,并详细讲述Visual Studio常用调试手段与技巧!
专栏4:
VC++常用功能开发汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/124272585
将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。
专栏5:
Windows C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.html
根据多年C++软件开发实践,详细地总结了Windows C++ 应用软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。
5、设置窗口透明度的接口CPaintManagerUI::SetTransparent的问题
duilib框架中的窗口支持设置透明度,设置窗口透明度的是CPaintManagerUI::SetTransparent接口,如下:
// 设置透明度,调用内核库函数
void CPaintManagerUI::SetTransparent(int nOpacity)
{
if (NULL == m_hWndPaint)
{
return;
}
typedef BOOL(__stdcall *PFUNCSETLAYEREDWINDOWATTR)(HWND, COLORREF, BYTE, DWORD);
PFUNCSETLAYEREDWINDOWATTR fSetLayeredWindowAttributes;
HMODULE hUser32 = ::GetModuleHandle(_T("User32.dll"));
if (hUser32)
{
fSetLayeredWindowAttributes =
(PFUNCSETLAYEREDWINDOWATTR)::GetProcAddress(hUser32, "SetLayeredWindowAttributes");
if (NULL == fSetLayeredWindowAttributes)
{
return;
}
}
DWORD dwStyle = ::GetWindowLong(m_hWndPaint, GWL_EXSTYLE);
DWORD dwNewStyle = dwStyle;
if (nOpacity >= 0 && nOpacity < 255)
{
dwNewStyle |= WS_EX_LAYERED;
}
else
{
// 1、问题1:不设置透明度,即nOpacity=255,也不应该取消WS_EX_LAYERED风格
// 可能外部要实现异形窗口,是需要WS_EX_LAYERED窗口风格
dwNewStyle &= ~WS_EX_LAYERED;
}
if (dwStyle != dwNewStyle)
{
::SetWindowLong(m_hWndPaint, GWL_EXSTYLE, dwNewStyle);
}
// 问题2:不设置透明度,即nOpacity=255,是不需要调用SetLayeredWindowAttributes接口
// 不管有没有设置透明度,都去调用SetLayeredWindowAttributes,可能和异形窗口调用UpdateLayeredWindow
fSetLayeredWindowAttributes(m_hWndPaint, 0, nOpacity, LWA_ALPHA);
}
5.1、窗口透明度设置为255(不透明),之前添加的WS_EX_LAYERED风格被删除了
我们之前在一个项目中要实现一个异形窗口,创建了一个dui窗口,给窗口设置WS_EX_LAYERED风格,然后调用UpdateLayeredWindow。
要实现异形窗口,首先要给窗口设置WS_EX_LAYERED分层窗口风格,然后要调用系统API接口UpdateLayeredWindow。
结果调试发现,异形窗口并没有生效,查看UpdateLayeredWindow函数返回值,发现UpdateLayeredWindow函数调用失败了,GetLastError获取到的错误码为87:
使用SPY++查看该窗口的属性,结果发现该窗口并没有WS_EX_LAYERED风格,明明代码中给窗口设置了WS_EX_LAYERED风格,难道是该窗口风格在什么地方被取消了?于是在代码中全局搜索了一下WS_EX_LAYERED,发现在duilib中的CPaintManagerUI::SetTransparent中有取消WS_EX_LAYERED风格的代码,如下所示:
DWORD dwStyle = ::GetWindowLong(m_hWndPaint, GWL_EXSTYLE);
DWORD dwNewStyle = dwStyle;
if (nOpacity >= 0 && nOpacity < 255)
{
dwNewStyle |= WS_EX_LAYERED;
}
else
{
// 1、问题1:不设置透明度,即nOpacity=255,也不应该取消WS_EX_LAYERED风格
// 可能外部要实现异形窗口,是需要WS_EX_LAYERED窗口风格
dwNewStyle &= ~WS_EX_LAYERED;
}
if (dwStyle != dwNewStyle)
{
::SetWindowLong(m_hWndPaint, GWL_EXSTYLE, dwNewStyle);
}
窗口不需要设置透明度,所以传入的透明度为255,从上述代码看,透明度为255时就被人为取消了WS_EX_LAYERED风格,显然这是不合理的。窗口不设置透明度,也有可能要实现异形窗口,也是需要WS_EX_LAYERED风格的。于是修改了一下,即不设置透明度(即nOpacity=255),也不能取消WS_EX_LAYERED:(将取消WS_EX_LAYERED风格代码删除掉)
DWORD dwStyle = ::GetWindowLong(m_hWndPaint, GWL_EXSTYLE);
DWORD dwNewStyle = dwStyle;
if (nOpacity >= 0 && nOpacity < 255)
{
dwNewStyle |= WS_EX_LAYERED;
}
if (dwStyle != dwNewStyle)
{
::SetWindowLong(m_hWndPaint, GWL_EXSTYLE, dwNewStyle);
}
5.2、 先调用了SetLayeredWindowAttributes接口,导致后面调用UpdateLayeredWindow失败
但修改代码之后,UpdateLayeredWindow函数还是会调用失败,错误码还是87。于是回到CPaintManagerUI::SetTransparent这个接口,发现了第二个问题,没设置透明度(即nOpacity=255),代码中还是调用了SetLayeredWindowAttributes,而这个SetLayeredWindowAttributes函数调用是和UpdateLayeredWindow互斥的,一旦调用了SetLayeredWindowAttributes函数,UpdateLayeredWindow函数调用就会失败,于是又修改了此处的代码,即没设置透明度(即nOpacity=255),就不要调用SetLayeredWindowAttributes函数:(增加了一个需要设置透明度的标记变量bNeedSetWindowOpacity)
if (bNeedSetWindowOpacity)
{
fSetLayeredWindowAttributes(m_hWndPaint, 0, nOpacity, LWA_ALPHA);
}
综上,修改后的完整代码如下:
void CPaintManagerUI::SetTransparent(int nOpacity)
{
if (NULL == m_hWndPaint)
{
return;
}
typedef BOOL(__stdcall *PFUNCSETLAYEREDWINDOWATTR)(HWND, COLORREF, BYTE, DWORD);
PFUNCSETLAYEREDWINDOWATTR fSetLayeredWindowAttributes;
HMODULE hUser32 = ::GetModuleHandle(_T("User32.dll"));
if (hUser32)
{
fSetLayeredWindowAttributes =
(PFUNCSETLAYEREDWINDOWATTR)::GetProcAddress(hUser32, "SetLayeredWindowAttributes");
if (NULL == fSetLayeredWindowAttributes)
{
return;
}
}
BOOL bNeedSetWindowOpacity = FALSE;
DWORD dwStyle = ::GetWindowLong(m_hWndPaint, GWL_EXSTYLE);
DWORD dwNewStyle = dwStyle;
if (nOpacity >= 0 && nOpacity < 255)
{
dwNewStyle |= WS_EX_LAYERED;
bNeedSetWindowOpacity = TRUE;
}
else
{
// 1、问题1:不设置透明度,即nOpacity=255,也不应该取消WS_EX_LAYERED风格
// 可能外部要实现异形窗口,是需要WS_EX_LAYERED窗口风格
//dwNewStyle &= ~WS_EX_LAYERED;
}
if (dwStyle != dwNewStyle)
{
::SetWindowLong(m_hWndPaint, GWL_EXSTYLE, dwNewStyle);
}
// 问题2:不设置透明度,即nOpacity=255,是不需要调用SetLayeredWindowAttributes接口
// 不管有没有设置透明度,都去调用SetLayeredWindowAttributes,可能和异形窗口调用UpdateLayeredWindow
if (bNeedSetWindowOpacity)
{
fSetLayeredWindowAttributes(m_hWndPaint, 0, nOpacity, LWA_ALPHA);
}
}
上述问题的排查过程以及实战功能实现的完整案例,可以参见我之前写的文章:
开发实例分享 | 使用Layered分层窗口实现视频会议中的桌面区域共享(使用分层窗口将部分区域透掉,鼠标可穿透)https://blog.csdn.net/chenlycly/article/details/139588302
关于SetLayeredWindowAttributes调用后再调用UpdateLayeredWindow会失败的详细说明,可以查看我的文章:
duilib中设置窗口透明度的接口CPaintManagerUI::SetTransparent有问题导致使用duilib窗口实现异形窗口无效的排查https://blog.csdn.net/chenlycly/article/details/140687246
6、弹出一个dui窗口的模态框时要将自销毁标记置为false
6.1、dui窗口对象的自销毁实现说明
在duilib框架中,dui窗口对象在窗口销毁时默认是自销毁的(delete this),相关代码如下所示:
LRESULT CALLBACK CWindowWnd::__WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
CWindowWnd* pThis = NULL;
if( WM_NCCREATE == uMsg )
{
LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
pThis->m_hWnd = hWnd;
::SetWindowLongPtr( hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis) );
}
else
{
pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
if( WM_NCDESTROY == uMsg && pThis != NULL )
{
LRESULT lRes = ::CallWindowProc( pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam );
::SetWindowLongPtr( pThis->m_hWnd, GWLP_USERDATA, 0L );
if( pThis->m_bSubclassed )
{
pThis->Unsubclass();
}
pThis->m_hWnd = NULL;
pThis->OnFinalMessage( hWnd );
return lRes;
}
}
if( pThis != NULL )
{
return pThis->HandleMessage( uMsg, wParam, lParam );
}
else
{
return ::DefWindowProc( hWnd, uMsg, wParam, lParam );
}
}
void CXXXWindow::OnFinalMessage( HWND /*hWnd*/ )
{
if( m_bDeleteSelf )
{
delete this; // 自动销毁当前的窗口类
}
}
窗口销毁时会产生WM_NCDESTROY消息,该消息进入窗口处理过程函数中处理,会调用OnFinalMessage接口,该接口中会自动将该窗口对象销毁。
6.2、自销毁特性引发的崩溃问题
这个自销毁的特性,在某些场景下可能会引发问题。可以来看如下的一段代码:
class CMessageBoxDlg : public WindowImplBase
{
public:
CMessageBoxDlg( STRINGorID strUI, unsigned int dwTransparent = 255, bool bDeleteSelf = true );
virtual ~CMessageBoxDlg();
// ...(其余代码省略)
}
void ShowMsgBoxTip()
{
CMessageBoxDlg dlg(IDR_XML_MSG_NOTIFY_DLG);
dlg.SetInfo(_T("文件正被占用中,请稍后再发送"), STRING_TIP, m_hParentWnd, ID_OK);
dlg.Create(m_hParentWnd, STRING_TIP, UI_WNDSTYLE_BOX, WS_EX_TOOLWINDOW);
dlg.CenterWindow(); // 居中显示
dlg.ShowModal();
}
首先,定义了窗口类CMessageBoxDlg,其构造函数中第三个参数是bDeleteSelf自销毁选项。我们在用这个窗口类定义一个局部变量,然后创建这个窗口,然后调用ShowModal显示模态框。弹出该模态提示框,点击确定,将窗口关闭(窗口将被销毁),ShowModal返回,dui框架中会自动将该CMessageBoxDlg dlg对象自动delete掉,就会产生崩溃!
为什么会产生崩溃呢?因为CMessageBoxDlg dlg对象是局部变量,对象的内存是在栈上分配的,而delete释放的是堆内存,去释放栈内存肯定就有问题了!新手可能会犯这样的错误!关于C++程序内存的五大分区,可以去查看文章:
实例详解C++程序的五大内存分区https://blog.csdn.net/chenlycly/article/details/120958761 解决办法很简单,在定义CMessageBoxDlg dlg对象时将第三个参数bDeleteSelf设置为false,就是不要dui框架去自动销毁了:
// 第三个参数bDeleteSelf设置false,让dui框架不要自动delete窗口对象
CMessageBoxDlg dlg(IDR_XML_MSG_NOTIFY_DLG, 255, false);
因为是局部变量,该局部变量的内存在所在的函数ShowMsgBoxTip退出时会自动释放。
如果CMessageBoxDlg对象是new出来的,如下:
CMessageBoxDlg* pDlg = new CMessageBoxDlg(IDR_XML_MSG_NOTIFY_DLG);
pDlg->SetInfo(strTip, STRING_TIP, NULL);
pDlg->Create(g_pMainLogic->GetMainHwnd(), STRING_ERR_TIP, UI_WNDSTYLE_BOX, WS_EX_TOOLWINDOW);
pDlg->CenterWindow();
pDlg->ShowWindow(SW_SHOW);
此时是可以依赖dui框架的自销毁的。
7、CMenuWnd菜单窗口中的菜单项相关实现细节
7.1、设置菜单窗口属性以及构建菜单窗口中的菜单项
可以直接在xml中添加菜单项,也可以实时去new一个菜单项(new CMenuElementUI)添加到菜单窗口CMenuWnd中。但不管哪种方式,都需要在xml中设置菜单窗口的基本属性,如下:
包括菜单项的文字颜色itemtextcolor、悬浮选中时的颜色itemselectedtextcolor、菜单项背景图片itembkimage、悬浮选中时的背景图片itemselectedimage等。
上面的截图中,就是直接在xml文件中添加菜单项的,有两个菜单项。也可以动态地去new一个CMenuElementUI菜单项,添加到菜单窗口CMenuWnd中,如下所示:
CMenuWnd* pMenu = new CMenuWnd( IDR_XML_DEFAULT_MENU_CONFIG, _T("xml"), &m_pm );
ASSERT( pMenu != NULL );
// 添加菜单项1
CMenuElementUI* pElement = new CMenuElementUI();
pElement->SetName( _T("MenuOpenOper") );
pElement->SetText( STRING_WB_OPEN_DATA_OPER );
pElement->SetFixedHeight( MENU_ITEM_HEIGHT );
pMenu->AddMenuItem( pElement );
// 添加菜单项2
pElement = new CMenuElementUI();
pElement->SetName( _T("MenuCloseOper") );
pElement->SetText( STRING_WB_CLOSE_DATA_OPER );
pElement->SetFixedHeight( MENU_ITEM_HEIGHT );
pMenu->AddMenuItem( pElement );
// 调用TrackPopupMenu将菜单窗口显示出来
RECT rcBtnDataOper = pBtnDataOper->GetPos();
CPoint pt;
pt.x = rcBtnDataOper.left;
pt.y = rcBtnDataOper.bottom;
::ClientToScreen( GetHWND(), &pt );
pMenu->TrackPopupMenu( eMenuAlignment_Left | eMenuAlignment_Top, pt, GetHWND() );
7.2、操作菜单项的其他接口
此外,还有几个操作菜单项的其他接口。
可以调用CMenuWnd::EnableMenuItem接口根据菜单控件名称将菜单项置灰或者使能:
pMenu->EnableMenuItem( _T("menu_open_oper"), false );
可以调用CMenuWnd::ModifyMenu接口,修改菜单项的文字:
pMenu->ModifyMenu(_T("menu_ctrl"), _T("控制"));
可以调用CMenuWnd::DeleteMenuItem删除菜单项:(比如我们在xml中配置了固定的几个菜单项,如果有个菜单项不要,可以通过代码删除)
pMenu->DeleteMenuItem(_T("menu_shortmsg"));
还可以在菜单项之间设置一个分割线项,该分割线项只设置一个像素高度,设置一条横线的背景图即可:
// 分割线,设置一个像素高度,设置一条横线的背景图
CControlUI* pControl = new CControlUI();
pControl->SetName(MENU_SEPARATOR_NAME);
pControl->SetFixedHeight(1);
pControl->SetBkImage(MENU_SEPARATOR_BK_IMG);
pMenuWnd->AddMenuItem(pControl);
8、在格式化CStdString字符串变量时要注意的问题
8.1、将CStdString::Format接口中不支持浮点的wvsprintf接口换成_vstprintf_s
在CStdString类的格式化字符串的Format接口中,要将不支持浮点数的wvsprintf接口,换成支持浮点数的_vstprintf_s,代码如下:
int CStdString::Format( LPCTSTR pstrFormat, ... )
{
CStdString sFormat = pstrFormat;
// Do ordinary printf replacements
// NOTE: Documented max-length of wvsprintf() is 1024
TCHAR szBuffer[1025] = { 0 };
va_list argList;
va_start( argList, pstrFormat );
// wvsprintf不支持浮点格式,所以换成_vstprintf,by 2013/12/02
// 使用_vstprintf_s安全一点
int iRet = ::_vstprintf_s( szBuffer, sizeof(szBuffer)/sizeof(TCHAR), sFormat, argList );
va_end( argList );
Assign( szBuffer );
return iRet;
}
8.2、在格式化CStdString对象时遇到的崩溃问题
我们在项目中格式化CStdString对象中的字符串时就遇到了一个很典型的问题,甚至引发了崩溃。我们看如下的一段示例代码:
CStdString strLog;
CStdString strName;
CStdString strId;
// .... 获取名称和Id的代码省略
// 将strName和strId中的字符串格式化
strLog.Format(_T("strName: %s, strId: %s"),strName,strId );
就是这句格式化的代码有问题!咋一看上去,好像没有问题,但从CStdString类的成员变量构成以及函数调用时参数是通过栈传递的角度,就会发现问题了!
CStdString类有两个成员变量:m_pstr和m_szBuffer数组,如下所示:
class DIRECTUI_API CStdString
{
public:
enum { MAX_LOCAL_STRING_LEN = 16 };
CStdString(); // 初始化字符串类
CStdString( const TCHAR ch );
// 该类中间部分的代码全部省略
protected:
LPTSTR m_pstr; // 字符指针
TCHAR m_szBuffer[MAX_LOCAL_STRING_LEN]; // 字符缓冲区
};
对于有问题的Format这句代码:
strLog.Format(_T("strName: %s, strId: %s"),strName,strId );
在执行进Format这个接口之前,传入的参数strName和strId内存中的内容压到栈上,通过栈传递给被调用函数Format的,所以会把CStdString类的两个成员变量m_pstr和m_szBuffer数组内存中的内容压到栈上,而有两个参数strName和strId,相当于要将4个变量的内存压到栈上,而前面的格式化符号只有两个,目的是要将strName和strId中存放的字符串进行格式化的。
格式化函数Format内部(函数的更底部)会依次根据解析出来的格式化符,依次到栈上取出格式化符对应的变量的值,去格式化。因为strName将两个成员变量m_pstr和m_szBuffer数组内存中内容压到栈上,所以在解析出第二个%s后到栈上拿数据,就错位了,就不是想要拿的strId中存放的字符串了!
要搞懂这个问题,首先要了解函数调用时参数的值是如何通过栈内存传递给被调用函数的,以及格式化函数内存时如何通过解析出来的格式化符到栈上拿数据的。
关于函数调用的栈分布,可以查看我之前写的文章:
C++函数调用栈分布详解https://blog.csdn.net/chenlycly/article/details/121001096 关于格式化相关的项目实战问题分析实例,可以查看我之前写的文章:
将string类对象中的内容格式化到字符串buffer中时遇到的异常崩溃分析https://blog.csdn.net/chenlycly/article/details/126211718UINT64整型数据在格式化时使用了不匹配的格式化符%d导致其他参数无法打印的问题排查https://blog.csdn.net/chenlycly/article/details/132549186 这个问题的解决办法很简单,当一个类中包含不止一个成员时,应该明确类中的哪个数据成员要参与格式化!对于本例中的CStdString类对象,要格式化类中存放的字符串,直接引用类中的成员变量m_pstr即可,可以调用CStdString::GetData获取即可,所以修改后的代码为:
strLog.Format(_T("strName: %s, strId: %s"),strName.GetData(),strId.GetData() );
9、常用布局CVerticalLayOutUI、CHorizontalLayoutUI和CTileLayoutUI的使用技巧
9.1、常用布局特性说明
除了基本控件之外,duilib为了方便大家对界面控件元素进行排列布局,提供了几个通用的布局,即垂直布局CVerticalLayOutUI、水平布局CHorizontalLayoutUI和平铺布局CTileLayoutUI等。
这些布局均继承于容器类CContainerUI,CContainerUI中抽象出了一些共性的东西。我们在编写xml去构建UI界面时,主要使用这几个布局去排布窗口中的控件去实现界面的排布,其中垂直布局CVerticalLayOutUI和水平布局CVerticalLayOutUI最为常用。不同的布局,其内部的控件排布方法是不同的(具体的控件排布控制可以查看布局类的SetPos接口),要根据界面的构成,划分出不同的区域,选择不同的布局。
在垂直布局CVerticalLayOutUI中,控件是在垂直方向上排布的,如下所示:
在水平布局CVerticalLayOutUI中,控件默认是在水平方向上排布的,如下所示:
在平铺布局CTileLayoutUI中,控件默认是一个方格一个方格排布的,比如企业微信的表情窗口中一个个表情格:
在实际编写界面xml时,基本都要在布局中嵌套布局,即布局中还有子布局,需要根据界面效果,灵活使用这些常用的布局!
9.2、使用布局去构建UI界面的实例
在实现UCD设计的UI界面时,需要对界面进行区域划分,划分出多个区域,每个区域中的控件怎么依赖所属的布局进行排布,以QQ登录界面为例:
讲解一下如何进行区域划分,如何使用布局实现区域中的控件排布。
将上述界面划分成若干个区域,区域内再去控制控件的排放,选区域的原则是区域内方便使用布局去控制具体控件的排布。将整个界面先分成3大区域:水平布局1、水平布局2和水平布局3,这三个区域内部可以直接控制控件的排放,因为这三个区域正还是垂直方向上排布的,所以最外层使用垂直布局CVerticalLayOutUI。
怎么划分区域,也是要看经验,做的多了,就熟悉了!
对于水平布局1,内部有三小块,左边固定位置的QQ图标(可设置固定宽度),右边是相对右边界固定的位置(设置固定宽度),中间这个区域可以使用占位符控件(除了左边和右边的固定宽度之外,余下的都留给占位符),这样这三小块在水平布局1内水平方向排布的,中间区域是占位符区域,可以直接使用水平布局的自动排布。
对于水平布局2,水平方向上可以分三个区域,中间区域是固定宽度的头像区域,两边可以做成占位符区域,所以利用水平布局的自动排布,中间区域设置固定宽度,余下的宽度被左右两边的占位符平分,这样头像区域就自动居中了。
对于水平布局3,也是水平方向上分出3大块,中间固定宽度的一块以及左右两边的占位符区域,这样中间区域设置固定宽度,余下宽度被两边的占位符区域平分,这样中间区域就自动居中了。然后,中间的区域又可以嵌套布局,根据控件的摆放位置,可以设置成垂直布局,让控件在竖直方向上做排布控制,就可以了!
对于CVerticalLayOutUI、CHorizontalLayoutUI和CTileLayoutUI等布局,其内部排布控件的方法,查看这些类的SetPos接口就可以插看到。
10、为了给字体设置提高文字清晰度的ClearType属性,_WIN32_WINNT宏的值从0x500修改成0x501,导致ToolTip窗口不显示的问题
为什么要将_WIN32_WINNT宏的值从0x500修改成0x501呢?当时我们的软件在设置高DPI的电脑上运行,系统会对软件界面进行放大,放大后字体会比较模糊,为了将文字显示的更清晰一点,给绘制文字的字体设置ClearType属性,这样绘制出来的文字会更有棱角一点,会清晰一些。操作系统也支持设置ClearType选项,如下所示:
如何给字体设置ClearType属性呢?我们在使用LOGFONT结构体创建字体时,直接给LOGFONT结构体中的lfQuality字段设置CLEARTYPE_QUALITY值即可,如下所示:
跳转到CLEARTYPE_QUALITY宏的定义处:
可以看到只有当定义的NT版本达到0x501,才定义这个CLEARTYPE_QUALITY宏,但我们的directui工程中将_WIN32_WINNT宏值定义为0x500,于是为了支持CLEARTYPE_QUALITY宏,直接_WIN32_WINNT的值由0x500改成0x501。
结果测试发现,程序中所有的ToolTip窗口都不显示了,就与这个将_WIN32_WINNT的值由0x500改成0x501有关!此问题具体的细节以及解决的办法,可以参见我之前写的文章:
当_WIN32_WINNT大于0x500时,ToolTip窗口不显示问题排查https://blog.csdn.net/chenlycly/article/details/139565872