WINAPI标识符在WINDEF.H中定义:
该语句制订了一个调用约定,包括如何生成机器代码以及在堆栈中放置函数调用的参数,
1:1)在Win32API中,长指针和短(近)指针是没有区别的,这只是16位Windows的遗物
2):wndclass.lpfnWndProc=WndProc;
这条语句将这个窗口类类的窗口过程设置为WndProc,这个过程将处理基于这个窗口类创建的所有
窗口的全部消息,在c语言中,像这样在语句中使用函数名时,实际引用提供的是指向函数的指针
3): 下面两个域用于在类结构和Windows内部保存的窗口结构中预留一些额外空间(一般为0)
wndclass.cbClsExtra=0;
wndclass.cbWndExtra=0;
4):一条历史性的经验:在windows示例程序中,可能在WinMain中看到以下代码:
if(!hPrevInstance)
{
wndclass.cbStyle=CS_HREDRAW|CS_VREDRAW;
[other wndclass initialization]
RegisterClass(&wndclass);
}
在32位的Windows中,hPrevInstance总是NULL
2: 对于windows程序,如过不想要某个功能,可以取反后在做与操作,例如:
HWND hwnd;
hwnd = CreateWindow(“Lanbo”, “我的第一个窗口程序”,
WS_OVERLAPPEDWINDOW&~WS_MINIMIZEBOX, 280, 240, 600, 400, NULL, NULL, hInstance, NULL);
!!!!!!!
3:当窗口大小改变时,windows会给窗口过程发送一个WM_SIZE消息,传给窗口过程的lParam参数的低位字中
包含客户区的宽度,高位字中包含客户区的高度。可以定义两个静态变量保存这些尺寸
static int cxClient,cyClient; //c表示count,即像素数
处理WM_SIZE时:
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
char szChar1[50];
sprintf_s(szChar1,”cxClient=%d,cyClient=%d”, cxClient, cyClient);
HDC hdc3;
hdc3 = GetDC(hwnd);
TextOut(hdc3, 0, 20, szChar1, strlen(szChar1));
ReleaseDC(hwnd, hdc3);
break;
最好不要用MessageBox弹出对话框,因为当要用鼠标改变窗口大小时,就会弹出对话框,结果大小改变不
了
4:滚动条
只需在CreateWindow的第三个参数加上WS_VSCROLL就可以加上垂直滚动条了,水平滚动条是WS_HSCROLL
WS_OVERLAPPEDWINDOW|WS_VSCROLL
SetScrollRange(),SetScrollPos(),GetScrollRange(),GetScrollPos()
虽然这四个函数时“过时”的,但它们仍然具有良好的功能,而且不与新函数冲突,
Win32 API介绍的两个滚动条函数是SetScrollInso和GetScrollInfo,这两个函数可以完成之前4个函数的
全部功能,并且增加了两个新特性
wParam消息参数被分为一个低位字和一个高位字。其低位字是一个数值,它指出了鼠标对滚动条进行的操 作,这个数值被看作一个通知码(例如SB_LINEUP....等,SB表示Scroll Bar)(详细信息看windows程序设计
92页)
对于滚动条,以下是程序员要做的事:
*初始化滚动条的范围和位置;
*处理窗口过程的滚动条消息;
*更新滚动框内滚动条的位置;
*更新客户区的内容以响应对滚动条的更改
5: 图形输出设备分为两大类:光栅设备和矢量设备,大部分pc的输出设备时光栅设备,这意味着它们以点模式
来表示图像,这类设备包括视频显示适配器,点阵打印机和激光打印机;设量设备使用线来绘制图像,通常
局限于绘图仪
//=====================================各种消息的wParam和lParam消息=====================================================
6:wParam和lparam
1)WM_SIZE;
lParam的低位字是客户区的宽度,高位字是客户区的高度
2)WM_VSCROLL和WM_HSCROLL:
wParam的低位字是SB_LINEUP,SB_PAGEDOWN….等消息,当消息为SB_THUMBPOSITION时,wParam的高位字
时释放鼠标按键时滚动条的位置,即拖动后的最终位置
3)WM_LBUTTONDOWN,WM_RBUTTONDOWN和WM_MOUSEMOVE:
wParam的值是指示鼠标键及Shift和Ctrl键的状态
lParam的低位字是光标的当前位置的x坐标,高位字是y坐标
(值得注意的是:非客户区(在窗口的客户区外但还在窗口内,包括标题栏,菜单和窗口滚动条)鼠标消息
的wParam和lParam参数有一定的差别,wParam参数指明移动或单击鼠标键的非客户区位置,它设置为
WINUSER.H中定义的以HT开头的标识符之一(HT表示“命中测试”),lParam参数包含低位字的x坐标和高 位字的y坐标,但是,它们都是屏幕坐标,而不是客户区坐标)
4) WM_CHAR:
wParam是所按下的键的ASCII码
5) WM_DISPLAYCHANGE:
当显示器的分辨率改变后,此窗口过程会接到WM_DISPLAYCHANGE消息
wParam参数为显示设备新的颜色深度,即每个像素所占的颜色位数
lParam参数的低字节部分为显示设备新的水平分辨率,高字节部分为显示设备的新的垂直分辨率。
6) WM_INPUTLANGCHANGE:
当应用程序的输入法法伤变化时,WM_INPUTLANGCHANGE消息发送给最顶端的受影响窗口
wParam: 该输入法使用的字符集
lParam: 该输入法的HKL(KeyboardLayout–键盘布局,也被称为Input Local Identifier–输入区域标
识)
7)WM_KEYDOWN
其中wParam 指定非系统键的虚拟键码, lParam 指定重复次数,扫描码,扩展键标识符,上下文代码, 前一键状态标识符,以及转换状态标识符.
8) WM_CREATE
子窗口创建过程中有:
hwndButton[i] = CreateWindow(TEXT(“button”), button[i].szText, WS_CHILD | WS_VISIBLE | button[i].iStyle,cxChar, cyChar*(1 + 2 * i), 20 * cxChar, 7 * cyChar / 4, hwnd, (HMENU)i, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
其中CreateWindow调用的实例句柄看起来有些奇怪,但是它利用了如下事实,即在处理WM_CREATE消息
的过程中,lParam实际上是指向CREATESTRUCT结构的指针,该结构有一个hInstance成员。所以将lParam
转换成指向CREATESTRUCT结构的一个指针,并取出hInstance;(有些Windows程序使用名为hInst的全局 变量,使窗口过程能访问WinMain中的实例句柄)
也可以用 GetWindowLong获取实例句柄
GetWindowLong(hwnd,GWL_HINSTANCE);
9)WM_COMMAND
当子窗口向父窗口发送WM_COMMAND消息时,各参数含义如下:
LOWORD(wParam) 子窗口ID
HIWORD(wParam) 通知码
lParam 子窗口句柄
按钮通知码标识符
* User Button Notification Codes
*/
#define BN_CLICKED 0
#define BN_PAINT 1
#define BN_HILITE 2
#define BN_UNHILITE 3
#define BN_DISABLE 4
#define BN_DOUBLECLICKED 5
#if(WINVER >= 0x0400)
#define BN_PUSHED BN_HILITE
#define BN_UNPUSHED BN_UNHILITE
#define BN_DBLCLK BN_DOUBLECLICKED
#define BN_SETFOCUS 6
#define BN_KILLFOCUS 7
#endif /* WINVER >= 0x0400 */
10)WM_CTLCOLORBTN
当父窗口过程收到WM_CTLCOLORBTN消息时,wParam消息时按钮的设备描述表句柄,lParam是按钮的
窗口句柄;当你的窗口过程处理一个WM_CTLCOLORBTN消息时,你必须完成如下三个动作:
*使用SetTextColor有选择的设置一种文本颜色
*使用SetBkColor有选择的设置一种文本背景颜色
*将一个画刷句柄返回给子窗口
11)WM_DRAWITEM
在处理WM_DRAWITEM消息期间,lParam消息参数是指向类型DRAWITEMSTRUCT结构的指针,OWNERDRAW程序将这个指针保存在pdis变量中,这个结构包含了画该按钮时程序所必须的消 息(这个结构也可以为自绘列表框和菜单所使用)。对按钮而言非常重要的结构域有hDC(按钮的设备描述表),rcItem(提供按钮尺寸的RECT结构),ctlID(控制窗口的ID)和itemState(它 说明按钮是否被按下或者是否拥有输入焦点)
如果按钮当前被按下,那么DRAWITEMSTRUCT的itemState域中的某位被置位。你可以使用0DS_SELECTED常量来测试这些位。如果按钮拥有输入焦点,那么itemState的ODS_FOCUS位将被 置位
//==============================================================================================================================
7:变量ps是类型为PAINTSTRUCT的结构,该结构的hdc字段是BeginPaint返回的设备描述表句柄;PAINTSTRUCT结 构又包含一个窗口客户区无效范围的矩形。使用从BeginPaint获得的设备描述表句柄,只能在这个区域内绘 图,GetDC,ReleaseDC与BeginPaint和EndPaint的组合之间的基本区别是,利用从Getdc返回的句柄可以在整 个客户区上绘图,当然,不能使客户区中任何可能的无效区域变成有效
Windows程序还可以获取适用于整个窗口(而不仅限于窗口的客户区)的设备描述表句柄:
hdc=GetWindowDC(hwnd);
[other program lines]
ReleaseDC(hwnd,dc);
ps:这个函数很少用,如果想使用它,必须捕获WM_NCPAINT(非客户区绘制)消息,Windows使用该消息在窗口 的非客户区上绘图
还有CreateDc,CreateCompatibleDC,DeleteDC等函数
8: 保存设备描述表:
如果想要在释放设备描述表之后,仍然保存程序中对设备描述表属性所做的改变,以便下次调用GetDC和
BeginPaint时它们任然起作用,为此,可在登录窗口类时,将CS_OWNDC标志包含为窗口类的一部分
wndclass.style=CS_HREDRAW|CS_VREDRAW|OWNDC;
现在,基于这个窗口类所创建的每个窗口都将拥有自己的设备描述表,它一直存在,直到窗口被删除,使用 了该标志,就只需初始化设备描述表一次,可以在处理WM_CREATE消息期间完成这一操作
case WM_CREATE;
hdc=GetDC(hwnd);
//Initialize device context attributes
ReleaseDC(hwnd,hdc);
return 0;
在某些情况下,可能想改变某些设备描述表属性,用改变之后的属性进行绘图,然后恢复原来的设备描述表,
要简化这一过程,可以通过如下调用来保存设备描述表的状态,
idSaved=SaveDC(hdc);
现在可以改变一些属性,在想要回到调用SaveDC之前存在的设备描述表时,调用:
RestoreDC(hdc,idSaved);
可以在调用RestoreDc之前调用SaveDC数次
如果调用RestoreDc(hdc,-1);则将设备描述表恢复到最近有SaveDC函数保存的状态中
9:画图
*画直线:画一条直线,必须调用两个函数,第一个指定线的开始点,第二个指定了线的终点;
MoveToEx(hdc,xBeg,yBeg,NULL);
LineTo(hdc,xEnd,yEnd);
如果不调用MoveToEx的话,默认从(0,0)开始,MoveToEx函数的最后一个参数是指向POINT结构的指针,从该
函数返回后,POINT结构的x和y字段指出了先前的位置,如果不需要该信息(通常如此),可将其设置为
NULL;如果需要当前位置,可以调用GetCurrentPosition(hdc,&pt);(pt是POINT结构)
POINT apt[] = { 100, 100, 200, 100, 200, 200, 100, 200,100,100};
/* MoveToEx(hdc, apt[0].x, apt[0].y, NULL);
for (int i = 0; i < 5; i++)
LineTo(hdc, apt[i].x, apt[i].y);*/
//由于LineTo从当前位置画到(但不包括)LineTo函数中给出的点,所以这段代码没有在任何坐标处画两次
//虽然在显示器中多输出几次不存在问题,但是在绘图仪上或者在其他绘图方式下,视觉效果就不太好了
Polyline(hdc,apt, 5);//与上面注释语句效果相同
//当要将数组中的点连接成线时,使用Polyline函数要简单得多
//Polyline函数的最后一个参数是点的数目,也可以用(sizeof(apt)/sizeof(POINT))来表示,Polyline与 //一个MoveToEx函数后面加几个LineTo函数的效果相同,但是,Polyline即不使用也不改变当前位置。
//PolylineTo有些不同,这个函数使用当前位置作为开始点,并将当前位置设置为最后一根线的终点,下面 //代码画出与上面所示一样的矩形:
MoveToEx(hdc,apt[0].x,apt[0].y,NULL);
PolylineTo(hdc,apt+1,4);
*边界框函数:
下面讨论的是Arc函数,它绘制椭圆曲线。然而,如果不先讨论Ellipse函数,Arc函数将难以理解; 而如果不先讨论Rectangle函数,那么Ellipse函数右将难以理解;而如果讨论Ellipse和Rectangle函数,
又会讨论RoundRect,Chord/*弦*/和Pie/*饼*/函数.
问题在于,Rectangle,Ellipse,RoundRect,Chord和Pie函数严格来说不是画线函数,没错,这些函数
是在画线,但它们同时又在使用当前的区域填充刷子填充一个封闭区域,这个刷子默认为白色,这些函 数属于后面关于填充区域的小节
Arc画的是弧,Chord是填充弦和弧,Pie是填充饼(扇形);
10: 可以定义一个画笔句柄
HPEN hPen;
hPen=GetStockObject(WHITE_PEN);
然后把画笔选进设备描述表:
SeleteObject(hdc,hPen);
再这个调用后,所画的线将使用WHITE_PEN,直到将另一个画笔选进设备描述表或者释放描述表句柄为止;
也可以不定义hPen,直接SeleteObject(hdc,GetStockObject(WHITE_PEN));
如果想返回到使用BLACK_PEN的状态,可以用一条语句获取这种现有画笔的句柄,并将其选进设备描述表:
SeleteObject(hdc,GetStockObject(BLACK_PEN));
SeleteObject的返回值是此调用前设备描述表中的画笔句柄,如果启动一个新的设备描述表并调用:
hPen=SelectObject(hdc,GetStockObject(WHITE_PEN));
则设备描述表中的当前画笔为WHITE_PEN,变量hPen将会是BLANK_PEN的句柄
11:画笔的创建,选择和删除
通常是调用CreatePen或CreatePenIndirect创建一个逻辑画笔,然后调用SelectObject将画笔选进设备描述 表,在释放设备描述表句柄后,就可以调用DeleteObject删除所创建的逻辑画笔了;
逻辑画笔是一种”GDI对象”,是 可以创建的6种GDI对象之一,其他5种是刷子,位图,区域,字体和调色板
除了调色板之外,这些对象都是通过SeleteObject选进设备描述表的
hPen=CreatePen(iPenStyle,iWidth,crColor);
其中,1)iPenStyle参数确定画笔是实线,点线还是虚线…..等等
2)iWidth参数是画笔的宽度,值为0意味着画笔宽度为1个像素,现有画笔是一个像素宽,如果指定 的是点划线或虚线式画笔线型,同时又指定一个大于1的物理宽度,那么Windows将使用实线画笔 来代替
3)crColo参数是一个COLORREF值,它指定画笔的颜色对于除了PS_INSIDEFRAME之外的画笔线型,
如果将画笔选进设备描述表中,Windows会将颜色转换为设备所能表示的最相近的纯色,PS_IN
SIDEFRAME是唯一一种可以使用抖动色的画笔线型,并且只有在宽度大于1的情况才如此
4)要使用CreatePenIndirect,首先定义一个LOGPEN类型的结构:
LOGPEN logpen;
此结构有3个成员,lopnStyle(无符号整数即UINT)是画笔线型,lopnWidth(POINT结构)是按照
逻辑单位度量的画笔宽度,lopnColor(COLORREF)是画笔颜色,Windows只使用lopnWidth结构的
x值作为画笔宽度而忽略y值
将结构的地址传递给CreatePenIndirect就可以创建句柄了
hPen=CreatePenIndirect(&logpen);
如果想要随时创建句柄,删除句柄,可以这样做
SeleteObject(hdc,CreatePen(PS_DASH,0,RGB(255,0,0)));
DeleteObject(SeleteObject(hdc,GetStockObject(BLACK_PEN)));
这样就把RGB(255,0,0)这个画笔删除了,因为SeleteObject返回的是上一次的句柄
12:填充空隙(使用点式画笔和虚线画笔回产生一个有趣的问题:点和虚线之间的空隙会怎样呢?)
空隙的着色取决于设备描述表的两个属性—-背景模式和背景颜色;默认背景模式为OPAQUE(不透明的,不 传热的,迟钝的),在这种方式下,Windows使用背景色来填充空隙,默认背景色为白色,可以调用以下函数
改变Windows用来填充空隙的背景色
SetBkColor(hdc,crColor);
通过将背景模式转换为TRANSPARENT,可以阻止Windows填充空隙:
SetBkMode(hdc,TRANSPARENT);
此后,Windows将会忽略背景色,并且不填充空隙
13 :绘图方式:
设备描述表中定义的绘图方式也影响显示器上所画线的外观,设想这样一条直线,它的色彩由画笔色彩 和画线区域原来的色彩共同决定,设想用同一种画笔在白色表面画出黑线而在黑色表面画出白线,而且不用 知道表面是什么色彩,这些通过绘图方式的设置,都可以实现.
当Windows使用画笔来画线时,它实际上执行画笔像素与目标位置处原来像素之间的某种按位布尔操作
像素间的按位布尔运算叫做”光栅运算,简记为”ROP”(raster 光栅).由于画一条直线只涉及到两种像素(画 笔和目标),因此这种布尔运算又称为”二元光栅运算”,简记为”ROP2”,Windows定义了16种ROP代码,表示
Windows组合画笔像素和目标像素的方式.在默认设备描述表中,绘图方式定义为R2_COPYPEN,这意味着 Windows只是将画笔像素复制到目标像素,此外,还有15种ROP2码.
16种不同的ROP2码是怎么得来的呢?为了示范的需要,我们假设使用单色系统,目标色(窗口客户区的 )的色彩为黑色(用0来表示)或者白色(用1表示),画笔也可以为黑色或者白色,用黑色或者白色画笔在黑色
或者白色目标上画图有4种组合,
画笔在目标上,绘制后会得到什么呢?一种可能是不管画笔和目标的色彩,画出的线总是黑色的,这 种绘图方式有ROP2代码R2_BLACK表示.另一种可能是只有当画笔和目标都是黑色时,画出的结果才是白色, 其他情况下画出的都是黑色,尽管这似乎有些奇怪,Windows还是为这种方式起了一个名字,叫做
R2_NOTMERGEPEN(merge 合并).Windows执行目标像素与画笔像素的按位”或”运算,然后翻转所得色彩
可以通过以下调用在设备描述表中设置新的绘图模式:
SetROP2(hdc,iDrawMode);
可以用iDrawMode=GetROP2(hdc);来获取当前绘图模式
图形以当前设备描述表中选择的刷子来填充。默认情况下,使用现有对象,这意味着图形内部将画为白色
Windows定义6种现有刷子:WHTTE_BRUSH,LTGRAY_BRUSH,GRAY_BRUSH,DKGRAY_BRUSH,BLACK_BRUSH和 NULL_BRUSH(也叫HOLLOW_BRUSH),可以将任何一种现有的画刷选入设备描述表中,就和选择画笔一样,
Windows将HBRUSH定义为刷子的句柄,所以可以先定义一个刷子句柄变量:
HBRUSH hBrush;
然后可以通过调用GetStockObject或取GRAY_BRUSH的句柄:
hBrush=GetStockObject(GRAY_BRUSH);
可以调用SelectObject将其选进设备描述表:
SelectObject(hdc,hBrush);
现在,如果要画一个图形,其内部将为灰色
如果想画一个没有边界框的图形,可以将NULL_PEN选进设备描述表:
SelectObject(hdc,GetStockObject(NULL_PEN));
如果想画出图形的边界框,但不填充内部,则将NULL_BRUSH选进设备描述表:
SelectObject(hdc,GetStockObject(NULL_BRUSH));
Polygon函数和多边形填充方式
Polygon(hdc,apt,iCount);//与PolyLine类似
不过,如果最后一个点与第一个点不同,则Windows会再加一条线,将最后一个点与第一个点连接起来,而
PolyLine不会
多边形填充方式有ALTERNATE和WINDING,这两种方式已经很熟悉了,就不做笔记了,博客上写了
14:用画刷填充内部
1)创建一个逻辑画刷:
HBRUSH hBrush;
hBrush=CreateSolidBrush(crColor);
SelectObject(hdc, hBrush);
Rectangle(hdc, 10, 30, 50, 90);//这样就画了一个矩形,内部填充颜色为红色
还可以使用有水平,垂直或者倾斜的线组成的”影线标记(hatch marks)”来创建画刷,函数为:
hBrush=CreateHatchBrush(iHatchStyle,crColor);
iHatchStyle有这几种:
HS_HORIZONTAL
HS_VERTICAL
HS_BDIAGONAL //diagonal斜的,对角线的(朝右上方)
HS_FDIAGONAL //对角线的(朝左上方)
HS_CROSS //十字型的,方格的
HS_DIAGCROSS //十字型对角线的
也可以创建自己的基于位图的画刷:
CreatePatternBrush和CreateDIBPatternBrushPt
创建逻辑画刷的第5个函数包含其他4个函数;
hBrush=CreateBrushIndirect(&logbrush);
变量logbrush是一个类型为LOGBRUSH(“逻辑画刷”)的结构,该结构的三个字段分别是:
lbStyle(UINT) lbColor(COLORREF) ibHatch(LONG)
15: 映射方式:
Windows定义了8种映射方式
(pa: 1英寸约为2.54厘米
1英尺约等于30.48厘米
1厘米约等于28.35磅
l厘米约等于567缇
像素即每英寸点数(dots per inch),72dpi即每英寸72点)
分别是: 增加值
映射方式 逻辑单位 x值 y值
MM_TEXT 像素 右 下
MM_LOMETRIC 0.1 mm 右 上
MM_HIMETRIC 0.01 mm 右 上
MM_LOENGLISH 0.01 in(inch,即英寸) 右 上
MM_HIENGLISH 0.001 in 右 上
MM_TWIPS 1/1440 in 右 上
MM_ISOTROPIC 任意(x=y) 可选 可选
MM_ANISOTROPIC 任意(x!=y) 可选 可选
默认映射方式为:MM_TEXT(这种映射方式下,逻辑单位与物理单位相同,这样可以直接以像素为单位进行 操作),可用SetMapMode设置映射方式,用GetMapMode获取映射方式
设备坐标与逻辑坐标:
如果使用MM_LOENGLISH映射方式,是不是将会得到以百分之一英寸为单位的WM_SIZE消息呢?绝对不会。 Windows对所有消息(如WM_MOVE.WM_SIZE和WM_MOUSEMOVE),对所有非GDI函数,甚至对一些GDI函数,永远使 用设备坐标;可以这样来考虑:由于映射方式是一种设备描述表属性,所以,只有对需要设备描述表句柄作
参数的GDI函数,映射方式才会起作用,GetSystemMetrics不是GDI函数,所以它总是以设备单位(即像素)为度 量来返回大小的,尽管GetDeviceCaps是GDI函数,需要一个设备描述表作为参数,但是Windows任然对HORZRES
和VERTRES以设备单位作为返回值,因为该函数的目的之一就是给程序员提供以像素为单位的设备大小
不过从GetTextMetrics调用返回的TEXTMETRICS结构的值就是用逻辑单位的,如果在进行此调用时映射方式为
MM_LOENGLISH,则GetTextMetrics将以百分之一英寸为单位提供字符的宽度和高度,在调用GetTextMetrics以
获取字符的宽度和高度信息时,映射方式必须设置成根据这些信息输出文本时所使用的映射方式,这样就可以
简化工作
16:视口(设备坐标)和窗口(逻辑坐标)
Windows提供了两个函数进行设备点和逻辑点之间的转换
DPTiDP(hdc,pPoints,iNumber);
LPToDP(hdc,pPoints,iNumber);
17: 处理MM_TEXT
对于MM_TEXT映射方式,默认的原点和范围如下:
窗口原点: (0,0) 可以改变
视口原点: (0,0) 可以改变
窗口范围: (1,1) 不可以改变
视口范围: (1,1) 不可以改变
Windows提供了函数SetViewportOrgEx和SetWindowOrgEx,用来改变视口和窗口的原点,这些函数都具有改 变轴的效果,以至(0,0)不在指向左上角,(一般来说,会使用两者之一,但不会同时使用二者):
如果将视口原点改变为(xViewOrg,yViewOrg),则逻辑点(0,0)就会映射为设备点(xViewOrg,yViewOrg).如果
将窗口原点改变为(xWinOrg,yWinOrg),则逻辑点(xWinOrg,yWinOrg)将会映射为设备点(0,0)
ps:设备点(0,0)总是在左上角
可以调用GetViewportOrgEx(hdc,&pt);和GetWindowOrgEx(hdc,&pt);获取当前视口和窗口的原点
(MM_TEXT映射方式缺点是y轴正方向是向下的,这有点让人不习惯)
所以可以使用剩下的7种映射方式:
MM_LOMETRIC 0.1 mm 右 上
MM_HIMETRIC 0.01 mm 右 上
MM_LOENGLISH 0.01 in(inch,即英寸) 右 上
MM_HIENGLISH 0.001 in 右 上
MM_TWIPS 1/1440 in 右 上
对于这5种方式
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
SetMapMode(hdc, MM_LOENGLISH);
POINT pt;
pt.x = cxClient;
pt.y = cyClient;
DPtoLP(hdc, &pt, 1);
SetWindowOrgEx(hdc, -pt.x/2, -pt.y/2,NULL);//也可以使用SetWindowOrgEx来改变逻辑点 //(0,0),但是这样稍微困难一点,因为SetWindowOrgEx的参数必须使用逻辑单位,先要将 //(cxCilent,cyClient)用DPtoLP函数转换为逻辑坐标,这种做法与SetViewportOrgEx等效(对 //于MM_LOMETRIC,MM_HIMETRIC,MM_LOENGLISH,MM_HIENGLISH,MM_TWIPS这几种而言,因为MM_TEXT //的逻辑坐标和设备坐标的比例是1:1,所以不转换的话也没事)
//SetViewportOrgEx(hdc, cxClient / 2, cyClient / 2, NULL);
MoveToEx(hdc, -cxClient / 2, 0, NULL);
LineTo(hdc, cxClient / 2, 0);
MoveToEx(hdc, 0, cyClient / 2, NULL);
LineTo(hdc, 0, -cyClient / 2);
for (int i = 0; i < 1000; i++)
{
pCoor[i].x = -cxClient / 2 + i*cxClient / 1000;
pCoor[i].y = -cyClient / 2* sin(pCoor[i].x*PI*2/cxClient);
}
MoveToEx(hdc, pCoor[0].x, pCoor[0].y, NULL);
Polyline(hdc, pCoor, 1000);
EndPaint(hwnd, &ps);
break;
剩下的映射方式为MM_ISOTROPIC,MM_ANISOTROPIC;只有这两种映射方式运行您改变到视口和窗口范围,也 就是说可以改变Windows用来转换逻辑和设备坐标的转换因子(isotropic:各向同性(x,y轴的单位长度相等 ),anisotropic:各向异性(x,y轴单位长度不相等))
例如,想要一个”传统的”单象限虚拟坐标系,其中(0,0)在客户区的左下角,宽度和高度的范围都是0- 32767,并且希望x轴和y轴的单位具有相同的物理尺寸,一下是所需要的程序:
SetMapMode(hdc,MM_ISOTROPIC);
SetWindowExtEx(hdc,32767,32767,NULL);
SetViewportExtEx(hdc,cxClient,-cyClient,NULL);
SetViewportOrgEx(hdc,0,cyClient,NULL);
//具体还有一些知识看windwos程序设计188页
如果其后调用GetWindowExtEx和GetViewportExtEx函数获取了窗口和视口的范围,可以发现,它们并不是 先前指定的值,Windows讲根据显示设备的纵横比来调整范围,以便两条轴上的逻辑单位表示相同的物理
尺寸
18:矩形、区域和裁剪
1):矩形函数
FillRect(hdc,&rect,hBrush);//使用指定画刷填充矩形(直到但不包含right和bottom坐标,该函数不需 //要先将画刷选进设备描述表)
FrameRect(hdc,&rect,hBrush);//使用画刷画矩形框,但不填充矩形
InvertRect(hdc,&rect)//将矩形中所有像素翻转,1变成0,0变成1(例如:白色变为黑色,黑色变成黑色,
//绿色变成品红色)
rect是RECT类型的结构
其他一些函数:
例如:要将RECT结构的4个字段设置为指定值,通常:
rect.left=xLeft;
rect.right=xRight;
rect.top=yTop;
rect.bottom=yBottom;
但是,可以通过SetRect函数达到同样的效果:
SetRect(&rect,xLeft,yTop,xRight,yBottom):
OffsetRect(&rect,x,y);//将矩形沿x,y轴移动几个单元)
Inflate()&rect,x,y);//(inflate vt. 使充气;使通货膨胀 vi. 膨胀;充气)
//增减矩形的尺寸
SetRectEmptty(&rect);//矩形各字段设置为0
CopyRect(&DestRect,&SrcRect)//将矩形复制給另一个矩形(可以直接DestRect=SrcRect)
IntersectRect(&DestRect,&SrcRect1,&SrcRect2);//vi. 相交,交叉 vt. 横断,横切;贯穿
//获取两个矩形的交集
UnionRect(&DestRect,&SrcRect1,&SrcRect2);//获取两个矩形的并集
bEmpty=IsRectEmpty(&rect);//确定矩形是否为空
bInRect=PtInRect(&rect,point);
2): 创建和绘制区域
创建矩形区域:
HRGN hRgn;
hRgn=CreateRectRgn(xLeft,yTop,xRignt,yBottom);
或者:
hRgn=CreateRectRgnIndirect(&rect);
也可以创建椭圆区域:
hRgn=CreateEllipticRgn(xLeft,yTop,xRignt,yBottom);
或者:
hRgn=CreateEllipticRgnIndirect(&rect);
创建圆角矩形区域:CreateRoundRectRgn
创建多边形区域:CreatePolygonRgn(&point,iCount,iPolyFillMode);
iRgnType=CombineRgn(hDestRgn,hSrcRgn1,hSrcRgn2,iComebine);
!! ComebineRgn函数
int CombineRgn(hDestRgn,hSrcRgn1,hSrcRgn2,iCombine);
iCombine值:
RGN_AND 两个源区域的公共部分
RGN_OR 两个源区域的全部
RGN_XOR 两个源区域的全部除去公共部分
RGN_DIFF hSrcRgn1不在hSrcRgn2中的部分
RGN_COPY hSrcRgn1的全部(忽略hSrcRgn2)
该函数将两个源区域(hSrcRgn1和hSrcRgn2)组合起来并用句柄hDestRgn指向组合成的目标区域,这
三个区域句柄必须都是有效的,但是hDestRgn原来所指向的区域被破坏了(当使用这个函数时,可能 让hDestRgn在初始时指向一个小的矩形区域)
区域的句柄可以用于4个绘图函数
FillRgn(hdc,hRgn,hBrush);
FrameRgn(hdc,hRgn,hBrush,xFrame,yFrame);
InvertRgn(hdc,hRgn);
PaintRgn(hdc,hRgn);
3)矩形与区域的裁剪
InvalidateRect使一个矩形区域失效,类似的,有InvalidateRgn函数
当接收到一个由无效区域引起的WM_PAINT消息时,裁剪区域不一定是矩形;
可以使用SelectClipRgn(hdc,hRgn);或者SeleteObject(hdc,hRgn);通过将一个区域选进设备描述表来
创建自己的裁剪区域,这个裁剪区域使用设备坐标;GDI为裁剪区域建立一份副本,所以在将它选进设备 描述表之后,用户可以删除它,Windows还提供了几个对裁剪区域进行操作的函数,如ExcludeClipRect 用于将一个矩形从裁剪区域里排除掉,IntersectClipRect用于创建一个新的裁剪区域,它是前一个裁剪
区域与一个矩形的交集,OffsetClipRgn用于将裁剪区域移动到客户区的另一部分
19: 键盘
1)击键消息:
按下 释放
非系统键 WM_KEYDOWN WM_KWYUP
系统键 WM_SYSKEYDOWN WM_SYSKEYUP
通常,按下和释放消息总是成对出现的,不过,如果按住一个键使得自动重复功能生效,那么该键 最后被释放时,Windows会给窗口过程发送一系列WM_KEYDOWN(或者WM_SYSKEYDOWN)消息和一个 WM_KEYUP(或者SYSKEYUP)消息。像所有入队列的消息一样,击键消息也有时间信息,通过调用 (GetMessageTime),可以获得按下或者释放键的相对时间
2)系统击键与非系统击键:
WM_SYSKEYDOWN和WM_SYSKEYUP消息经常由与Alt相组合的击键产生,这些击键激活程序菜单或者系统
菜单上的选项,或者用于切换活动窗口等系统功能(Alt+Tab或者Alt+Esc),也可以用作系统菜单加速键
(Alt键与一个功能键组合,例如Alt+F4关闭当前应用程序).程序通常忽略WM_SYSKEYUP和WM_SYSKEYDOWN
消息,并将它们传到DefWindowProc。由于Windows处理所有Alt键的功能,所以无需捕获这些消息。你 的窗口过程将最后收到关于这些击键结果(如菜单选择)的其他消息,如果你想自己在窗口过程中包括
捕获系统击键的代码,那么在处理这些消息之后再传到DefWindowProc,Windows就任然可以将它们用于
通常的目的
但是,再考虑一下,几乎所有会影响用户程序窗口的消息都会通过用户窗口过程,只有用户把消息
传给DefWindowProc,Windows才会对消息进行处理。例如,如果将语句
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_SYSCHAR:
return 0;
加入到一个窗口过程中,那么当程序主窗口拥有输入焦点时,就可以有效的禁用所有Alt键操作,其中
包括Alt+Tab,Alt+Esc,以及菜单操作,
2)lParam消息:
在4个击键消息(WM_KEYDOWN,WM_KWYUP,WM_SYSKWYDOWN,WM_SYSKWYUP)中,wParam消息参数含有相应的 的虚拟键码,而lParam消息参数则含有对了解击键非常有用的消息;lParam的32位分为6个域:
31 30 29 28 27 26 25 24 23 ... 16 15 ...00
00-15: 16位重复计数
16-23:8位OEM扫描码(Original Equipment Manufacturer:原始设备制造商)
24: 扩展键标志
29: 环境代码
30: 键的先前状态
31: 转换状态
//对于WM_CHAR消息,wParam参数是ASCII或Unicode字符代码,lParam一样
*重复计数:该消息所表示的击键次数,大多情况下设置为1,不过,如果按下一个键后,您的窗口过程不 够快,以至于不能处理自动重复速率(您可以在“控制面板”的“键盘”中进行设置)下的按 键消息,Windows就把几个WM_KEYDOWN或者WM_SYSKEYDOWN消息组合到单个消息中,并相应地增 加重复计数,WM_KEYUP和WM_SYSKEYUP消息的重复计数总为1;(ps:因为重复计数大于1指示出击 键速率大于程序的处理能力,所以也可以在处理键盘消息时忽略重复计数,不过,有时可能也会
用到重复计数,你应该尝试使用两种方法运行程序,从中找出一种较好的办法)
*OEM扫描码:我们现在不在需要这些信息了;Windows程序能够忽略几乎所有的OEM扫描码,除非它取决于
键盘的物理布局(请参见第二十二章程序KBMIDI)
*扩展键标志:如果击键结果来自IBM增强键盘的附加键之一,那么扩展键标志为1(IBM增强型键盘有101或 102个键;功能键在顶端;)。对键盘右端的Alt和Ctrl键,以及不是数字键盘那部分的光标
移动键(包括Insert和Delete键),数字键盘上的斜杠(/)和Enter键,以及Num Lock键等,此
标志均被设置为1.Windows程序员常忽略扩展键标志
*环境代码: 环境代码在按下Alt键后为1.对WM_SYSKEYUP和WM_SYSKEYDOWN消息,这一位总是1,对 WM_KEYUP和WM_KEYDOWN消息,这一位总是0;但有两个例外:
*如果活动窗口最小化了,则它没有输入焦点,这时候所有的击键产生WM_SYSKEYUP和
WM_SYSKEYDOWN消息,如果Alt键未被按下,则环境代码域被设置为0.Windows使用WM_SYS
KEYUP和WM_SYSKEYDOWN消息,从而使最小化了的活动窗口不处理这些击键
*对于一些外国语键盘,有些字符是通过Shift,Ctrl或者Alt键与其他键相组合产生的,这 时环境代码为1,但是此消息并非系统击键消息
*键的先前状态: 如果在此之前键是释放的,则键的先前状态为0,否则为1.对WM_KEYUP或者WM_SYSKEYUP
消息,它总是设为1,但对于WM_KEYDOWN或者WM_SYSKEYDOWN消息,此位可以是0,也可以
是1,如果为1,则表示该键是自动重复功能所产生的第二个后续消息
*转换状态: 如果键正被按下,则转换状态为0;如果键正被释放,则转换状态为1,对于WM_KEYDOWN或者 WM_SYSKEYDOWN消息,此域为0,对于WM_KEYUP或者WM_SYSKEYUP消息,此域为1
2) 换挡状态:
在处理击键消息时,您可能需要知道是否按下了换挡键(Shift,Ctrl和Alt)或开关键(Caps Lock,Num Lock和Scroll Lock)。通过调用GetKeyState函数,就能获得这些消息;例如:
iState=GetKeyState(VK_SHIFT);
如果按下了Shift,则iState的值为负(即高位被置位)。如果Caps Lock键打开,则从
iState=GetKeyState(VK_CAPITAL);
返回的值低位被置位,此位与键盘上的小灯保持一致
归纳:多数情况下,您将只处理光标移动键(有时也为Insert和Delete键)的WM_KEYDOWN消息。在使用这些
键的时候,可以通过GetKeyState来检查Shift和Ctrl键的状态
3): 字符消息
*处理控制字符:
处理击键和字符消息的基本规则是:如果需要读取输入到窗口的键盘字符,那么可以处理WM_CHAR
消息.如果需要读取光标键,功能键.Delete键,Insert键,Shift键,Ctrl键,以及Alt键,那么可 以处理WM_KEYDOWN消息
但是Tab键怎么办?Enter,BackSpace和Escape键又怎么办?传统上,这些键都产生ASCII控制字 符,但在Windows中,它们也产虚拟代码,作者通常处理成控制字符:
case WM_CHAR:
[other program lines]
switch(wParam)
{
case ‘\0’: //backspace
[other program lines]
break;
case '\t': //tab
[other program lines]
break;
case '\n': //linefeed
[other program lines]
break;
case '\r' //carriage return (回车不换行,比如输出cout<<"123456\r7")
//结果是723456
[other program lines]
break;
default:
[other program lines]
break;
}
break;
*死字符消息(Windows程序员经常忽略WM_DEADCHAR和WM_SYSDEADCHAR消息)
在某些非U.S.英语键盘上,有些键用于给字母加上音调.因为他们本身不产生字符,所以称之为"死键"
4):TrueType和大字体
Windows支持三种字体---位图字体,矢量字体和TrueType字体
支持多种字符集的TrueType字体有时也称为"大字体".在这种情况下的"大"并不是指字符的大小,而是
指数量
当输入法改变时,会收到WM_INPUTLANGCHANGE消息,接收到此消息时,wParam参数表示该输入法使用的 字符集
CreateFont和CreateFontIndirect创建逻辑字体
20: 插入符
当向程序中输入文本时,通常有一个下划线,竖条或者方框来指示输入的下一个字符将出现在屏幕的位 置,该标志通常称为”光标”,但是在Windows下编程,必须改变这个习惯,在Windows中,它成为”插入 符” ,”光标”是指鼠标位置的那个位图图像
1):插入符函数(主要有5个插入符函数)
# CreateCaret 创建与窗口有关的插入符
# SetCaretPos 在窗口中设置插入符的位置
# ShowCaret 显示插入符
# HideCaret 隐藏插入符
# DestroyCaret 撤销插入符
另外还有获取插入符当前位置(GetCaretPos)和获取与设置插入符闪烁时间(GetCaretBlinkTime和 SetCaretBlinkTime)的函数,通过处理WM_SETFOCUS和WM_KILLFOCUS消息,程序就可以确定它是否有输 入焦点。
使用插入符的主要规则就是:窗口过程在WM_SETFOCUS期间调用CreateCaret,再WM_KELLFOCUS期间屌用 DestroyCaret;还有其他的几条规则:插入符刚创建时是隐蔽的,如果想使插入符可见,那么在调用 CreateCaret之后,必须还调用ShowCaret,另外,当窗口过程处理一个非WM_PAINT消息而且希望在窗口绘制
某些东西时,必须调用HideCaret隐藏插入符,在绘制完毕后,再调用ShowCaret显示插入符,HideCaret的 影响具有累积效果,如果多次调用HideCaret而不调用ShowCaret,那么只有调用相同次数ShowCaret时,才
能看到插入符
21:鼠标
1):鼠标基础
可以用GetSystemMetrics函数确定鼠标是否存在:
fMouse=GetSystemMetrics(SM_MOUSEPRESENT)
确定所安装鼠标上键的个数,可使用
cButtons=GetSystemMetrics(SM_CMOUSEBUTTONS);
2):客户区鼠标消息
当鼠标已移过窗口的客户区时,窗口过程收到WM_MOUSEMOVE消息。当在窗口的客户区按下或者释放一个 鼠标键时,窗口过程会接收到以下消息:
键 按下 释放 双击键
左 WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDBLCLK
中 WM_MBUTTONDOWN WM_MBUTTONUP WM_MBUTTONDBLCLK
右 WM_RBUTTONDOWN WM_RBUTTONUP WM_RBUTTONDBLCLK
对于所有这些消息来说,其lParam值均含有鼠标的位置:低位字为x坐标,高位字为y坐标
wParam的值指示鼠标键及Shift和Ctrl键的状态,可以用头文件WINUSER.H中定义的为屏蔽来测试wParam
MK前缀代表”鼠标键”。
MK_LBUTTON 按下左键
MK_MBUTTON 按下中键
MK_RBUTTON 按下右键
MK_SHIFT 按下Shift键
MK_CONTROL 按下Ctrl键
例如:如果收到了WM_LBUTTONDOWN消息,而且值wParam&MK_SHIFT是TRUE(非0),就知道按下鼠标左键时
也按下了Shift键
3):双击鼠标键
如果希望窗口过程能够收到双击键的鼠标消息,那么在调用RegisterClass初始化窗口类结构中,必须
在窗口风格中包含CS_DBLCLKS;
Wndclass.style=CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;
如果窗口风格中未包含CS_DBLCLKS,而用户在短时间内双击了鼠标,那么窗口过程收到以下消息:
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDOWN
WM_LBUTTONUP
如果包含了,则收到的消息是:
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDBLCLK
WM_LBUTTONUP
#非客户区鼠标消息:
键 按下 释放 按下(双击)
左 WM_NCLBUTTONDOWN WM_NCLBUTTONUP WM_NCLBUTTONDBLCK
中 WM_NCMBUTTONDOWN WM_MCMBUTTONUP WM_NCMBUTTONDBLCK
右 WM_NCRBUTTONDOWN WM_NDRBUTTONUP EM_NCRBUTTONDBLCK
非客户区鼠标消息到wParam和lPafam参数与客户区鼠标消息不同,wParam参数指明移动或者单击鼠标
键的非客户区位置,它设置为WINUSER.H中定义的以HT开头的标识符之一(HT即Hit test击中测试)
lParam包含低位字的x坐标和高位字到y坐标,不过都是屏幕坐标,可用函数在屏幕坐标和客户区坐 标进行互相转换
ScreenToClient(hwnd,&pt);
ClientToScreen(hwnd,&pt);
#击中测试消息:
鼠标消息中的WM_NCHITTEST,代表"非客户区命中测试",熙消息优先于所有其他的客户区和非客户区
鼠标消息.lParam参数含有鼠标位置的x,y屏幕坐标,wParam没用
Windows应用程序通常把这个消息传给DefWindowProc,然后Windows用WM_NCHITTEST消息产生基于鼠 标位置的所有其他鼠标消息.对于非客户区鼠标消息,当处理WM_NCHITTEST时,从DefWindowProc返回到值
将成为鼠标消息中wParam参数,这个值可以是任意非客户区鼠标消息的wParam值再加上一下内容:
HTCLIENT 客户区
HTNOWHERE 不在窗口中
HTTRANSPARENT 窗口由另一个窗口覆盖
HTERROR 使DefWindowProc产生蜂鸣声
如果DefWindowProc在其处理WM_NCHTCLIENT消息后返回HTCLIENT,那么Windows将把屏幕坐标转换为客户 区坐标并产生客户区鼠标消息;如果在窗口过程中包含以下几条语句:
case WM_NCHTTEST:
return (LRESULT)HINOWHERE;
就可以有效地禁止窗口中的所哟客户区和非客户区鼠标消息,这样一来,当鼠标在窗口(包括系统菜单
图标,缩放按钮以及关闭按钮)中时,鼠标键将失效
#bool型变量可以通过^1运算将false转化为true,将true转化为false
例如:bool f=true;
f^=1;//f为false
每个子窗口都有唯一的子窗口ID号,该ID号在CreateWindow调用创建窗口时定义,在Checker3中,此ID 号是矩形的x和y位置的组合。可以通过下面的调用来获得特定子窗口的子窗口ID
idChild=GetWindowLong(hwndChild,GWL_ID);
下面的函数功能相同
idChild=GetDlgCtrlID(hwndChild);//该函数主要用于对话框和控制窗口
如果知道父窗口的句柄和子窗口ID,也可以用以下函数获得子窗口的句柄
hwndChild=GetDlgItem(hwndParent,idChile)
4)使用键盘仿真鼠标
即使没有安装鼠标,Windows任然可以显示一个鼠标光标,Windows为这个光标保存了一个"显示计数".
如果安装了鼠标,显示计数被初始化为0,否则为-1.只有在显示计数非负时才显示鼠标光标,要增加
显示计数,可以调用
ShowCursor(TRUE);
要减少鼠标计数
ShowCursor(FALSE);
要获取光标的位置,可以调用:
GetCursorPos(&pt); //pt是POINT结构
SetCursorPos(x,y); //设置鼠标光标的位置
//都是使用屏幕坐标
#每一个子窗口都有唯一的一个子窗口ID号,该ID号在CreateWindow调用创建窗口时定义,在checker3 中此ID号是矩形的x和y位置的结合,可以通过下面的调用来获得一个特定子窗口的子窗口ID:
idChild=GetWindowLong(hwndChild,GWL_ID);
下面的函数也有同样的功能:
idChild=GetDlgCtrlID(hwndChild);//主要用于对话框和控制窗口;
如果知道父窗口句柄和子窗口ID,此函数一可以获得子窗口的句柄
hwnd=GetDlgItem(hwndParent,idChild);
5)捕获鼠标
还记得BlockOut1程序吗?当在客户区按下鼠标左键,然后将光标移出窗口,程序将停止接收WM_MOUSE
MOVE消息,现在释放左键,BlockOut1将不再获得WM_LBUTTONUP消息,因为光标在客户区外,现在将光标
移回客户区,窗口过程任然认为键处于按下状态,这明显有缺陷
如果用户正在拖动鼠标,那么当鼠标短时间内被拖出窗口时应该没有什么大问题, 程序应该仍然控 制着鼠标
SetCapture(hwnd);
调用该函数后,Windows将所有鼠标消息发给窗口句柄为hwnd的窗口过程,鼠标消息总是客户区消息
即使鼠标正在窗口的非客户区。lParam参数将指示鼠标在客户区坐标中的位置。不过,当鼠标位于客户 区的左边或者上方时,这些x和y坐标可以是负的,当你想释放鼠标时,调用:
ReleaseCapture();
从而使处理恢复正常
int SetROP2(HDC hdc,int fnDrawMode);//Windows API SetROP2(int nDrawMode)主要用于设定当前前 //景色的混合模式。R2_NOT就是取反的意思,即前景色为背景色的反色,经常用 //R2_NOT来画橡皮线,因为两次取反可以还原背景色。
6)鼠标轮
转动鼠标滑轮会产生WM_MOUSEWHEEL消息
22:计时器
1)可以通过调用SetTimer为你的windows程序分配一个计时器,SetTimer有一个时间间隔范围为1- 4294967295(将近50天)的整型参数,例如将此参数设为1000毫秒,Windows将每秒给程序发送一个 WM_TIMER消息
当程序用完计时器时,它调用KillTimer函数停止计时器消息。在处理WM_TIMER消息时,可以通过调 用KillTimer函数来编写一个“瞬时”计时器,KillTimer调用清除消息队列中尚未被处理的WM_TIMER消 息,从而使程序在调用KellTimer之后不会再接收到WM_TIMER消息;计时器消息不是异步的
2)计时器的使用:三种方法
如果需要在整个程序期间使用计时器,可以从Winmain函数或者在处理WM_CREATE消息时调SetTimer
在退出WinMain或WM_DESTROY消息调用KillTimer。根据调用SetTimer时使用的参数,可以用一下三种方 法之一使用计时器
方法一:
最方便的一种方法,它让Windows把WM_TIMER消息发送到应用程序的正常窗口过程中,调用如下:
SetTimer(hwnd,1,ulMseclnterval,NULL);
第一个参数是其窗口过程将接收WM_TIMER消息的窗口句柄,第二个参数是计时器ID,是一个非0数,在
整个例子中假定为1;第三个参数是一个32位无符号整数,以毫秒为单位制定一个时间间隔,例如设为
60 000将使Windows每分钟发送一次WM_TIMER消息
可以通过调用
KillTimer(hwnd,1);
在任何时刻停止WM_TIMER消息(即使正在处理WM_TIMER消息).此函数的第二个参数是SetTimer调用中所
用到同一个计时器ID,在终止程序之前,应该响应WM_DESTROY消息以停止任何活动的计时器;
当窗口过程收到一个WM_TIMER消息时,wParam参数等于计时器的ID值(上述情形为1),lParam参数
为0.如果需要设置多个计时器,那么每个计时器都使用不同的计时器ID,wParam的值将随着传到窗口
过程的WM_TIMER消息的不同而不同,如下:
# define TIMER_SEC 1
# define TIMER_MIN 2
SetTimer(hwnd,TIMER_SEC,1000,NULL);
SetTimer(hwnd,TIMER_MIN,60000,NULL);
case WM_TIMER:
switch(wParam);
{
case TIMER_SEC:
//once per second processing
break;
case TIMER_MIN:
//once per minute processing
break;
}
return 0;
如果要将一个已经存在的计时器设置为不同的时间间隔,可以简单的用不同的时间值再次调用 SetTimer
方法二:
方法一是将WM_TIMER消息发送给通常的窗口过程,而第二种方法是让Windows直接将计时器消息发送
给另一个回调函数
void CLLBACK TimerProc(HWND hwnd,UINT uMsg,UINT iTimerID,DWORD dwTime);
{
[process WM_TIMER message]
}
TimerProc的参数hwnd是在调用SetTimer时指定的窗口句柄,Windows只把WM_TIMER消息发送给 TimerProc,因此消息参数总是WM_TIMER,iTimerID值是计时器ID,dwTimer值是与从GetTickCount
函数返回值兼容的值、这是自Windows启动后所经过的毫秒数
此时SetTimer如下:
SetTimer(hwnd,iTimerID,iMseclnterval,TimerProc);
方法三:
类似于方法二,只是传递给SetTimer的hwnd参数设置为NULL,并且第二个参数(通常为计时器ID) 被忽略了,最后此函数返回计时器ID
iTimerID=SetTimer(NULL,0,iMseclnterval,TimerProc);
如果没有可用的计时器,那么返回NULL
KillTimer的第一个参数(通常是窗口句柄)也必须为NULL,计时器ID必须是SetTimer的返回值:
KillTimer(NULL,iTimerID);
传递给TimerProc计时器的hwnd参数也必须是NULL,这种设置计时器的方法很少被使用,如果在
程序中有一系列不同时刻的SetTimer调用,而又不希望跟踪已经用过了哪些计时器ID,那么使用
此方法是很方便的
3)获取当前时间
SYSTEMTIME结构:
typedef struct _SYSTEMTIME
{
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliSecond;
}
SYSTEMTIME,*PSYSTEMTIME;
SYSTEMTIME主要用于GetLocalTime和GetSystemTime函数.GetSystemTime函数返回当前的世界时
(Coordinated Universal Time,UTC),大概与英国格林威治的时间相同,GetLocalTime函数返回当地时 间,依赖于计算机位于的时区
23: 父窗口向子窗口发送消息
父窗口过程也能向子窗口发送消息,这些消息包括以前缀WM开头的许多消息,另外在WINUSER.H中还定义了
8个按钮说明消息,前缀BM表示”按钮消息”
按钮消息 值
BM_GETCHECK 0X00F0
BM_SETCHECK 0X00F1 //获取或者设置复选框和单选框按钮的选中标记
BM_GETSTATE 0X00F2
BM_SETSTATE 0X00F3 //表示按钮处于正常状态还是(鼠标键或SpaceBar(空格)键按下时)按 //下状态”
BM_SETSTYLE 0X00F4
BM_CLICK 0X00F5
BM_GETIMAGE 0X00F6
BM_SETIMAGE 0X00F7
!!还是说一下,每个子窗口都具有一个在其兄弟中唯一的窗口句柄和ID值,对与句柄和ID这两者,知道其 中一个就可以获得另一个;
1)如果知道子窗口的窗口句柄,可以如下活动ID
id=GetWindowLong(hwndChild,GWL_ID);
2)也可以用另一种方式获得ID
id=GetDlgCtrlID(hwndChild);
3)知道ID和父窗口句柄,就能获得子窗口句柄
hwndChild=GetDlgItem(hwndParent,id);
24: 下压按钮(push button)
下压按钮控制主要用来触发一个立即响应的动作,而不保留任何形式的开/关指示。两种类型的按钮控制有两种风格,分别是BS_PUSHBUTTON和BS_DEFPUSHBUTTON.用来设计对话框时, 两种类型控制的作用不同,但是当作用于子窗口控制时,两种类型的按钮作用相同,尽管BS_DEFPUSHBUTTON的边框要粗一些
!当按钮的高度为文本字符高度的7/4倍时按钮的外观最好,高度至少调节到文本的宽度再加上两个字符的宽度
25: 复选框(check box)
复选框是一个文本框,文本通常出现在复选框的右边。(如果创建按钮时指定了BS_LEFTTEXT风格,那么文本会出现在左边;可以用BS_RIGHT直接调整文本来组合此风格) 复选框通常 用于允许用户对选项进行选择的应用程序中。复选框的常用功能如同一个开关;单击框一次将显示复选标记,再次单击将清除复选标记
复选框最常用的两种风格是BS_CHECKBOX和BS_AUTOCHECKBOX。在使用BS_CHECKBOX时,需要自己向该控制发送BM_SETCHECK消息来设置复选标记,wParam参数置1时设置复选标记,0时
清除复选标记。在处理来自控制的WM_COMMAND消息时,可以用如下的指令来翻转X标记
SendMessage((HWND)lParam,BM_SETCHECK,(WPARAM)!SendMessage((HWND)lParam,BM_GETCHECK,0,0),0);
注意第二个SendMessage调用前面的操作符”!”,其中lParam是在WM_COMMAND消息中传给用户窗口过程的子窗口句柄;如果以后想知道按钮的状态,可以向它发送另一个BM_GETCHECK消 息,也可以将当前状态保存在你的窗口过程的一个静态变量里,或者向它发送BM_SETCHECK消息来初始化带复选标记的BS_CHECKBOX复选框:
SendMessage(hwndButton,BM_SETCHECK,1,0);
对于BS_AUTOCHECKBOX风格,按钮自己触发复选标记的开和关,你的窗口过程可以忽略WM_COMMAND消息,当需要按钮的当前状态时,可以向控制发送BM_GETCHECK消息:
iCheck=(int)SendMessage(hwndButton,BM_GETCHECK,0,0);
如果该按钮被选中,则iCheck值非0,反之为0;
其余两种复选框风格是BS_3STATE和BS_AUTO3STATE,正如它们的名字所暗示的,这两种风格能显示第三种状态–复选框内是灰色–它出现在向控制发送wParam=2的WM_SETCHECK消息
时。灰色向用户表明此框是不能被选中的或者是禁用的;复选框沿矩形的左边框对齐,并集中在调用CreateWindow过程中规定的矩形的顶边和底边之间,在该矩形内的任何地方按下鼠 标都会向其父窗口发送一个WM_COMMAND消息.
复选框的最小高度是一个字符的高度,最小宽度是文本中的字符数加2
26: 单选按钮(radio button)
单选按钮的名称在一系列按钮的后面,这些按钮就像汽车上的收音机一样。汽车收音机上的每一个按钮都对应一种收音状态而且一次只能有一个按钮被按下,在对话框中,单选按钮 组常常用来表示相互排斥的选项。与复选框不同,单选框的工作与开关不一样,也就是说,当第二次按单选按钮时,它的状态保持不变。
单选按钮的形状是一个圆圈,而不是方框,除此之外,它非常像复选框。圆圈内的加重圆点表示该单选按钮已经被选中。单选按钮有窗口风格BS_RADIOBUTTON和BS_AUTORADIOBUTTON 两种,但是后者只用于对话框
当收到来自单选按钮的WM_COMMAND消息时,应该向它发送wParam等于1的BM_SETCHECK消息来显示其选中状态
SendMessage(hwndButton,BM_SETCHECK,1,0);
27:分组框(group box)
分组框即风格为BS_GROUPBOX的选择框,它是按钮类中的特例,即不处理鼠标输入和键盘输入,也不向其父窗口发送WM_COMMAND消息,分组框是一个矩形框,窗口文本在其顶部显示, 分组框常用来包含其他的按钮控制
28: 按钮颜色
对每一个需要多种颜色的按钮来说,COLOR_BTNFACE被用于下压按钮主要的表面颜色以及其他按钮主要的背景颜色(这也是用于对话框和消息框的系统颜色);COLOR_BTNSHADOW被建议
用作于按钮右下边以及复选框内部和单选按钮圆点的阴影。对于下压按钮,COLOR_BTNREXT被用作于文本颜色;而对于其他按钮,则使用C0LOR_WINDOWTEXT作为文本颜色。还有其他几种
系统颜色用于按钮设计的各个部分。
因此,如果在你想在我们的客户区表面显示按钮,那么一种避免颜色冲突的方法是屈服于这些系统颜色,首先,在定义窗口类时使用COLOR_BTNFACE作为你的客户区的背景颜色:
wndclass.hbrBackground=(HBRUSH)(COLOR_BTNFACE+1);
你可以在BTNLOOK程序中尝试使用这种方法。当WNDCLASS结构中的hbrBackground值是这个值时,Windows会明白这实际上指的是一种系统颜色而不是一个实际的句柄。Windows要求当
你在WNDCLASS结构的hbrBackground域中指定这些标识符时加1,这样做的目的是防止其值为NULL,而没有任何其他目的。如果你的程序运行中,系统颜色恰好发生了变化,那么客户区 将变得无效,Windows将使用新的COLOR_BRNFACE值,但是现在我们又引发了另一个问题。当你使用TextOut显示文本时,Windows使用的是在设备描述表中为背景颜色(它擦除文本后的背 景)和文本颜色定义的值,其默认值为白色(背景)和黑色(文本),而不管系统颜色和窗口类结构中的hbrBackground与为何值。所以,你需要使用SetTextColor和SetBkColor将文本和文 本背景的颜色改为系统颜色,你可以在获得设备描述表句柄之后这么做:
SetBkColor(hdc,GetSysColor(COLOR_BTNFACE));
SetTextColor(hdc,GetSysColor(COLOR_WINDOWTEXT));
这样,客户区背景,文本背景和文本的颜色都与按钮的一致了,但是,如果当你的程序运行时,用户改变了系统颜色,你可能要改变文本背景颜色和文字颜色,这时可以使用以下 代码:
case WM_SYSCOLORCHANGE:
InvalidateRect(HWND,NULL,TRUE);
break;
29: 按下按钮时,就会向父窗口产生WM_COMMAND消息;
使用BS_OWNERDRAW风格建立的按钮会在需要重新着色的任何时候都向它的父窗口发送一个WM_DRAWITE消息,这出现在一下几种情况中:当按钮被创建时,当按钮被按下或被释放时,当
按钮得到或者失去焦点时,以及当按钮需要重新着色的任何时候
在处理WM_DRAWITEM消息期间,lParam消息参数是指向类型DRAWITEMSTRUCT结构的指针,OWNERDRAW程序将这个指针保存在pdis变量中,这个结构包含了画该按钮时程序所必须的消息(这
个结构也可以为自绘列表框和菜单所使用)。对按钮而言非常重要的结构域有hDC(按钮的设备描述表),rcItem(提供按钮尺寸的RECT结构),ctlID(控制窗口的ID)和itemState(它说明按钮 是否被按下或者是否拥有输入焦点)
如果按钮当前被按下,那么DRAWITEMSTRUCT的itemState域中的某位被置位。你可以使用0DS_SELECTED常量来测试这些位。如果按钮拥有输入焦点,那么itemState的ODS_FOCUS位将被置 位
30: 静态类
在CreateWindow函数中指定窗口类为”static”,就可以建立静态的子窗口控制,这些子窗口非常”文静”.它既不接收鼠标或键盘输入,也不向父窗口发送WM_COMMAND消息.
当在静态子窗口上移动或者按下鼠标时,这个子窗口将捕获WM_NCHITTEST消息,并将HITTRANSPARENT的值返回给windows,这将使windows向其下层窗口(通常是它的父窗口)发送相同 的WM_NCHITTEST消息,父窗口常常将该消息传递给DefWindowProc,在这里,它被转换为客户的鼠标消息
31: 滚动条类
在第四章首次讨论了滚动条,也讨论了”窗口滚动条“与”滚动条控制“之间的一些区别,sysmets程序使用窗口滚动条,它出现在窗口的右下角。你可以在创建窗口时通过将标识 符WS_VSCROLL和WS_HSCROLL包含在窗口风格中,从而给窗口添加滚动条。现在我们准备建立一些滚动条控制,它们是可以在父窗口的客户区的任何地方出现的子窗口。你可以使用预先 定义的窗口类”scrollBar”以及两个滚动条风格SBS_VERT和SBS_HORZ中的一个来建立子窗口滚动条控制;
与按钮控制(以及将在后面讨论的的编辑和列表框控制)不同,滚动条控制不向父窗口发送WM_COMMAND消息,而是像窗口滚动条那样发送WM_VSCROLL和WM_HSCROLL消息。在处理滚动 条消息时,你可以通过lParam参数来区分开窗口滚动条与滚动条控制(而wParam参数的高位字和低位字都想同)。对于子窗口滚动条其值为0,对于滚动条控制其值为滚动条窗口句柄。
根据Windows文档,滚动条窗口风格标识符SBS_LEFTALIGN、SBS_RIGHTALIGN、SBS_TOPALIGN和SBS_BOTTOMALIGN给出滚动条的标准尺寸,但是这些风格只在对话框中对滚动条有效,
对于窗口滚动条,你可以使用同样的调用来设置滚动条控制的范围和控制
SetScrollRange(hwndScroll,SB_CTL,iMin,iMax,bRedraw);
SetScrollPos(hwndScroll,SB_CTL,iPos,bRedraw);
SetScrollInfo(hwndScroll,SB_CTL,&si,bRedraw);
其区别在于:窗口滚动条将父窗口句柄作为第一个参数,并且SB_VERT或者SB_HORZ作为第二个参数
令人吃惊的是,名为COLOR_SCROLLBAR的系统颜色不再用于滚动条。两端的按钮和和小方块的颜色由COCOR_BTNFACE,COLOR_BTNHILIGHT、COLOR_BTNSHADOW、COLOR_BTNTEXT(用于小
箭头)、COLOR_DKSHADOW、以及COLOR_BTNLIGHT决定。两端按钮之间较大的区域颜色是由COLOR_BTNFACE和COLOR_BTNHIGHLIGHT一起来确定的。如果你捕获了WM_CTLCOLORSCROLLBAR消息
那么可以在消息处理中返回画刷已取代该颜色