最近在学习 Win32编程,所以顺便将每日所学记录下来,一方面为了巩固学习的知识,另一方面也为同样在学习Win32开发的童鞋们提供一份参考。
本系列博文均根据学习《Windows程序设计》一书总结而来;
运行环境:
- 操作系统: Windows 7家庭版
- 编译器:Visual Studio 2013
文本输出
客户区:整个应用程序窗口中没有被标题栏、边框、菜单栏、工具栏、状态栏和滚动条占用的区域。简而言之,客户区就是窗口中程序可以在上面绘制并向用户传达可视化信息的区域。
大多数Windows程序在WinMain函数初始化过程中会在进入消息循环之前调用UpdateWindow函数,Windows利用这个机会向窗口过程发送最初的WM_PAINT消息,这个消息通知窗口过程绘制客户区。
绘制和更新
在Windows中,只能在窗口的显示区域中绘制文字和图片,而且不能确保在显示区域内显示的内容会一直保留在程序下一次更改的时候它依然存在;
Windows是一个消息驱动系统,它通过把消息投递到应用程序的消息队列中或把消息发送给合适的窗口处理程序,将发生的各种时间通知给程序。Windows通过发送WM_PAINT消息通知窗口处理函数;
WM_PIANT
大部分Windows程序都在WinMain中进入消息队列的初始化之前都要使用UpdateWindow函数,Windows利用这个机会给窗口处理函数发送第一个WM_PAINT函数。窗口处理函数应该在时刻都准备好处理其他的WM_PAINT消息:
- 使用者移动或显示窗口时,窗口原来被隐藏区域可见;
- 使用者改变窗口大小;
- 使用ScrollWindow或ScrollDC函数滚动显示区域的一部分;
- 使用InvalidataRect或InvalidataRgn函数产生WM_PAINT消息;
- 在某些情况,显示区域一部分被覆盖,Windows试图保存一个显示区域,并在以后恢复;
- Windows擦除覆盖了部分窗口的对话框或消息框
- 功能列表下拉
有效窗口和无效窗口
尽管窗口处理程序接收到WM_PAINT消息后,就准备更新整个窗口显示区域,但它经常只需要更新一个较小的区域。
Windows内部为每个窗口保存一个窗口结构,这个结构包含了无效区域最小的举行坐标及其他信息,这个区域称为无效窗口;如果窗口处理程序处理WM_PAINT消息之前显示区域的另一个区域变为无效。则Windows计算出一个包围两个区域的新的无效区域,并将这种变化后的消息放到消息结构体中。Windows不会将多个WM_PAINT消息都存放在消息队列中;
GDI
要在窗口的显示区域画图,可以使用Windows的图形装置界面函数;
Windows提供了几个GDI函数,用于将文字输出到视窗的显示区域内。但是我们更为使用频繁的是TextOut
1 | TextOut(hdc, //窗口 |
绘制信息结构
1 | typedef struct tagPAINTSTRUCT { |
当程序调用BeginPaint函数时,Windows将自动填充这个结构中的字段。程序只能够使用前三个字段。其他的供Windows内部使用。多数情况下,fErase成员将被设置为FALSE。这意味着Windows在BeginPaint函数中已经擦除了无效区域的背景。(在窗口过程中自定义背景擦除方式需处理WM_ERASEBKGND。)Windows使用WNDCLASS结构中的hbrBackground指定的画刷来擦除背景。rcPaint成员定义了无效矩形的边界(即RECT结构中的left、top、right、bottom)。
当程序通过调用InvalidateRect函数使客户区中的一个矩形失效时,则该函数的最后一个参数会指定是否擦除背景。如果这个参数为FALSE,则随后调用的BeginPaint函数将不会擦除背景,在调用完BeginPaint后PAINTSTRUCT结构的fErase成员将为TRUE。
获取设备环境句柄方法一
窗口过程在处理WM_PAINT消息时必须成对地调用BeginPaint和EndPaint,BeginPaint返回设备环境句柄,EndPaint释放设备环境句柄。如果窗口过程不处理WM_PAINT消息,WM_PAINT消息会被传送给Windows默认的窗口过程DefWindowProc,在DefWindowProc中,WM_PAINT消息处理的代码如下:
1 | PAINTSTRUCT ps; |
处理WM_PAINT消息时,必须成对的使用BeginPaint和EndPaint;
这两个BeginPaint和EndPaint使用之间没有任何叙述,仅仅使先前无效区域变为有效,但是不可以这么使用;
1 | case WM_PAINT: |
Windows将一个WM_PAINT消息存入消息队列中,是因为显示区域的一部分无效。如何不使用BeginPaint和EndPaint,则Windows不会使得改区域有效。相反,Windows会继续发送另外一个WM_PAINT消息,一直下去;
要在处理WM_PAINT消息时在更新的矩形外绘图,可以在调用BeginPaint之前使用如下调用:InvalidateRect(hwnd,NULL,TRUE);它使整个客户区变为无效,并擦除背景,但若最后一个参数为FALSE,则不擦除背景,原有的东西将保留在原处。
获取设备环境句柄方法二
成对使用GetDC和ReleaseDC,与BeginPiant函数返回的设备环境句柄不同,从GetDC返回的设备环境句柄中的裁剪矩形是整个客户区,GetDC不会将无效区域有效化。需使用ValidateRECT函数将整个客户区有效化。
另一个与GetDC类似的函数是GetWindowDC。GetWindowDC返回的是整个窗口的设备环境句柄。对应的消息为WM_NCPAINT(非客户区绘制)消息。
1 | PAINTSTRUCT ps; |
GetDC返回用于写入窗口客户区的设备描述表句柄,而GetWindowDC返回写入整个窗口的设备描述表句柄。例如,您的程序可以使用从GetWindowDC返回的设备描述表句柄在窗口的标题栏上写入文字。
如TextOut函数中,传递给函数的坐标常常被称为”逻辑坐标”。Windows有多种不同的”映射方式”,它们用来控制GDI绘图函数指定的逻辑坐标转换为显示器的物理像素坐标的方式。映射方式在设备描述表中定义,默认的映射方式是MM_TEXT,也就是说,逻辑单位与设备单位相同,都是相对于客户区左上角的像素数;x的值从左向右递增,y的值从上往下递增。
系统字体
要用TextOut显示多行文本,就必须确定字体的字符大小。
程序可以调用GetSystemMetrics函数来确定关于用户界面构件大小的信息,调用GetTextMetrics来确定字体大小。用下述方式调用
1 | GetTextMetrics: |
平均字符宽度cxChar=tm.tmAveCharWidth;
总的字符高度cyChar=tm.tmHeight+tm.tmExternalLeading;
大写字母的平均宽度cxCaps=(tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar/2;
获取客户区大小
1 | case WM_SIZE: |
滚动条
SetScrollRange设置滚动范围;
SetScrollPos在滚动条范围内设置新的滚动框位置;
GetScrollRange和GetScrollPos获取当前范围和位置。
在用鼠标单击或拖动滚动条时,Windows给窗口过程发送WM_VSCROLL和WM_HSCROLL消息。对于这两个消息,wParam消息参数的低字节指出了鼠标对滚动条进行的操作,被看做一个”通知码”。通知码的值由以SB开头的标识符定义。
Win32 API介绍的两个滚动条函数称作SetScrollInfo和GetScrollInfo,这些函数完成以前函数的全部功能,并增加了两个新特性。第一个功能涉及滚动条的大小,第二个是通过GetScrollInfo可以获取真实的32位值。