第二讲 统计报表的背景,图形和文字的绘制
WM_PAINT 是在窗口被掩盖或重绘的时候调用,它负责的是窗口客户区的绘制。
要绘制任何东西,必须要先获得HDC,
那么在WM_PAINT 这里可以通过调用BeginPaint()函数来返回与窗口关联的HDC,获取了HDC之后,就可以调用GDI或GDI+的方法或类来绘制图像图形了。
在所有绘制图像图形的工作完成之后,在退出WM_PAINT消息之前,需要调用EndPaint(hWnd,&ps);来对绘制的无效区域进行清空,如果不调用这个EndPaint(),就会导致程序不断重复调用WM_PAINT消息,而形成死循环。
窗口背景的绘制,需要用一张图片绘制到窗口的背景,生命周期和程序是一样的,所以要定义全局变量。
第一步:定义一个全局变量
HBITMAP hbmpback = NULL ; //初始化为NULL
接下来要对图像进行加载,对位图进行加载有两个API函数可以完成。一个是LoadBitmap,另外一个是LoadImage.
LoadBitmap函数进行图片加载
HBITMAP WINAPI LoadBitmapW(HINSTANCE hInstance,LPCWSTR lpBitmapName);
函数的作用是从应用程序工程资源中读取位图数据。
第一个参数HINSTANCE,它是资源文件所在的模块的实例句柄
第二个参数BitmapName是位图在资源中的ID名称。
所以要使用位图,第一步要先把位图导入到资源里面来,导入完之后,在InitInstance里面来初始化实例。
LoadImage函数进行图片加载
LoadImage这个函数可以加载位图,图标,光标多种图像数据。
从资源加载
hbmpBack = (HBITMAP)::LoadImage
(hInst, MAKEINTRESOURCE(IDB_BACK),IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION | LR_LOADMAP3DCOLORS);
LoadImage 既可以从文件中直接加载图片,也可以从资源中通过ID来加载图片,而LoadBitmap只能通过资源的ID来加载图片,所以LoadImage加载的图片格式可以是多种多样的。
从文件的路径加载
LoadImage()函数从文件路径加载
hbmpBack = (HBITMAP)::LoadImage
(NULL,_T(“.\\1.bmp”),IMAGE_BITMAP,0,0,LR_LOADFROMFILE | LR_DEFAULTSIZE | LR_CREATEDIBSECTION);
路径的字符串都是要双斜杠 \\ , 此外, 标志位上一定要写上LOADFROMFILE 就是说从文件中加载图片的意思
LR_DEFAULTSIZE 表示不去调整位图的大小,加载它默认的大小,
LR_CREATEDIBSECTION 就是创建DIBSECTION 这么一个设备无关的位图进来。
当用完位图资源之后就要及时的对其进行销毁,否则它占用的内存是非常大的。
需要在_tWinMain()函数的末尾进行位图的销毁,那么位图怎么销毁呢? 应该调用DeleteObject()函数来销毁。
那么现在已经把位图加载进来了, 接下来就要把位图画到窗口上去,那么怎么做?
如何将图片绘制到DC上去?
DC好比是一台打印机,位图好比是一张A4纸,电脑要打印东西的话,要通过打印机来发送指令,而结果是呈现在A4纸上面,而A4纸本身是没有任何绘图功能的,它只有数据而已,当把A4纸打满之后,可以再换一张A4纸,继续打印。
那么如何将图片绘制到DC上? 要把背景图片绘制到DC上面去的话,就相当于把A4纸塞入到打印机里面一样,但这里我们还不能直接把位图选进到窗口的目标DC上,位图需要绘制,肯定是要先选到DC上来才行,但是目前的DC是在WM_PAITN事件里BeginPaint返回的那个HDC,它是一个目标DC,这个目标DC选进来也是没有用的,它是跟我们显示器关联的,那么应该怎么把位图绘制到DC上去?我们又知道这个位图,又必须被先选进到一个DC上来,这里就需要创建一个和目标DC兼容的内存DC,然后把这个位图选到这个内存DC里面来,接下来再把内存DC里面的数据再拷贝到目标DC中来,那如果已经把图片选进入内存DC中来了,那么怎么又去进行拷贝呢?用哪个函数去拷贝到我们的目标DC上去?GDI函数库中有这么一个函数,叫BitBlt()。 BitBlt函数的功能是这样的:我们创建的内存DC,叫做源DC,那么那个WM_PAINT返回的窗口的那个HDC叫做目标DC,它是将内存里面的那个源DC上的指定区域的内容拷贝到目标DC上来,这个指定的区域 可以不是整张图的拷贝,可以是DC的某一部分的拷贝,需要注意的是BitBlt是原样拷贝,不支持拉伸操作。不能进行放大或缩小的操作。
BitBlt函数绘制位图,其原型如下:
BOOL WINAPI BitBlt(
HDC hdc, //目标DC的句柄
int x ,int y , int cx, int cy, //目标区域
HDC hdcSrc, // 源DC的句柄
int x1, int y1, // 源区域的左上角坐标
DWORD rop // 操作标志,一般是 SRCCOPY , 即拷贝的意思
);
下面来总结一下绘制位图的步骤
1. 获取目标DC句柄
2. 创建与目标DC兼容的内存DC
3. 将位图句柄选入到内存DC中
4. 使用BitBlt函数将内存DC中的位图拷贝到目标DC上来
5. 将位图句柄选出内存DC
6. 销毁内存DC
下面新建一个GdiDemo的win32 Application(默认配置,直接Finished即可)
第一步定义一个来接收位图的位图句柄
首先要加载一张图片来绘制窗口的背景,那么这张图片的生命周期和程序是一样的,所以首先在GdiDemo项目目录下的GdiDemo文件下新建一个res文件夹,然后把要加载的那张背景图放到这个res文件夹下,然后回到vs2010下的资源视图中右键单击GdiDemo.rc选择添加Bitmap—>import 选择刚才那张图片,添加进来, 然后选择刚添加进来的这张图片,选择属性视图窗口将其ID更改为IDB_BACK 保存。 之后就要来定义一个全局变量的位图句柄,并初始化为NULL。所以先在GdiDemo.cpp文件中的
这一”//Global Variables” 注释下面 定义一个全局位图句柄
HBITMAP hbmpBack = NULL;
第二步加载位图
定义了全局变量之后,那就要想了,这张图片是窗口创建之后加载呢还是在窗口创建之前就要加载? 作为背景图那么肯定是在窗口创建之前就要加载这张图,也就是说要在CreateWindow之前来加载这张图, 会到vs工程中,我们刚新建的GdiDemo 项目中,其实微软已经为我们完成了一个窗口创建的整个过程即三大部分 初始化窗口,创建窗口,消息处理,这三大部分又可以分为窗口类的声明,窗口的初始化,注册窗口,创建窗口,消息处理这么5个部分。既然是在创建窗口之前就要加载图片,那么肯定是在窗口初始化的时候来加载背景图片, 而微软为窗口初始化定义的函数是BOOL InitInstance(HINSTANCE hInstance,int nCmdShow); 既然如此那么我们就在这个窗口初始化函数中来加载背景图片
加载背景图片有两个函数可以做到,我们这里用LoadBitmapW()来完成图片的加载,LoadBitmap()执行成功就会返回一个位图的句柄BITMAP ,否则会返回NULL,我们把返回的位图句柄赋值给我们之前声明的位图句柄hbmpback 我们在InitInstance()函数中写下如下代码:
hbmpBack = ::LoadBitmapW(hInstance,(MAKEINTRESOURCE(IDB_BACK)));
LoadBitmap函数之前已经分析过了 它的第一个参数是资源文件所在的模块的实例句柄,那么这里就是目前窗口的实例句柄,第二参数就是要加载的位图资源的ID号 ,但是这里用了(MAKEINTRESOURCE(IDB_BACK)),这个MAKEINTRESOURCE 是一个宏,刚才看到的IDB_BACK它实际上是一个十进制的数字的宏定义,但是LoadBitmap是不认识这样的数字的,所以必须把它转换成字符串,MAKEINTRESOURCE就是生成整型的资源号的字符串的意思。 现在位图既然已经加载进来了,但是当位图用完之后就要理解销毁,否则它会占用非常大的内存,那么销毁是在 _tWinMain()函数的return 语句之前 通过调用DeleteObject()函数来销毁, 代码如下:
::DeleteObject(hbmpBack);
第三步 获取目标DC句柄
现在加载了位图那么就需要绘制到目标DC上,即将位图绘制到窗口背景上,而windows程序都是基于消息驱动的, 任何一个动作都是通过消息来驱动的,自然绘制也是通过消息事件来驱动的,那么windows程序是通过WM_PAINT事件来重绘窗口的,因为它负责窗口客户区的绘制。 那么在WM_PAINT中可以通过调用BeginPaint()函数来获得当前窗口的目标DC,我们在消息过程函数WndProc中找到 WM_PAINT事件分支, 这里可以看到它已经为我们获取了目标DC了 就是hdc。
第四步就是创建与目标DC兼容的内存DC
这里可以调用CreateCompatibleDC(hdc)来创建与目标DC兼容的内存DC,并将返回的内存DC句柄赋值给我们声明的hMemDC ,代码如下:
HDC hMemDC = ::CreateCompatibleDC(hdc);
第五步就是要将位图句柄选入到内存DC中来
通过调用SelectObject()函数把位图句柄选入到内存DC中来, SelectObjet()函数将位图句柄选入到内存DC中的同时也会返回一个老的位图句柄, 这里我们用hOldBmp来保存。
HBITMAP hOldBmp = (HBITMAP)::SelectObject(hMemDC,hbmpBack);
第六步使用BitBlt函数将内存DC中的位图拷贝到目标DC上来
我们可以通过BitBlt来完成内存DC内容到目标DC上的拷贝,但是在拷贝之前我们要知道目标DC上要绘制的区域的大小,这里目标DC上要绘制的区域就是指的窗口的背景,即窗口客户区的大小,但是这里我们用的是原样拷贝,所以这个区域的大小就是加载到源DC中位图的大小,但是这个位图的大小我们目前还没有得到,下面通过GetObject来得到位图的信息的指针,代码如下:
BITMAP bmp;
::GetObjectA(hbmpBack,sizeof(BITMAP),&bmp);
通过BitBlt将源DC(即内存DC)上的内存拷贝到目标DC上去
::BitBlt(hdc,0,0,bmp.bmWidth,
bmp.bmHeight,hMemDC,0,0,SRCCOPY);
第七步是把位图句柄选出内存DC ,
::SelectObject(hMemDc,hOldBmp);
第八步销毁内存DC
这里我们之前创建的位图句柄hOldBmp也是没有用了所以也一起销毁
::DeleteObject(hOldBmp);
::DeleteObject(hMemDC);
F5 运行, 查看运行结果发现背景图并没有全部铺满整个客户区,那怎么来解决这个问题?有两种方式,可以解决这个问题,一种是平铺的方式,还有一种是拉伸的方式。下面我们先采用拉伸的方式来解决此问题,最后再来将平铺的方式。
要把位图充满整个客户区,首先要获得客户区的大小,否则我们怎么充满呢?
可以通过调用GetClientRect来获取客户区的大小,原型如下:
BOOL WINAPI GetClientRect(HWND hWnd,LPRECT lpRect);
第一个参数传入窗口的句柄
第二个参数传入接收返回值Rect的指针。
那么获得客户区的大小,应该是对当前的客户区进行操作,所以它应该要有一个窗口的句柄。函数执行成功则返回TRUE,那么现在客户区的大小我们也知道了,位图的大小也知道了,要用到一个函数StretchBlt即拉伸拷贝的意思。StretchBlt拉伸函数原型:
BOOL WINAPI StretchBlt(
HDC hdcDest, // 目标DC的句柄
int xDest,int yDest, int wDest, int hDest, // 目标DC的区域
HDC hdcSrc, // 源DC的句柄
int xSrc, int ySrc, int wSrc, int hSrc, // 源DC的区域
DWORD rop //操作标志,一般为SRCCOPY, 意思为拷贝
);
StrechBlt 和BitBlt 功能上基本一样,区别在于BitBlt是不能拉伸图片,不能拉伸目标DC的。源DC 和 目标DC的长度和宽度可以不一样,所以此时源DC中的长度和高度需要指定。
那么现在可以把之前的BitBlt函数换成StretchBlt 但是在换之前要先计算出客户区的大小
RECT rcClients;
::GetClientRect(hWnd, &rcClients);
然后将BitBlt(hdc,0,0,bmp.bmWidth,bmp.bmHeight,hMemDC,0,0,SRCCOPY); 替换成:
StretchBlt(hdc,0,0,rcClients.right-rcClients.left,rcClients.bottom-rcClients.top,hMemDC,0,0,bmp.bmWidth,bmp.bmHeight,SRCCOPY);
F5 运行就可以看到背景图铺满了整个客户区了。
那么上面我们就完成了背景的绘制了,接下来是文字的绘制
在GDI中要绘制文字是通过DrawText和TextOut这两个函数来完成的。
DrawText函数原型:
int WINAPI DrawText(
HDC hdc, //DC的句柄
LPCWSTR lpchText, //要绘制的文本
int cchText, //要绘制的文本的长度
LPRECT lprc, //要绘制的目标区域
UINT format // 绘制标志,DT_SINGLELINE等。
);
第一个参数就是我们要将这个文本绘制到哪个画布上,而这个画布就是HDC ,即上面的DC句柄。
DrawText一般有两种用法,1.绘制文本,2.计算绘制的文本的长度和宽度。就是我这个文本要画出来的话到底要占用多大的区域,那么这种功能用法是不会画到DC上去的。
第一种用法, 绘制文本
我们首先要绘制一个标题,标题是横向居中,纵向居顶。 代码如下:
//绘制标题,这里的L意思指定了我的这个字符串是宽字符的
//注意这里用到wstring 则要包含头文件 #include<string>才行,否则提示wstring 没有定义
wstring str = L”网站流量统计报表”;
DrawText(hdc,str.c_str(),(int)str.length(),&rcClient,DT_CENTER | DT_TOP | DT_SINGLELINE);
此时能绘制出文本,但是文本格式不对,不美观,怎么办?那么就需要自己创建字体来美化了。 绘制字体的两个函数分别是CreateFont 和 CreateFontIndirect() ,尽管是两个不同的函数,但是意思是一样的。CreateFontIndirect()是将所有的字体信息放在一个LOGFONT结构中,用这个结构来作为参数来传入所有的数据,即它只传入一个参数,而CreateFont则是把所有参数都传入进来。通过CreateFontIndirect来创建字体要分两种情况:
#ifdef UNICODE
#define CreateFontIndirect CreateFontIndirectW
#else
#define CreateFontIndirect CreateFontIndirectA
#endif //!UNICODE
CreateFontIndirectA 代表在多个字节下使用,而CreateFontIndirectW则是代表在Unicode字节即宽字节编码下使用
那么下面对字体的逻辑结构的选择也是一样的(在宽字节下是LOGFONTW,而在多字节下是LOGFONTA):
#ifdef UNICODE
typedef LOGFONTW LOGFONT;
#else
typedef LOGFONTA LOGFONT;
#endif // UNICODE
HFONT WINAPI CreateFontIndirect(_in CONST LOGFONT *lplf);
LOGFONTA字体结构体
typedef struct tagLOGFONTA
{
LONG lfHeight; //指定需要的字体的高度
LONG lfWidth; //指定需要的字体的宽度
LONG lfEscapement; //指定需要的字体中,每个字符串的底线相对于水平线的角度
LONG lfOrientation; //指定每个字符的基线相对于页底部的角度
LONG lfWeight; //在0~1000(如400为正常字体,700为黑体)的范围内,指定字体所需要的深浅程度,0为默认值。
BYTE lfItalic; //如果要求的字体是倾斜的,则此参数为TRUE
BYTE IfUnderline; //如果要求的字体要加下划线,则此参数为TRUE
BYTE lfStrikeOut; //如果字体要加中划线,则此参数TRUE
BYTE lfCharSet; //指定字体所要求的字符集
BYTE lfOutPrecision; //指定所要求的输出精度。输出精度定义了输出字体必须如何紧密匹配所要求的字体的高度、宽度、字符转角、倾斜度和间距
BYTE lfClipPrecision; //指定所需要的剪贴精度。剪贴精度定义了如何对落在剪贴区域外部的字符进行剪贴
BYTE lfQuality; //指定字体所要求的输出质量
BYTE lfPitchAndFamily; //指定字体的间距和字体系列
CHAR IfFaceName[LF_FACESIZE];//指向一个NULL为终止符的字符串。该串指定字体的名字
}LOGFONTA,*PLOGFONTA;
绘制GDI文字的颜色有两个函数,一个是GetTextColor用来获得当前DC的文本颜色,另外一个是SetTextColor是设置当前DC的文本颜色,函数原型如下:
COLORREF WINAPI SetTextColor(HDC hdc, COLORREF color);
在windows里用colorref来定义颜色
下面来说说背景模式
通过SetBKMode设置DC的背景模式,相对应的GetBKMode是用来获取背景模式的。
int SetBKMode(HDC hdc, int mode);
参数mode可以有TRANSPARENT值(透明)和OPAQUE值(不透明,默认值)。
返回值是hdc在设置之前的背景模式。
那么坐标系中的字体有横向和纵向之分,这个可以通过采用库存字体来做
库存字体,可以通过GetStockObject函数获取。
GetStockObject(SYSTEM_FONT)获取默认的系统字体的句柄。在缺省情况下,系统使用该字体绘制菜单,文本。除SYSTEM_FONT外还可以设置如下参数:
OEM_FIXED_FONT: 原始设备制造商(OEM)相关固定间距(等宽)的字体
ANSI_FIXED_FONT:在Windows中为固定间距(等宽)的系统字体。
ANSI_VAR_FONT:在Windows中为变间距的系统字体。
DEVICE_DEFAULT_FONT:在windowsNT中为设备相关字体。
SYSTEM_FIXED_FONT:固定间距(等宽)的系统字体,该字体仅提供给兼容16位的Windows版本。
用TextOut绘制文字,原型如下:
TextOut(HDC hdc, int nXStart, int nYStart,LPCTSTR lpString,int cbString)
参数hdc就是要绘制文字的DC.
参数nXStart就是文字要绘制的起点位置的X坐标。
参数nYStart就是文字要绘制的起点位置的Y坐标。
参数lpString就是要绘制的文字的字符串指针。
参数cbString就是要绘制的文字的字符个数。
DrawText和TextOut都是用来绘制文本的,那么它们之间有什么区别呢?
相比两种绘制方式,发现DrawText是采用绘制区域跟绘制格式来控制绘制的位置,而TextOut是直接采用起始位置来控制绘制的位置,所以在自定义位置绘制时TextOut更灵活一些,而以布局式绘制时,DrawText更适合一些。此外DrawText还可以计算所绘制字符串的面积的大小,还可以以省略号代替超出区域的文本。
DrawText(HDC hDC,LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat);
lpString : 要绘制的字符串。
nCount:要绘制的字符个数,-1代表绘制以NULL结尾的字符串。
lpRect:要绘制的区域。
uFormat:要绘制的格式。可以是下列值的组合:
DT_BOTTOM, 文本底对齐
DT_CENTER, 文本水平居中显示。
DT_LEFT, 文本左对齐
DT_VEENTER, 文本垂直居中显示。
DT_RIGHT, 文本右对齐。
DT_SINGLELINE, 只指定单个行。回车和换行,均不打断原有行。
DT_TOP, 文本顶对齐
DT_WORDBREAK, 指定单词间的中断,如果字符串中的一个单词超出lpRect指向的矩形的右边界时,则函数将在单词之间自动切断正文行。
DT_CALCRECT, 确定矩形的宽度和高度,如果有多个正文行,DrawText则将使用由lpRect参数指定的矩形宽度,并且延伸矩形底部,直到能框住正文行的最后一行。如果只有一行正文,DrawText将修正此矩形的右边,以便能框住此行正文的最后一个字符。注意:带入此参数后,DrawText不绘制字符串。它只是用来计算要容纳所要绘制的文本的矩形的大小。
DT_END_ELLIPSIS, 如果字符串的长度超过矩形的范围,它会被截断并以省略号标识。
上面我们讲了两种绘制文本的函数 CreateFont 和CreateFontIndirect两个函数, 为了美化字体还讲了可以设置字体参数的字体逻辑结构LOGFONT,然后讲到设置文本颜色的函数SetTextColor,最后讲到了DrawText和TextOut绘制文本的区别,现在我们就来设置字体,美化之前用DrawText()绘制的文本。
首先要美化字体就要设置美化字体需要的什么参数,比方说字体大小,字体样式等,这些设置可以定义一个字体逻辑结构LOGFONT将各项具体参数传入,来设置字体的样式,代码如下:
LOFONT logfont; //先定义一个字体的逻辑结构
//然后将其内存清空
memset(&logfont,0,sizeof(LOGFONT));
//设置字体的宽度和高度
logfont.lfWidth = 20;
logfont.lfHeight = 40;
//接下来设置字体显示采用的字符集
logfont.lfCharSet = GB2312_CHARSET;
_tcscpy(logfont.lfFaceName,_T(“宋体”)); // _tcscpy() 同时支持unicode和宽字节
HFONT hFont = ::CreateFontIndirectW(&logfont); //通过CreateFontIndirect函数来创建字体
//并通过SelectObject来将新创建的字体选入到当前DC中,同时保存返回的老的字体句柄
HFONT hOldFont = (HFONT)::SelectObject(hdc,hFont);
//字体的大小,样式等设置好了之后,接下来就要设置绘制文本的颜色了。
//这里通过SetTextColor来给当前DC的绘制文本设置颜色,同时返回老的文本的颜色句柄
COLORREF clrOldText = ::SetTextColor(hdc, RGB(0,0,128));
文本颜色设置了之后,还需要设置一下背景显示模式 这里把背景显示模式设置为透明
::SetBKMode(hdc,TRANSPARENT); // 因为文本绘制的时候默认会自动绘制其背景,设置为TRANSPARENT之后,其背景透明
上面把文本的大小,样式,颜色等设置好了之后, 还要求文本是横向居中,纵向居顶显示,那么要满足这些要求,首先就要知道绘制客户区的大小, 可以通过GetClientRect()来获得,具体如下:
RECT rcClient;
::GetClientRect(hWnd,&rcClient);
wstring str = L”网站流量统计报表”;
DrawText(hdc,str.c_str(),(int)str.length(),&rcClient,DT_CENTER | DT_TOP | DT_SINGLELINE);
设置的字体用完之后,记得把老的字体还要进来
::SelectObject(hdc,hOldFont);
同时之前也是把文本颜色设置了,这里也要记得还原回来
::SetTextColor(hdc,clrOldText);
好现在可以把用完的hFont销毁了
::DeleteObject(hFont);
F5运行, 字体大小适中,横向居中,纵向居顶显示。
下面要来开始绘制图形
绘制图像之前先要了解什么是图像,什么是图形?图像就是所谓的 图片,位图等, 而图形则是所谓的点, 线,矩形,原形,饼图等。
首先来了解一下点的绘制,点可以采用SetPixel来绘制,SetPixel可以用来设置点的颜色,原型如下:
COLORREF SetPixel(HDC hDC,int x , int y , COLORREF crColor);
参数hDC就是所要绘制的点的DC
参数X就是所要绘制的点的坐标X.
参数Y就是所要绘制的点的坐标Y.
参数crColor就是所要设置的颜色。
返回值就是设置颜色之前,点(X,Y)的颜色。
比如设置点(100,200)的颜色为红色,代码如下:
SetPixel(hdc,100,200,RGB(255,0,0));
GetPixel可以获取指定点的颜色,原型如下:
COLORREF GetPixel(HDC, hDC,int x ,int y);
参数hDC就是所要获取的点的颜色的DC.
参数X就是所要获取的点的坐标X。
参数Y就是所要获取的点的坐标Y.
返回值就是点(X,Y)的颜色。
下面来看线的绘制,线的绘制,需要指定起点和终点。那么可以通过MoveToEx来设置线的绘制起点,原型如下:
MoveToEx(HDC hDC, int x , int y , LPPOINT lpPoint);
参数hDC就是所要设置起点位置的DC。
参数X就是所要设置起点位置的X坐标。
参数Y就是所要设置起点位置的Y坐标。
参数lpPoint就是返回旧的起点坐标,参数为NULL时,表示不返回旧的起点的坐标。
有了绘制起点之后,还需要一个画线的函数LineTo,才能完成线的绘制, LineTo原型如下:
LineTo(HDC hDC,int x , int y);
参数hDC就是所要绘制的直线的DC。
参数X就是所要绘制的直线的终点坐标X。
参数Y就是所要绘制的直线的终点坐标Y。
比如要绘制一条从(100,200)到(300,500)的直线,代码如下:
MoveToEx(hdc, 100,200,NULL);
LineTo(hdc,300,500);
矩形的绘制,绘制矩形可以用Rectangle函数, 原型如下:
Rectangle绘制矩形:
Rectangle(HDC hdc, int left ,int top ,int right, int bottom);
参数hdc为要绘制的矩形的DC。
参数left为要绘制的矩形的左边界的位置。
参数top为要绘制的矩形的上边界的位置。
参数right为要绘制的矩形的右边界的位置。
参数bottom为要绘制的矩形的底边界的位置。
那么矩形的绘制分为矩形框的绘制, 和矩形内部填充两部分, 要完成这两部分的工作就要选用画笔和画刷来绘制 , 画笔绘制边框,画刷负责内部的填充。
比如要绘制一个左上角坐标为(100,200),宽度高度分别为300,400的矩形边框,代码如下:
SelectObject(hdc, GetStockObject(NULL_BRUSH);
Rectangle(hdc, 100,200,100+300,200+400);
画完矩形之后,来看看圆是怎么画的, 圆其实就是在给定的矩形中画出来的, 可以用Ellipse来绘制圆,原型如下:
Ellipse(HDC hdc, int left, int top , int right, int bottom);
参数hdc 为要绘制的圆的DC。
参数left为圆的外切矩形的左边界的位置。
参数top为圆的外切矩形的顶边界的位置
参数right为圆的外切矩形的右边界的位置。
参数bottom为圆的外切矩形的底边界的位置。
比如要绘制一个外切矩形为RECT(100,100,300,300)的圆的边框,代码如下:
SelectObject(hdc, GetStockObject(NULL_BRUSH));
Ellipse(hdc, 100,100,300,300);
接下来学习饼图的绘制,饼图的绘制可以采用Pie函数来完成。
饼图是圆周上面的两个点向圆切割所形成的饼,而此圆的外切矩形,我们假设它为限定矩形
所以绘制饼图主要有两部分参数,第一部分参数是叫限定矩形,第二部分参数就是饼图的起点X和Y值,以及终点X和Y的值, 原型如下:
Pie(HDC hdc,int left, int top ,int right ,int bottom, int xr1,int yr1, int xr2, int yr2);
参数hdc为要绘制的饼图的DC。
参数left为指定限定矩形左上角的X坐标。
参数top为指定限定矩形左上角的Y坐标。
参数right为指定限定矩形右下角的X坐标。
参数bottom为指定限定矩形右下角的Y坐标。
参数xr1为饼图起点的径线端点的X坐标。
参数yr1为饼图起点的径线端点的Y坐标。
参数xr2为饼图终点的径线端点的X坐标。
参数yr2为饼图终点的径线端点的Y坐标。
比如要绘制一个限定矩形为RECT(470,440,690,690),起点的径线端点为(692,550),终点的径线端点为(582,441)的饼的边框,代码如下:
SelectObject(hdc, GetStockObject(NULL_BRUSH));
pIE(hdc, 470,400,690,690,692,550,582,441);
那么现在完成了点,线,矩形,圆, 饼的绘制,除了点之外的图形绘制时几乎都涉及到两个方面的绘制一个是边框的绘制, 一个是边框内部的填充, 那么这两个部分则分别由绘制的边框的画笔和填充图形的画刷来完成, 在上面的绘制中都是调用库寸画笔中的默认画笔和库存画刷中的空画刷来完成的图形绘制的。 下面来看看库存画笔的选用:
SelectObject(hdc, GetStockObject(WHITE_PEN));
表示获取并选进宽度为1,颜色为白色,样式为实线的画笔。
SelectObject(hdc,GetStockObject(BLACK_PEN));
表示获取并选进宽度为1,颜色为黑色,样式为实线的画笔。
SelectObject(hdc, GetStockObject(NULL_PEN));
获取并选进空的画笔。
注:在DC中默认的画笔是宽度为1,颜色为黑色的实线画笔
那么如何创建自定义的画笔呢?可以通过CreatePen创建画笔,原型如下:
HPEN CreatePen(int fnPenStyle,int nWidth,COLORREF crColor);
参数fnPenStyle为画笔的样式,它可以是下列任何值之一:
PS_SOLID , 实心笔
PS_DASH, 短线式笔,要求笔宽度<=1
PS_DOT, 点式笔,要求笔宽度<=1
PS_DASHDOT, 虚线笔,要求笔宽度<=1
PS_DASHDOTDOT,双虚线笔,要求笔宽度<=1
PS_NULL, 笔不可见,
PS_INSIDEFRAME,实心笔,但是笔宽是向里扩展。
参数nWidth 为画笔的宽度,
参数crColor为画笔的颜色。
PS_SOLID 与 PS_INSIDEFRAME的区别:
当宽度小于1时,两者没有区别。如图3
PS_SOLID的宽度大于1时,就往线的两边扩张。如图1
PS_INSIDEFRAME 的宽度大于1时,就往线的里边扩张,如图2.
那么现在知道如何来定义创建画笔, 那么如何使用和使用完之后如何来销毁它呢?
创建,使用和销毁自定义画笔 代码如下:
{
//创建自定义画笔
HPEN hPen = CreatePen(PS_SOLID,1,RGB(42,187,203));
//使用自定义画笔
HPEN hPenOld = (HPEN)SelectObject(hdc,hPen);
MoveToEx(hdc,78,315,NULL);
LineTo(hdc,150,170);
//销毁自定义画笔
SelectObject(hdc,hPenOld);
DeleteObject(hPen);
}
上面通过自定义画笔可以完成图形的边框绘制,下面来看看填充图形的方法
Rectangle函数用DC所选进的画刷来填充矩形的内部。
Ellipse函数用DC所选进的画刷来填充圆的内部。
Pie函数用DC所选进的画刷来填充饼的内部。
库存画刷:
SelectObject(hdc, GetStockObject(WHITE_BRUSH));
获取并选进白色画刷
SelectObject(hdc,GetStockObject(LTGRAY_BRUSH));
获取并选进亮灰色画刷
SelectObject(hdc,GetStockObject(GRAY_BRUSH));
获取并选进灰色画刷
SelectObject(hdc,GetStockObject(DKGRAY_BRUSH));
获取并选进暗灰色画刷
SelectObject(hdc,GetStockObject(BLACK_BRUSH));
获取并选进黑色画刷
SelectObject(hdc,GetStockObject(NULL_BRUSH));
获取并选进空画刷
也可以通过使用CreateSolidBrush来创建单色的画刷,执行成功之后会返回一个画刷的句柄。
创建单色画刷
HBRUSH CreateSolidBrush(COLORREF color);
参数color就是所要创建的画刷的颜色
返回值为所要创建出来的画刷的句柄。
例如使用蓝色画刷填充指定矩形,代码如下:
{
//创建自定义单色画刷
HBRUSH hbr = CreateSolidBrush(RGB(0,0,255));
//选进自定义单色画刷并备份旧画刷
HBRUSH hBrushOld = (HBRUSH)SelectObject(hdc,hbr);
//填充指定矩形
Rectangle(hdc,480,170,520,316);
//还原旧画刷
SelectObject(hdc,hBrushOld);
//删除自定义画刷
DeleteObject(hbr);
}
上面是创建和使用单色画刷, 下面来创建带有图案的画刷,有两种方式。
方式一:是创建带有阴影图案的画刷,采用CreateHatchBrush函数。
方式二:是创建带有自定义位图的画刷,采用CreatePatternBrush函数。
CreateHatchBrush创建影线画刷
HBRUSH CreateHatchBrush(int iHatch,COLORREF color);
参数iHatch为阴影类型
参数color为阴影的颜色。
返回值为所创建出来的画刷句柄。
如果要创建效果更加丰富的画刷,则可以使用CreatePatternBrush画刷
CreatePatternBrush创建位图画刷
HBRUSH CreatePatternBrush(HBITMAP hbmp);
参数hbmp为所要使用的位图的句柄。
返回值为所创建出来的画刷句柄。
注意:只要是自定义的画刷,在用完之后,都要做两个操作:
1. 选出DC
2. DeleteDC 来删除掉
以上学习了自定义画笔和画刷,那么如何让字体和背景看起来相对透明呢? 就是透过字体好像可以看见背景的效果,但是GDI是不支持透明的,GDI所有的Alpha值默认是0,没有Alpha值可供设置,但是可以用GDI+函数库。
// GdiDemo.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
#include "GdiDemo.h"
#include <string>
using namespace std;
#define MAX_LOADSTRING 100
// Global Variables:
HINSTANCE hInst; // current instance
TCHAR szTitle[MAX_LOADSTRING]; // The title bar text
TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
HBITMAP hbmpBack = NULL;
// Forward declarations of functions included in this code module:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: Place code here.
MSG msg;
HACCEL hAccelTable;
// Initialize global strings
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_GDIDEMO, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_GDIDEMO));
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
::DeleteObject(hbmpBack);
return (int) msg.wParam;
}
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hbmpBack = ::LoadBitmapW(hInstance,MAKEINTRESOURCE(IDB_BACK));
hInst = hInstance; // Store instance handle in our global variable
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
RECT rcClients;
::GetClientRect(hWnd,&rcClients);
HDC hMemDC = ::CreateCompatibleDC(hdc);
HBITMAP hOldBmp = (HBITMAP)::SelectObject(hMemDC,hbmpBack);
BITMAP bmp;
::GetObjectA(hbmpBack,sizeof(BITMAP),&bmp);
StretchBlt(hdc,0,0,rcClients.right-rcClients.left,rcClients.bottom-rcClients.top,hMemDC,0,0,bmp.bmWidth,bmp.bmHeight,SRCCOPY);
//BitBlt(hdc,0,0,bmp.bmWidth,bmp.bmHeight,hMemDC,0,0,SRCCOPY);
::SelectObject(hMemDC,hOldBmp);
::DeleteObject(hOldBmp);
::DeleteObject(hMemDC);
//绘制标题文本
/*wstring str = L"网站流量统计报表";
DrawText(hdc,str.c_str(),(int)str.length(),&rcClients,DT_CENTER | DT_TOP | DT_SINGLELINE);*/
PaintString(hdc,hWnd);
EndPaint(hWnd, &ps);
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
return (INT_PTR)FALSE;
}
void PaintString(HDC hdc,HWND hWnd)
{
LOGFONT logfont;
memset(&logfont,0,sizeof(LOGFONT));
logfont.lfWidth = 20;
logfont.lfHeight = 40;
logfont.lfCharSet = GB2312_CHARSET;
_tcscpy(logfont.lfFaceName,_T("宋体"));
HFONT hFont =::CreateFontIndirectW(&logfont);
HFONT hOldFont = (HFONT)::SelectObject(hdc, hFont);
COLORREF clrOldColor=::SetTextColor(hdc,RGB(0,0,128));
RECT rcClient;
::GetClientRect(hWnd,&rcClient);
wstring str = L" 网站流量统计报表";
DrawText(hdc,str.c_str(),(int)str.length(),&rcClient,DT_CENTER | DT_TOP | DT_SINGLELINE);
::SelectObject(hdc,hOldFont);
::SetTextColor(hdc,clrOldColor);
::DeleteObject(hFont);
}