由于用mfc经常需要在界面上进行一些绘制输出,所以用windows GDI比较多,但是用windowsGDI 绘图比较麻烦,刚开始学的时候还经常弄不清一堆DC, Object, HANDLE到底是干啥的。后来就琢磨清楚了,但为了用起来更方便,就弄个类,这样画什么就直接调对应的函数和常用的控制参数,比如大小位置颜色,而不需要自己去操控上下文、绘制画笔画刷等等麻烦事,而且一个函数完成一个简单图形的绘制,后面又学了gdi+,就重写了部分函数实现,用了更简单的方式完成。
为了避免闪烁,这里用了双缓存的原理,其实就是相当于在内存里开辟空间画完后在显示在屏幕上。至于闪烁的缘由,在我的另一篇博文里面有浅浅的探究:http://www.straka.cn/blog/flickering-in-mfc/
这里也不得不指明这么做是有损效率的,因为期间会重复创建和销毁画笔画刷等对象。但对于多数应用场景,这个损失是可以接受的。
要使用GDI+(Graphics device interface),要做些铺垫工作,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
ULONG_PTR
m_gdiplusToken
;
BOOL
CGDIDemoApp
::
InitInstance
(
)
{
.
.
.
CWinApp
::
InitInstance
(
)
;
Gdiplus
::
GdiplusStartupInput
gdiplusStartupInput
;
Gdiplus
::
GdiplusStartup
(
&m_gdiplusToken
,
&gdiplusStartupInput
,
NULL
)
;
.
.
.
}
int
CGDIDemoApp
::
ExitInstance
(
)
{
// TODO: 在此添加专用代码和/或调用基类
Gdiplus
::
GdiplusShutdown
(
m_gdiplusToken
)
;
return
CWinApp
::
ExitInstance
(
)
;
}
|
一般为了方便在应用程序的实例初始化阶段就可以把gdiplus一起初始化了,然后实现ExitInstance虚函数,在其中释放使用gdiplus所占的资源。
封装的类:
class CDrawMethod
{
HWND m_dlgHWND; //the handle of dialog window
CDC *m_pDC; //the device context of dialog
CDC m_dcMem; //the memory dc, to contain all the drawing than flush onto screen
CBitmap m_bmpMem; //bitmap bind to memory dc--m_dcMem
Gdiplus::Graphics* m_pGraph; //point to the object of Gdiplus
CDrawMethod(HWND hwnd, CDC *pDC);
~CDrawMethod();
//call this func to init the member before any draw method
int BeginDraw();
//after all operate has been executed(drawn on memory DC), call this to draw on screen
void DrawOnScreen();
//call this func after all operate has done, free or release the resources, in case of memory leak
int EndDraw();
//save picture in memory DC to path as jpg file
int SaveMemDCAsJPG(char* strPath);
int SaveMemDCAsBMP(char* strPath);
//return the memory bitmap member
CBitmap *GetMemBitmap();
//return the memory DC member
CDC *GetMemDC();
public:
CRect m_rtClient;
};
主要的成员和函数都在上述代码中列出,使用的话每次新建类实例,传入对话框句柄HANDLE和设备上下文DC,然后调用BeginDraw()初始化,等全部函数执行完毕,再调用DrawOnScreen()画到屏幕上,最后EndDraw()释放资源。如果需要更进一步的操作可以用GetMemBitmap() 和 GetMemDC()方法取得内存位图和画布进一步操作。但是注意这里的指针是指向成员的,用完不可释放,类内部管理。如果画完不需要显示在显示器上也可以用SaveMemDCAsJPG 和 SaveMemDCAsBMP 函数输出到文件。
而具体承担绘图任务的函数是自定义可以添加的:
//putout the text follow the params onto the dc
TEXTMETRIC PutOutTextA(char* lpsz, int X, int Y, unsigned long lColor, int nFontHeight, int nFontWidth = FW_NORMAL, EShadowType eShadow = NONE, unsigned long lShadowColor = 0, LPCWSTR lpFont = TEXT("微软雅黑"));
TEXTMETRIC PutOutTextW(char* lpsz, int X, int Y, unsigned long lColor, int nFontHeight, int nFontWidth = FW_NORMAL, EShadowType eShadow = NONE, unsigned long lShadowColor = 0, LPCWSTR lpFont = TEXT("微软雅黑"));
//draw rectangle frame
void DrawRectFrame(CRect rect,int width, unsigned long lColor,byte alpha=255);
//draw shadow of rectangle
//@param coeff: control the color gradiant rate
void DrawRectShadow(CRect rect, unsigned long lColor, byte alpha, int width, float coeff=0.8);
//fill inside of rectangle
void FillRect(CRect rect, unsigned long lColor, byte alpha);
void DrawEllipseFrame(CRect rect, int width, unsigned long lColor, byte alpha=255);
void DrawEllipseShadow(CRect rect, unsigned long lColor, byte alpha, int width, float coeff=0.8);
void FillEllipse(CRect rect, unsigned long lColor, byte alpha);
void DrawPolygonFrame(Gdiplus::Point* arrPoints, int ctn, int width, unsigned long lColor, byte alpha=255);
void FillPolygon(Gdiplus::Point* arrPoints, int ctn, unsigned long lColor, byte alpha);
//draw round corner rectangle
void DrawRoundRect(CRect rect, float arcSize, float lineWidth, unsigned long lColor, byte alpha, bool fillPath = false, unsigned long lColorFill = 0, byte alphaFill = 0);
void DrawRoundRect(float x, float y, float Width, float Height, float arcSize, float lineWidth, unsigned long lColor, byte alpha, bool fillPath = false, unsigned long lColorFill = 0, byte alphaFill = 0);
//method to draw cubic of area located by points array
void FillCubicSurface(Gdiplus::Point* arrPoints, int ctn, unsigned long lColor, byte alpha = 155,unsigned long frameColor = 0);
//draw image on path by method 1 to location by rect
bool DrawImage(char* pStr, CRect rect);
//draw png picture at path input to location by rect
void DrawPNG(WCHAR* strPath, CRect rect);
我就把常用的一些添加了进来,画矩形、椭圆形、多边形框、及填充矩形、椭圆形、多边形内部、画矩形、椭圆形阴影,画圆角矩形、画立方体,绘制图片,以及输出文字。
其中画矩形阴影实现就是画了尺寸稍大的多个矩形,这种比较简单,当然也有别的处理方法,根据大家需要另外添加函数即可。
使用示例:
在对话框OnPaint函数中添加代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
if
(
IsIconic
(
)
)
{
.
.
.
}
else
{
//CDialogEx::OnPaint();
CPaintDC
dc
(
this
)
;
// 用于绘制的设备上下文
CDrawMethod
drawMethod
(
m_hWnd
,
&dc
)
;
drawMethod
.
BeginDraw
(
)
;
.
.
.
//draw objects here
drawMethod
.
DrawOnScreen
(
)
;
drawMethod
.
EndDraw
(
)
;
}
|
在第11行处添加画图代码,比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
drawMethod
.
DrawRectFrame
(
rect1
,
1
,
RGB
(
0
,
255
,
0
)
,
150
)
;
drawMethod
.
DrawRectShadow
(
rect1
,
RGB
(
200
,
255
,
255
)
,
200
,
5
)
;
drawMethod
.
FillRect
(
rect1
,
RGB
(
255
,
0
,
0
)
,
150
)
;
drawMethod
.
DrawEllipseFrame
(
rect2
,
1
,
RGB
(
100
,
20
,
155
)
,
200
)
;
drawMethod
.
DrawEllipseShadow
(
rect2
,
RGB
(
0
,
255
,
255
)
,
200
,
5
,
0.7
)
;
drawMethod
.
FillEllipse
(
rect2
,
RGB
(
255
,
255
,
0
)
,
190
)
;
Gdiplus
::
Point
pts
[
8
]
;
pts
[
0
]
.
X
=
170
;
pts
[
0
]
.
Y
=
250
;
pts
[
1
]
.
X
=
280
;
pts
[
1
]
.
Y
=
pts
[
0
]
.
Y
;
pts
[
2
]
.
X
=
400
;
pts
[
2
]
.
Y
=
100
;
pts
[
3
]
.
X
=
330
;
pts
[
3
]
.
Y
=
pts
[
2
]
.
Y
;
pts
[
4
]
.
X
=
pts
[
0
]
.
X
;
pts
[
4
]
.
Y
=
210
;
pts
[
5
]
.
X
=
pts
[
1
]
.
X
;
pts
[
5
]
.
Y
=
pts
[
4
]
.
Y
;
pts
[
6
]
.
X
=
pts
[
2
]
.
X
;
pts
[
6
]
.
Y
=
75
;
pts
[
7
]
.
X
=
pts
[
3
]
.
X
;
pts
[
7
]
.
Y
=
pts
[
6
]
.
Y
;
drawMethod
.
FillCubicSurface
(
pts
,
8
,
RGB
(
0
,
200
,
50
)
,
200
,
RGB
(
0
,
255
,
0
)
)
;
|
上述代码绘图效果:
Demo源码等更多信息见原博客:
http://www.straka.cn/blog/encapsulate-gdi-draw-method/