最近接触GDI编程比较多,就把常见的技巧和注意点整理成一个系列吧,希望对大家有帮助。
1.TextOut的基本使用
TextOut的属于比较老的文本输出函数,但是简单的文本输出和格式控制使用它非常方便,废话不多说,基本用法如下:
- void DrawArea1(CDCHandle mydc, POINT ptLeftTop, POINT ptRightBottom)
- {
- mydc.SaveDC();
- CRect rc(ptLeftTop, ptRightBottom);
- CBrushHandle brush;
- brush.CreateSolidBrush(RGB(255,0,0));
- FillRect(mydc, rc, brush);
- brush.DeleteObject();
- mydc.SetBkMode(OPAQUE);
- mydc.TextOut(10+ptLeftTop.x, 10+ptLeftTop.y, L"文字填充背景 非透明");
- mydc.SetBkMode(TRANSPARENT);
- mydc.TextOut(10+ptLeftTop.x, 30+ptLeftTop.y, L"文字填充背景 透明");
- mydc.SetBkMode(OPAQUE);
- mydc.SetBkColor(RGB(0,255,0));
- mydc.TextOut(10+ptLeftTop.x, 50+ptLeftTop.y, L"文字填充背景 非透明 设置背景填充色");
- mydc.SetBkMode(OPAQUE);
- mydc.SetBkColor(RGB(0,0,255));
- mydc.SetTextColor(RGB(0,255,0));
- mydc.TextOut(10+ptLeftTop.x, 70+ptLeftTop.y, L"文字填充背景 非透明 设置背景填充色和文字颜色");
- mydc.RestoreDC(-1);
- }
void DrawArea1(CDCHandle mydc, POINT ptLeftTop, POINT ptRightBottom)
{
mydc.SaveDC();
CRect rc(ptLeftTop, ptRightBottom);
CBrushHandle brush;
brush.CreateSolidBrush(RGB(255,0,0));
FillRect(mydc, rc, brush);
brush.DeleteObject();
mydc.SetBkMode(OPAQUE);
mydc.TextOut(10+ptLeftTop.x, 10+ptLeftTop.y, L"文字填充背景 非透明");
mydc.SetBkMode(TRANSPARENT);
mydc.TextOut(10+ptLeftTop.x, 30+ptLeftTop.y, L"文字填充背景 透明");
mydc.SetBkMode(OPAQUE);
mydc.SetBkColor(RGB(0,255,0));
mydc.TextOut(10+ptLeftTop.x, 50+ptLeftTop.y, L"文字填充背景 非透明 设置背景填充色");
mydc.SetBkMode(OPAQUE);
mydc.SetBkColor(RGB(0,0,255));
mydc.SetTextColor(RGB(0,255,0));
mydc.TextOut(10+ptLeftTop.x, 70+ptLeftTop.y, L"文字填充背景 非透明 设置背景填充色和文字颜色");
mydc.RestoreDC(-1);
}
效果如下:
可以看到,
1.使用SetBkMode决定背景是否透明,这在一张背景图上输出文字时经常遇到
2.使用SetBkColor和SetTextColor设置文本和文本背景颜色
2.SetTextAlign控制TextOut输出格式
如下:
- void DrawArea2(CDCHandle mydc, POINT ptLeftTop, POINT ptRightBottom)
- {
- mydc.SaveDC();
- mydc.SetTextAlign(TA_RIGHT);
- mydc.TextOut(150+ptLeftTop.x, 10+ptLeftTop.y, L"TextOut Right Align");
- mydc.SetTextAlign(TA_LEFT);
- mydc.TextOut(150+ptLeftTop.x, 30+ptLeftTop.y, L"TextOut Left Align");
- mydc.SetTextAlign(TA_TOP);
- mydc.TextOut(150+ptLeftTop.x, 70+ptLeftTop.y, L"Top Align");
- mydc.SetTextAlign(TA_BOTTOM);
- mydc.TextOut(150+ptLeftTop.x, 70+ptLeftTop.y, L"Bottom Align");
- mydc.SetTextAlign(TA_TOP |TA_LEFT);
- mydc.TextOut(150+ptLeftTop.x, 90+ptLeftTop.y, L"Line 1\nLine2\nLine3\tLine3");//不识别居中
- mydc.RestoreDC(-1);
- }
void DrawArea2(CDCHandle mydc, POINT ptLeftTop, POINT ptRightBottom)
{
mydc.SaveDC();
mydc.SetTextAlign(TA_RIGHT);
mydc.TextOut(150+ptLeftTop.x, 10+ptLeftTop.y, L"TextOut Right Align");
mydc.SetTextAlign(TA_LEFT);
mydc.TextOut(150+ptLeftTop.x, 30+ptLeftTop.y, L"TextOut Left Align");
mydc.SetTextAlign(TA_TOP);
mydc.TextOut(150+ptLeftTop.x, 70+ptLeftTop.y, L"Top Align");
mydc.SetTextAlign(TA_BOTTOM);
mydc.TextOut(150+ptLeftTop.x, 70+ptLeftTop.y, L"Bottom Align");
mydc.SetTextAlign(TA_TOP |TA_LEFT);
mydc.TextOut(150+ptLeftTop.x, 90+ptLeftTop.y, L"Line 1\nLine2\nLine3\tLine3");//不识别居中
mydc.RestoreDC(-1);
}
效果如下:
默认的是TA_LEFT和TA_TOP
注意:
1.TA_RIGHT意味着输出文字是以当前的坐标向左输出
2.TA_BOTTOM意味着当前字体的底部和当前输出的坐标点对齐,所以可以看到同样是距离顶部70,TA_TOP和TA_BOTTOM分别用当前字体的顶部和底部和当前输出坐标点对齐,导致两次输出不在同一行。
3.TextOut不支持转义字符\n、\t的使用
3.DrawText使用
DrawText是TextOut的升级版,支持更多的格式控制,如下:
- void DrawArea3(CDCHandle mydc, POINT ptLeftTop, POINT ptRightBottom)
- {
- mydc.SaveDC();
- CRect rc(ptLeftTop, ptRightBottom);
- CBrushHandle brush;
- brush.CreateSolidBrush(RGB(120,120,0));
- FillRect(mydc, rc, brush);
- brush.DeleteObject();
- CSize sizeText;
- CRect rcText(ptLeftTop, ptRightBottom);
- //自动计算居中
- CString csText = L"A Single Line1";
- mydc.DrawText(csText, csText.GetLength(), rcText, DT_SINGLELINE | DT_CENTER/* | DT_VCENTER*/);
- //手动计算居中
- csText = L"A Single Line2";
- mydc.GetTextExtent(csText, csText.GetLength(), &sizeText);
- rcText = CRect(ptLeftTop.x + (ptRightBottom.x-ptLeftTop.x-sizeText.cx)/2,
- 20+ptLeftTop.y,
- ptLeftTop.x + (ptRightBottom.x-ptLeftTop.x-sizeText.cx)/2 + sizeText.cx,
- 20+ptLeftTop.y+ sizeText.cy);
- mydc.DrawText(csText, csText.GetLength(), rcText, DT_SINGLELINE | DT_LEFT | DT_TOP);
- //换种方法计算居中
- csText = L"A Single Line3";
- CRect rcCalc;
- mydc.DrawText(csText, csText.GetLength(), rcCalc, DT_CALCRECT | DT_SINGLELINE);//也可计算多行
- rcText = CRect(ptLeftTop.x + (ptRightBottom.x-ptLeftTop.x-rcCalc.Width())/2,
- 40+ptLeftTop.y,
- ptLeftTop.x + (ptRightBottom.x-ptLeftTop.x-rcCalc.Width())/2 + sizeText.cx,
- 40+ptLeftTop.y+ rcCalc.Height());
- mydc.DrawText(csText, csText.GetLength(), rcText, DT_SINGLELINE | DT_LEFT | DT_TOP);
- //消除背景
- mydc.SetBkMode(TRANSPARENT);
- //多行文字,识别\n,使用DT_EXPANDTABS扩展\t
- rcText = CRect(CPoint(ptLeftTop.x, ptLeftTop.y+60), ptRightBottom);
- csText = L"Line 1\nLine2\nLine3\tLine3";
- mydc.DrawText(csText, csText.GetLength(), rcText, DT_LEFT | DT_TOP | DT_EXPANDTABS);
- //自动截断-DT_END_ELLIPSIS-多行时只截断最后一行
- rcText = CRect(CPoint(ptLeftTop.x, ptLeftTop.y+120), ptRightBottom);
- csText = L"1A Very Long Long Long Long Long Long Long Long Long Long Single Line\n"
- L"1A Very Long Long Long Long Long Long Long Long Long Long Single Line";
- mydc.DrawText(csText, csText.GetLength(), rcText, DT_LEFT | DT_TOP | DT_END_ELLIPSIS);
- //自动截断-DT_PATH_ELLIPSIS-多行时只截断路径最后一行
- rcText = CRect(CPoint(ptLeftTop.x, ptLeftTop.y+160), ptRightBottom);
- csText = L"C:\\testtesttest\\testtesttest\\testtesttest\\testtesttest\\testtesttest\\testtesttest\\testtesttest\n"
- L"\\testtesttest\\testtesttest\\testtesttest\\testtesttest\\testtesttest\\testtesttest\\testtesttest";
- mydc.DrawText(csText, csText.GetLength(), rcText, DT_LEFT | DT_TOP | DT_PATH_ELLIPSIS);
- //自动截断-DT_WORD_ELLIPSIS-多行时截断每一行
- rcText = CRect(CPoint(ptLeftTop.x, ptLeftTop.y+200), ptRightBottom);
- csText = L"2A Very Long Long Long Long Long Long Long Long Long Long Single Line\n"
- L"2A Very Long Long Long Long Long Long Long Long Long Long Single Line";
- mydc.DrawText(csText, csText.GetLength(), rcText, DT_LEFT | DT_TOP | DT_WORD_ELLIPSIS);
- //设置前后景颜色
- mydc.SetBkMode(OPAQUE);
- mydc.SetBkColor(RGB(0,0,255));
- mydc.SetTextColor(RGB(0,255,0));
- //自动换行
- rcText = CRect(CPoint(ptLeftTop.x, ptLeftTop.y+240), ptRightBottom);
- csText = L"1A Very Long Long Long Long Long Long Long Long Long Long Single Line to change line";
- mydc.DrawText(csText, csText.GetLength(), rcText, DT_LEFT | DT_TOP);
- rcText = CRect(CPoint(ptLeftTop.x, ptLeftTop.y+260), ptRightBottom);
- csText = L"2A Very Long Long Long Long Long Long Long Long Long Long Single Line to change line";
- mydc.DrawText(csText, csText.GetLength(), rcText, DT_LEFT | DT_TOP | DT_WORDBREAK);
- mydc.RestoreDC(-1);
- }
void DrawArea3(CDCHandle mydc, POINT ptLeftTop, POINT ptRightBottom)
{
mydc.SaveDC();
CRect rc(ptLeftTop, ptRightBottom);
CBrushHandle brush;
brush.CreateSolidBrush(RGB(120,120,0));
FillRect(mydc, rc, brush);
brush.DeleteObject();
CSize sizeText;
CRect rcText(ptLeftTop, ptRightBottom);
//自动计算居中
CString csText = L"A Single Line1";
mydc.DrawText(csText, csText.GetLength(), rcText, DT_SINGLELINE | DT_CENTER/* | DT_VCENTER*/);
//手动计算居中
csText = L"A Single Line2";
mydc.GetTextExtent(csText, csText.GetLength(), &sizeText);
rcText = CRect(ptLeftTop.x + (ptRightBottom.x-ptLeftTop.x-sizeText.cx)/2,
20+ptLeftTop.y,
ptLeftTop.x + (ptRightBottom.x-ptLeftTop.x-sizeText.cx)/2 + sizeText.cx,
20+ptLeftTop.y+ sizeText.cy);
mydc.DrawText(csText, csText.GetLength(), rcText, DT_SINGLELINE | DT_LEFT | DT_TOP);
//换种方法计算居中
csText = L"A Single Line3";
CRect rcCalc;
mydc.DrawText(csText, csText.GetLength(), rcCalc, DT_CALCRECT | DT_SINGLELINE);//也可计算多行
rcText = CRect(ptLeftTop.x + (ptRightBottom.x-ptLeftTop.x-rcCalc.Width())/2,
40+ptLeftTop.y,
ptLeftTop.x + (ptRightBottom.x-ptLeftTop.x-rcCalc.Width())/2 + sizeText.cx,
40+ptLeftTop.y+ rcCalc.Height());
mydc.DrawText(csText, csText.GetLength(), rcText, DT_SINGLELINE | DT_LEFT | DT_TOP);
//消除背景
mydc.SetBkMode(TRANSPARENT);
//多行文字,识别\n,使用DT_EXPANDTABS扩展\t
rcText = CRect(CPoint(ptLeftTop.x, ptLeftTop.y+60), ptRightBottom);
csText = L"Line 1\nLine2\nLine3\tLine3";
mydc.DrawText(csText, csText.GetLength(), rcText, DT_LEFT | DT_TOP | DT_EXPANDTABS);
//自动截断-DT_END_ELLIPSIS-多行时只截断最后一行
rcText = CRect(CPoint(ptLeftTop.x, ptLeftTop.y+120), ptRightBottom);
csText = L"1A Very Long Long Long Long Long Long Long Long Long Long Single Line\n"
L"1A Very Long Long Long Long Long Long Long Long Long Long Single Line";
mydc.DrawText(csText, csText.GetLength(), rcText, DT_LEFT | DT_TOP | DT_END_ELLIPSIS);
//自动截断-DT_PATH_ELLIPSIS-多行时只截断路径最后一行
rcText = CRect(CPoint(ptLeftTop.x, ptLeftTop.y+160), ptRightBottom);
csText = L"C:\\testtesttest\\testtesttest\\testtesttest\\testtesttest\\testtesttest\\testtesttest\\testtesttest\n"
L"\\testtesttest\\testtesttest\\testtesttest\\testtesttest\\testtesttest\\testtesttest\\testtesttest";
mydc.DrawText(csText, csText.GetLength(), rcText, DT_LEFT | DT_TOP | DT_PATH_ELLIPSIS);
//自动截断-DT_WORD_ELLIPSIS-多行时截断每一行
rcText = CRect(CPoint(ptLeftTop.x, ptLeftTop.y+200), ptRightBottom);
csText = L"2A Very Long Long Long Long Long Long Long Long Long Long Single Line\n"
L"2A Very Long Long Long Long Long Long Long Long Long Long Single Line";
mydc.DrawText(csText, csText.GetLength(), rcText, DT_LEFT | DT_TOP | DT_WORD_ELLIPSIS);
//设置前后景颜色
mydc.SetBkMode(OPAQUE);
mydc.SetBkColor(RGB(0,0,255));
mydc.SetTextColor(RGB(0,255,0));
//自动换行
rcText = CRect(CPoint(ptLeftTop.x, ptLeftTop.y+240), ptRightBottom);
csText = L"1A Very Long Long Long Long Long Long Long Long Long Long Single Line to change line";
mydc.DrawText(csText, csText.GetLength(), rcText, DT_LEFT | DT_TOP);
rcText = CRect(CPoint(ptLeftTop.x, ptLeftTop.y+260), ptRightBottom);
csText = L"2A Very Long Long Long Long Long Long Long Long Long Long Single Line to change line";
mydc.DrawText(csText, csText.GetLength(), rcText, DT_LEFT | DT_TOP | DT_WORDBREAK);
mydc.RestoreDC(-1);
}
对应效果如下:
可以看到:
1.SetBkMode、SetBkColor、SetTextColor对于DrawTextOut仍然可用
2.可以使用格式控制符控制在指定区域居中显示,也可以计算输出文本的区域大小从而计算文本位置来达到自定义居中控制。这里使用两种方法计算文本区域大小,前一种调用dc.GetTextExtent实际上是调用SDK的GetTextExtentPoint32函数,后一种传入DT_CALCRECT参数先计算文本区域大小,这里推荐使用后一种。
3.DrawText默认是识别\n的,为了识别\t,传入参数DT_EXPANDTABS即可。
4.DrawText支持自动截断,注意以下几个参数的不同。
DT_END_ELLIPSIS -多行时只截断最后一行
DT_PATH_ELLIPSIS -多行时只截断路径最后一行
DT_WORD_ELLIPSIS -多行时截断每一行
5.使用参数DT_WORDBREAK支持自动换行
4.路径的使用
使用路径可以完成复杂的自定义绘制,类似PS中的路径功能,如下:
- void DrawArea4(CDCHandle mydc, POINT ptLeftTop, POINT ptRightBottom)
- {
- mydc.SaveDC();
- CFont fontNew;
- fontNew.CreatePointFont(1200, L"Tohama");//20px
- mydc.SelectFont(fontNew);
- //创建和添加当前剪切路径
- CRect rcText(ptLeftTop, ptRightBottom);
- mydc.BeginPath();
- mydc.TextOut(ptLeftTop.x, ptLeftTop.y, L"路径");
- mydc.EndPath();
- mydc.SelectClipPath(RGN_XOR);
- mydc.DrawText(L"路径", -1, rcText, DT_SINGLELINE|DT_CALCRECT);
- int nStep = 2;
- for (int i=rcText.top; i<=rcText.bottom; i+= nStep)
- {
- mydc.MoveTo(rcText.left, i);
- mydc.LineTo(rcText.right, i);
- }
- mydc.RestoreDC(-1);
- }
void DrawArea4(CDCHandle mydc, POINT ptLeftTop, POINT ptRightBottom)
{
mydc.SaveDC();
CFont fontNew;
fontNew.CreatePointFont(1200, L"Tohama");//20px
mydc.SelectFont(fontNew);
//创建和添加当前剪切路径
CRect rcText(ptLeftTop, ptRightBottom);
mydc.BeginPath();
mydc.TextOut(ptLeftTop.x, ptLeftTop.y, L"路径");
mydc.EndPath();
mydc.SelectClipPath(RGN_XOR);
mydc.DrawText(L"路径", -1, rcText, DT_SINGLELINE|DT_CALCRECT);
int nStep = 2;
for (int i=rcText.top; i<=rcText.bottom; i+= nStep)
{
mydc.MoveTo(rcText.left, i);
mydc.LineTo(rcText.right, i);
}
mydc.RestoreDC(-1);
}
效果如下:
使用路径的步骤基本是固定的,在BeginPath和EndPath之间绘制GDI元素,然后SelectClipPath选择即可作为当前剪切区域使用。
5.区域的使用
区域特别是剪切区域在界面绘制中用的非常多,如下:
- void DrawArea5(CDCHandle mydc, POINT ptLeftTop, POINT ptRightBottom)
- {
- mydc.SaveDC();
- CRect rcDraw(ptLeftTop, ptRightBottom);
- rcDraw.DeflateRect(30, 30);
- //选择剪切区域
- CRgn rgnDraw;
- rgnDraw.CreateRectRgnIndirect(rcDraw);
- mydc.SelectClipRgn(rgnDraw, RGN_AND);
- //排除部分绘制区域
- CRect rcExclude(ptLeftTop, ptRightBottom);
- rcExclude.DeflateRect(60,60);
- mydc.ExcludeClipRect(rcExclude);
- int nSteps = 5;
- for (int i=ptLeftTop.y; i<ptRightBottom.y; i+=nSteps)
- {
- mydc.MoveTo(ptLeftTop.x, i);
- mydc.LineTo(ptRightBottom.x, i);
- }
- mydc.RestoreDC(-1);
- }
void DrawArea5(CDCHandle mydc, POINT ptLeftTop, POINT ptRightBottom)
{
mydc.SaveDC();
CRect rcDraw(ptLeftTop, ptRightBottom);
rcDraw.DeflateRect(30, 30);
//选择剪切区域
CRgn rgnDraw;
rgnDraw.CreateRectRgnIndirect(rcDraw);
mydc.SelectClipRgn(rgnDraw, RGN_AND);
//排除部分绘制区域
CRect rcExclude(ptLeftTop, ptRightBottom);
rcExclude.DeflateRect(60,60);
mydc.ExcludeClipRect(rcExclude);
int nSteps = 5;
for (int i=ptLeftTop.y; i<ptRightBottom.y; i+=nSteps)
{
mydc.MoveTo(ptLeftTop.x, i);
mydc.LineTo(ptRightBottom.x, i);
}
mydc.RestoreDC(-1);
}
效果如下:
这里剪切区域SelectClipRgn和排除绘制区域ExcludeClipRect都是常用的技巧,前者保证只在指定区域绘制,后者保证指定的区域不重复绘制,都可以达到优化绘制效率的效果。
6.窗口和视口区域的调整
这里还是按照Petzold的定义,逻辑坐标系是视口(View),设备坐标系是窗口(Window),默认的映射关系是MM_TEXT,一般不变,如果需要深入了解的话,建议去看下《Windows程序设计》,这里只演示最常用的原点变换,如下:
- void DrawArea6(CDCHandle mydc, POINT ptLeftTop, POINT ptRightBottom)
- {
- mydc.SaveDC();
- mydc.SetViewportOrg(ptLeftTop.x+50, ptLeftTop.y+50);
- mydc.TextOut(0, 0, L"Text 1");
- mydc.SetViewportOrg(0, 0);
- mydc.SetWindowOrg(-ptLeftTop.x-50, -ptLeftTop.y-50);
- mydc.TextOut(0, 20, L"Text 2");
- mydc.RestoreDC(-1);
- }
void DrawArea6(CDCHandle mydc, POINT ptLeftTop, POINT ptRightBottom)
{
mydc.SaveDC();
mydc.SetViewportOrg(ptLeftTop.x+50, ptLeftTop.y+50);
mydc.TextOut(0, 0, L"Text 1");
mydc.SetViewportOrg(0, 0);
mydc.SetWindowOrg(-ptLeftTop.x-50, -ptLeftTop.y-50);
mydc.TextOut(0, 20, L"Text 2");
mydc.RestoreDC(-1);
}
显示如下:
一般建议使用SetViewportOrg,这逻辑上比较直观。
本文完整演示代码下载链接
原创,转载请注明来自http://blog.csdn.net/wenzhou1219