在逆向一些软件的时候,会涉及到Windows编程以及API的一些知识,以此初探windows编程
helloworld 1.编写Windows程序,需要头文件,这个文件包括了四个文件
windef.h:基本类型定义
winbase.h:内核函数
wingdi.h:用户接口函数
winuser.h: 图形设备接口函数
所有的API都在这些头文件中声明
2.程序入口
控制台程序(console)程序的入口是main函数 ;GUI程序的入口是WinMain() (动态链接库(DLL)以 DllMain() 为入口函数)
WinMain函数原型
1 2 3 4 5 6 int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) ;
句柄:用来告诉程序去哪里寻找需要的数据(不是指针)
3.第一个程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <windows.h> int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { int nSelect = MessageBox(NULL , TEXT("Hello World\n烫" ), TEXT("first GUI" ), MB_OKCANCEL); if (nSelect == IDOK) { MessageBox(NULL , TEXT("你点击了“确定”按钮" ), TEXT("提示" ), MB_OK); } else { MessageBox(NULL , TEXT("你点击了“取消”按钮" ), TEXT("提示" ), MB_OK); } return 0 ; }
MessageBox() 函数:
1 int WINAPI MessageBox ( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType ) ;
可以知道,该api的返回值是int类型,代表了一个按钮消息
hWnd:该消息框的父窗口句柄,如果此参数为NULL,则该消息框没有拥有父窗口。
lpText:消息框的内容。LPCTSTR 是自定义数据类型,等价于 const char *。
lpCaption:消息框的标题。
uType:对话框的按钮样式和图标。
4.按钮的类型
微软给出了几种默认的按钮类型(MB_按钮的作用)
按钮
含义
0:MB_OK
默认值 ,有一个“确认”按钮在里面
1:MB_YESNO
有“是”和“否”两个按钮
2:MB_ABORTRETRYIGNORE
有“中止”,“重试”和“跳过”三个按钮
3:MB_YESNOCANCEL
有“是”,“否”和“取消”三个按钮
4:MB_RETRYCANCEL
有“重试”和“取消”两个按钮
5:MB_OKCANCEL
有“确定”和“取消”两个按钮
uType 还图标(图标对用户有提醒作用):
图标
含义
MB_ICONEXCLAMATION
一个惊叹号出现在消息框:
MB_ICONWARNING
一个惊叹号出现在消息框(同上)
MB_ICONINFORMATION
一个圆圈中小写字母i组成的图标出现在消息框:
MB_ICONASTERISK
一个圆圈中小写字母i组成的图标出现在消息框(同上)
MB_ICONQUESTION
一个问题标记图标出现在消息框:
MB_ICONSTOP
一个停止消息图标出现在消息框:
MB_ICONERROR
一个停止消息图标出现在消息框(同上)
MB_ICONHAND
一个停止消息图标出现在消息框(同上)
图标和按钮混合使用
1 2 int nSelect = MessageBox(NULL , TEXT("Hello World\nlallalalallalalalla烫" ), TEXT("first GUI" ), MB_OKCANCEL|MB_ICONEXCLAMATION);
5.注意,messagebox虽然返回数字,但是被定义为宏,所以可以使用数字或者宏名称
返回值
含义
IDOK
用户按下了“确认”按钮
IDCANCEL
用户按下了“取消”按钮
IDABORT
用户按下了“中止”按钮
IDRETRY
用户按下了“重试”按钮
IDIGNORE
用户按下了“忽略”按钮
IDYES
用户按下了“是”按钮
IDNO
用户按下了“否”按钮
数据类型 1.Windows使用typedef
或#define
定了很多新的数据类型,但是大多数都是基本数据类型的新名字,这样作可以提高兼容性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 typedef int INT; typedef unsigned int UINT; typedef unsigned int *PUINT; typedef int BOOL; typedef unsigned char BYTE; typedef unsigned short WORD; typedef unsigned long DWORD; typedef float FLOAT; typedef FLOAT *PFLOAT; typedef BOOL near *PBOOL; typedef BOOL far *LPBOOL;typedef BYTE near *PBYTE; typedef BYTE far *LPBYTE;typedef int near *PINT; typedef int far *LPINT;typedef WORD near *PWORD; typedef WORD far *LPWORD;typedef long far *LPLONG; typedef DWORD near *PDWORD; typedef DWORD far *LPDWORD;typedef void far *LPVOID; typedef CONST void far *LPCVOID;
可以总结一下规律就是
无符号类型:一般是以“U”开头,比如“INT”对应的“UINT”。
指针类型:其指向的数据类型前加“LP”或“P”,比如指向 DWORD 的指针类为“LPDWORD”和“PDWORD”。
句柄类型:以“H”开头。比如,HWND 是window(WND简写)也就是窗口的句柄,菜单(MENU)类型对应的句柄类型为 “HMENU” 等等。
2.宽字节和unicode
unicode是宽字节编码的一种,需要用多个字节来表示的代码称为宽字符
2.1 使用宽字符
使用wchar.h
头文件中的wchar_t
来定义宽字符
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> #include <wchar.h> int main () { char ch = 'A' ; wchar_t wch = 'A' ; char str[] = "C语言中文网" ; wchar_t wstr[] = L"C语言中文网" ; printf ("ch=%d, wch=%d, str=%d, wstr=%d\n" , sizeof (ch), sizeof (wch), sizeof (str), sizeof (wstr)); return 0 ; }
运行结果:ch=1, wch=2, str=12, wstr=14
wstr 之所以比 str 多两个字节是因为:字符 ‘C’ 占用两个字节,字符串结束标志 ‘\0’ 也占用两个字节。
2.2 计算ASCII字符串长度使用 strlen 函数,计算宽字符串长度使用 wcslen 函数:
1 2 3 4 5 6 7 8 9 10 #include <stdio.h> #include <wchar.h> #include <string.h> int main () { char str[] = "Hello API" ; wchar_t wstr[] = L"Hello API" ; printf ("strlen(str)=%d, wcslen(wstr)=%d\n" , strlen (str), wcslen(wstr)); return 0 ; }
strlen函数将一字节算作是一个字符,wcslen将两个字节算成一个字符
窗口 窗口、控件、图像、音频视频等都称为资源(Resource) ,在程序中都可以使用、创建、添加、修改等。
句柄 不同窗口、控件、图像等都对应一个唯一的数字(初学者可以理解为 ID),称为句柄(Handle)。
通过句柄,程序可以找对对应的信息。例如:例如用 CreateFile()
函数创建文件后会返回一个文件句柄,然后通过这个句柄就可以读写、删除该文件.
消息机制 1.事件:用户敲击键盘、点击鼠标、拖动窗口、选择菜单、输入文字等所有的操作都称为事件(Event)。
2.当事件发生时,windows会产生一条信息 ,告诉程序发生了什么。
3.每当事件发生时,Windows 会生成一条消息,并放到一个由系统维护的队列中 。然后,程序会自己从这个队列中获取消息并分析 ,调用事件处理函数(处理事件的代码也就在这个函数中),对用户的操作进行响应。
4.队列:先进先出的数据结构
注意:Windows 向队列中分派消息和应用程序从队列中获取消息并不是同步的,只要有事件发生,就会将消息丢进队列,什么时候处理完毕是应用程序的事。
总结:消息是windows连接程序的桥梁,Windows通过 消息 告诉程序发生了什么,应用程序通过 消息 来办事。
消息结构体 消息其实是一个结构体,名字为 MSG,定义为:
1 2 3 4 5 6 7 8 9 typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG;
Windows 向队列中投递消息,其实就是将一个类型为 MSG 的结构体变量 丢进队列。
1.message:因为数值不好记忆,所有Windows将消息对应的数值定义为WM_XXX宏 (WM是Window Message的缩写)的形式。例如:鼠标左键按下消息是WM_LBUTTONDOWN;键盘按下消息WM_KEYDOWN;字符消息是WM_CHA。在程序中我们通常都是以WM_XXX宏的形式来使用消息的。
MSG结构体和WndProc窗口过程详解 MSG结构体 MSG 结构体用来表示一条消息,各个字段的含义如下:
1 2 3 4 5 6 7 8 9 typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG;
1.wParam 和 lParam :wParam、lParam 表示的信息随消息类型的不同而不同。
2.一般约定,wParam 用来表示控件的ID,或者高 16 位和低 16 位组合起来分别表示鼠标的位置,如果发送消息时需要附带某种结构的指针或者是某种类型的句柄时,习惯上用 lParam。
WndProc 窗口过程 1 2 3 4 5 6 7 8 LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) { }
注意:WndProc 的各个参数和 MSG 结构体的前四个字段是一一对应的。需要铭记的是:每产生一条消息,都会调用一次 WndProc 函数。
2.当用户点击按钮、编辑框、下拉列表框等控件的时候,会产生WM_COMMAND消息。对于不同来源的 WM_COMMAND 消息,wParam、lParam 参数也不同,见下表:
消息来源
wParam (高16位)
wParam (低16位)
lParam
菜单
0
菜单ID
0
快捷键
1
快捷键ID
0
控件
控件通知码
控件ID
控件句柄
控件通知码用来识别控件的操作。例如 Button 控件一般有两种通知码,BN_CLICKED 和 BN_DOUBLECLICKED,前者表示 Button 被单击,后者表示 Button 被双击。
3.对于 Button 控件,我们可以通过LOWORD(wParam)
来获取它的 ID。
窗口创建 思路
定义 WinMain
函数
定义 窗口处理函数
注册窗口类
创建窗口
显示窗口
消息循环
消息处理
完整的窗口 一般的窗口包括有:最大化、最小化、关闭按钮,也包含菜单、单选框、图像等各种控件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szClassName[] = TEXT("HelloWin" ); HWND hwnd; MSG msg; WNDCLASS wndclass; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon (NULL , IDI_APPLICATION); wndclass.hCursor = LoadCursor (NULL , IDC_ARROW); wndclass.hbrBackground= (HBRUSH) GetStockObject (WHITE_BRUSH); wndclass.lpszMenuName = NULL ; wndclass.lpszClassName= szClassName; RegisterClass(&wndclass); hwnd = CreateWindow( szClassName, TEXT("Welcome" ), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500 , 300 , NULL , NULL , hInstance, NULL ); ShowWindow (hwnd, iCmdShow); UpdateWindow (hwnd); while ( GetMessage(&msg, NULL , 0 , 0 ) ) { TranslateMessage(&msg); DispatchMessage (&msg); } return msg.wParam; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rect; switch (message){ case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; DrawText( hdc, TEXT("第一个win api程序" ), -1 , &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER ); EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage(0 ) ; return 0 ; } return DefWindowProc(hwnd, message, wParam, lParam) ; }
注册窗口类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 WNDCLASS wndclass; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon (NULL , IDI_APPLICATION); wndclass.hCursor = LoadCursor (NULL , IDC_ARROW); wndclass.hbrBackground= (HBRUSH) GetStockObject (WHITE_BRUSH); wndclass.lpszMenuName = NULL ; wndclass.lpszClassName= szClassName;
创建窗口
1 2 3 4 5 6 7 8 9 10 11 12 13 hwnd = CreateWindow( szClassName, TEXT("Welcome" ), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500 , 300 , NULL , NULL , hInstance, NULL );
显示器坐标远点在左上角,横为x,竖为y,没有负数
上述函数只是在内存中创建了窗口,分配了空间,获得了句柄,做好了准备工作,但是并不能显示
显示窗口
1 ShowWindow(hWnd, iCmdShow)
消息循环
在 UpdateWindow 函数被调用之后,新建的窗口在屏幕中就可以显示了。
此时,程序必须能够接受用户的键盘或鼠标事件 。
1 2 3 4 5 while ( GetMessage(&msg, NULL , 0 , 0 ) ){ TranslateMessage(&msg); DispatchMessage (&msg); }
1.GetMessage 的返回值永远为非零值,while 循环会一直进行下去。如果队列中没有消息,GetMessage 函数会等待, 直到有消息进入。
2.获取到消息后,需要调用 TranslateMessage 函数对消息进行转换(翻译),然后再调用 DispatchMessage 函数将消息传给窗口过程去处理(调用窗口过程)。
窗口过程
1.就是处理窗口事件的函数,也就是上面代码中最后的 WndProc 函数。
2.GetMessage 每获取到一条消息,最终都会丢给窗口过程去处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rect; switch (message) { case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; DrawText( hdc, TEXT("第一个win api程序" ), -1 , &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER ); EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage(0 ) ; return 0 ; } return DefWindowProc(hwnd, message, wParam, lParam) ; }
绘制和重绘 1.当用户在屏幕移动其他程序的窗口时,遮住当前程序的部分窗口。Windows 不会保存被遮住的那部分窗口,当其他程序的窗口被移开时,Windows 会要求你的程序重新绘制刚才被遮住的部分区域。如果你自己不重绘,Windows 是不会管的。
并不是简单的遮挡关系
2.Windows 是一个消息驱动的系统,它使用两种方式把各种事件通知给应用程序:
把消息放到应用程序的消息队列中,让程序自己通过 GetMessage 函数获取;
向窗口直接发送消息。
WM_PAINT 消息 1.WM_PAINT 消息表示绘制窗口的客户区 。
以下任何一个事件发生时,窗口过程都会收到一条 WM_PAINT 消息:
用户移动一个窗口,导致原来被遮住的部分窗口暴露出来。
用户调整窗口的大小(当窗口类中 style 字段的值设定为 CS_HREDRAW | CS_VREDRAW 时)。
客户区滚动条滚动时。
当然,你可以可以调用相应的函数强制生成一条 WM_PAINT 消息。
注意:窗口类中 style 字段的值经常被设定为CS_HREDRAW | CS_VREDRAW
,只有这样,调整窗口宽度或高度时才会发生重绘 。
2.在少数情况下,Windows 总是会保存被覆盖的部分窗口,然后再恢复 ,例如:
在Windows中一切界面都是绘图,一旦界面发生改变,就需要重新绘制。
有效矩形和无效矩形 1.Windows 一般不重绘整个客户区,而是绘制客户区的一部分。需要重新绘制的部分被称为“无效区域”或“更新区域”
在绘制的时候,会将无效区域装进一个“最小矩形之中”,称为 无效矩形
向窗口输出文字—TextOut和DrawText函数 设备环境DC 1.Windows不允许我们直接访问硬件,在与这些硬件通信前要获得设备环境(Device Context,简称 DC),进而间接的访问硬件。
设备环境有时也被称为设备上下文或设备描述表。
2.设备环境:与当前硬件设备有关的各种信息,它是硬件设备的抽象。
3.与文字输出、图形绘制有关的函数,在使用时大都需要传入一个参数,就是设备环境句柄。让程序知道往哪里输出
4.获取句柄:常用的是 BeginPaint 函数。绘图完成后,还要释放句柄,使用 EndPaint 函数。
BeginPaint 和 EndPaint 函数 发生绘制事件时,程序通过bp函数通知windows当前程序需要显示器,bp函数执行完就返回显示器的句柄, 绘图之后,用ep函数通知windows绘图结束,可以往下走了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rect; switch (message) { case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; DrawText( hdc, TEXT("第一个win api程序" ), -1 , &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER ); EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage(0 ) ; return 0 ; } return DefWindowProc(hwnd, message, wParam, lParam) ; }
TextOut 函数 TextOut 函数可以在客户区输出一段文本,原型为:
1 2 3 4 5 6 7 BOOL TextOut ( HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cbString ) ;
每当发生WM_PAINT事件时,窗口就会发生重绘,这个时候需要向窗口输出文字:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) { HDC hdc; PAINTSTRUCT ps; TCHAR szText[20 ] = TEXT("欢迎来到C语言中文网" ); switch (message){ case WM_PAINT: hdc = BeginPaint (hwnd, &ps); TextOut(hdc, 50 , 50 , szText, wcslen(szText)); EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage(0 ) ; return 0 ; } return DefWindowProc(hwnd, message, wParam, lParam) ; }
DrawText函数 TextOut 在一个起点开始输出文本,可以精确定位,不能自动换行;
DrawText 在指定区域内输出文本,可以控制格式对,齐居左、居中、居右,可以换行(DrawText 在内部其实也是调用TextOut )
1 2 3 4 5 6 7 8 int DrawText ( HDC hDC, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat ) ;
参数 uFormat 有四种取值,它们可以任意组合:用 | 隔开
DT_CALCRECT:这个参数比较重要,可以使 DrawText 函数计算出输出文本的尺寸。如果输出文本有多行,DrawText 函数使用 lpRect 定义的矩形的宽度,并扩展矩形的底部以容纳输出文本的最后一行。如果输出文本只有一行,则 DrawText 函数改变矩形的右边界,以容纳下正文行的最后一个字符。出现上述任何一种情况。
DT_CENTER:指定文本水平居中显示。
DT_VCENTER:指定文本垂直居中显示。该标记只在单行文本输出时有效,所以它必须与DT_SINGLELINE结合使用。
DT_SINGLELINE:单行显示文本,回车和换行符都不断行。
Windows GDI绘图基础与轻量进阶 1,简介:GDI 是 Graphics Device Interface 的缩写,称为图形设备接口,主要用来绘图,由动态链接库 GDI32.DLL 提供支持。Windows 本身也使用GDI来显示用户界面,比如菜单、滚动条、图标和鼠标指针等。
GDI 基础 绘制矩形 Rectangle 函数可以在窗口上绘制一个矩形,它的原型为:
1 2 3 4 5 6 7 BOOL Rectangle ( HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect ) ;
示例代码
1 2 3 4 5 case WM_PAINT: hdc = BeginPaint(hwnd, &ps); Rectangle(hdc, 50 , 50 , 150 , 150 ); EndPaint(hwnd, &ps); return 0 ;
运行效果就是会在一个窗口内出现一个矩形。
绘制圆形 如果要绘制一个圆形,可以使用 RoundRect 函数,函数的原型是:
1 2 3 4 5 6 7 8 9 BOOL RoundRect ( HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect, int nWidth, int nHeight ) ;
当 nHeight >= nBottomRect 且 nWidth = nRightRect 时,那么绘制出的就是一个圆
1 RoundRect(hdc, 20 , 20 , 150 , 150 , 150 , 150 );
绘制椭圆 Ellipse() 函数可以用来绘制椭圆,它的原型为:
1 2 3 4 5 6 7 BOOL Ellipse ( HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect ) ;
绘制直线 确定起点使用 MoveToEx 函数。MoveToEx 用来指定画笔的起始位置,也就是从哪里开始画,它的原型为:
1 2 3 4 5 6 7 8 9 10 11 12 13 BOOL MoveToEx ( HDC hdc, int x, int y, LPPOINT lpPoint ) ; BOOL LineTo ( HDC hdc, int xEnd, int yEnd ) ;
注意:win32不再支持 MoveTo,只支持它的扩展函数 MoveToEx。
(在这里知道后面的Ex是原先api的拓展)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 case WM_PAINT: hdc = BeginPaint(hwnd, &ps); GetClientRect(hwnd, &rect); MoveToEx(hdc, 150 , 150 , NULL ); LineTo(hdc, 200 , 60 ); LineTo(hdc, 250 , 150 ); LineTo(hdc, 150 , 150 ); EndPaint(hwnd, &ps); return 0 ;
这样就可以绘制出一个三角形
画笔和画刷 1.画笔就是划线的,画刷就是可以覆盖 一块区域
创建和使用画笔 1.上面的绘图使用的是Windows的默认画笔,也就是宽度为1个像素,颜色为黑色的画笔。
创建画笔的API函数为 CreatePen:
1 2 3 4 5 6 HPEN CreatePen ( int nPenStyle, int nWidth, COLORREF crColor ) ;
1.画笔样式 nPenStyle 有7种取值:
宏定义
宏定义对应的值
说明
PS_SOLID
0
实线
PS_DASH
1
虚线(段线),要求画笔宽度 <= 1
PS_DOT
2
点线,要求画笔宽度 <= 1
PS_DASHDOT
3
线、点,要求画笔宽度 <= 1
PS_DASHDOTDOT
4
线、点、点,要求画笔宽度 <= 1
PS_NULL
5
不可见
PS_INSIDEFRAME
6
实线,但画笔宽度是向里扩展的
2.画笔宽度 :nWidth 指逻辑宽度。iWidth为 0 则意味着画笔宽度为一个像素。如果画笔样式为点线或者虚线,同时又指定一个大于 1 的画笔宽度,那么Windows将使用实线画笔来代替。
3.画笔的颜色 crColor 可以直接使用 RGB 颜色。
GDI对象 画笔、画刷、字体等被称为GDI对象。可以供 GDI 函数使用。新创建的 GDI 对象必须通过 SelectObject
函数选入设备环境才能使用。
1 2 3 4 HGDIOBJ SelectObject ( HDC hdc, HGDIOBJ ho ) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc; static HPEN hPen; switch (message){ case WM_CREATE: hPen = CreatePen(PS_SOLID, 2 , RGB(255 , 0 , 0 )); break ; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); SelectObject(hdc, hPen); MoveToEx(hdc, 150 , 150 , NULL ); LineTo(hdc,200 , 60 ); LineTo(hdc, 250 , 150 ); LineTo(hdc, 150 , 150 ); EndPaint(hWnd, &ps); break ; case WM_DESTROY: DeleteObject(hPen); PostQuitMessage(0 ); break ; default : return DefWindowProc(hWnd, message, wParam, lParam); } return 0 ; }
上述代码会画一个红色的三角形。
画笔最好在 WM_CREATE 事件中创建,因为当应用程序运行时会频繁地触发 WM_PAINT 事件,比如窗口被覆盖后再显示、窗口被拖动、窗口被拉伸等,每次都需要重新创建画笔,浪费资源
创建和使用画刷 Windows API 中有两个函数可以用来创建画刷。
1.CreateSolidBrush 函数可以用来创建一个指定颜色的实心画刷
1 HBRUSH CreateSolidBrush ( COLORREF crColor ) ;
2.CreateHatchBrush 函数可以用来创建一个指定颜色的含有特定阴影样式的画刷,原型为:
1 2 3 4 HBRUSH CreateHatchBrush ( int fnStyle, COLORREF crColor ) ;
HS_BDIGONAL: 45度向上,自左至右的阴影(///)
HS_CROSS: 表示水平直线和垂直直线交叉阴影(+++)
HS_DIAGCROSS: 45度交叉阴影(XXX)
HS_FDIAGONAL: 45度向下自左至右的阴影(\)
HS_HORIZONTAL:水平阴影(—-)
HS_VERTICAL: 垂直阴影
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc; static HBRUSH hSolidBrush; static HBRUSH hHatchBrush; switch (message){ case WM_CREATE: hSolidBrush = CreateSolidBrush(RGB(0 , 0 , 255 )); hHatchBrush = CreateHatchBrush(HS_DIAGCROSS,RGB(0 ,255 ,0 )); break ; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); SelectObject(hdc, hSolidBrush); Rectangle(hdc, 0 , 0 , 200 , 100 ); SelectObject(hdc, hHatchBrush); Ellipse(hdc,0 ,100 ,200 ,200 ); EndPaint(hWnd, &ps); break ; case WM_DESTROY: DeleteObject(hSolidBrush); DeleteObject(hHatchBrush); PostQuitMessage(0 ); break ; default : return DefWindowProc(hWnd, message, wParam, lParam); } return 0 ; }
如上程序可以生成带颜色的长方形和矩形
Windows static控件 1.实际开发中一般使用静态文本框控件来输出文本。静态文本框是Windows 的一种标准控件 ,可以用来在窗口上显示一段文本,并且文本容易受到控制。除了静态文本框,Windows的标准控件还有很多种,例如按钮、下拉菜单、单选按钮、复选框等。
2.控件是子窗口 ,创建时必须指定父窗口,这样控件才能有“归属”。
1 2 3 4 5 6 7 8 9 10 11 12 13 HWND CreateWindow ( LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam ) ;
lpWindowName 应传入窗口的标题,若你希望创建控件,则应传入控件的文本。
写起来程序和上面的程序差不多,但是有一个地方就是`CreateWindow 函数传入的倒数第二个参数为 hInst
,表示当前程序的实例句柄。
当前实例句柄是通过 WinMain 函数的参数传入的,所以必须要定义一个全局变量 hInst,然后在 WinMain 中给它赋值后才能使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <windows.h> HINSTANCE hInst; int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow ) { hInst = hInstance; }
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;HINSTANCE hInst; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szClassName[] = L"HelloWinStatic" ; HWND hwnd; MSG msg; WNDCLASS wndclass; hInst = hInstance; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL , IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL , IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szClassName; RegisterClass(&wndclass); hwnd = CreateWindow( szClassName, TEXT("Welcome2Static" ), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500 , 500 , NULL , NULL , hInstance, NULL ); ShowWindow(hwnd, iCmdShow); UpdateWindow(hwnd); while (GetMessage(&msg, NULL , 0 , 0 )) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc ( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { PAINTSTRUCT ps; HDC hdc; HWND hStatic; switch (message) { case WM_CREATE: hStatic = CreateWindow( L"static" , L"C语言中文网" , WS_CHILD | WS_VISIBLE | WS_BORDER | SS_CENTER | SS_CENTERIMAGE , 20 , 20 , 200 , 100 , hWnd, (HMENU)1 , hInst, NULL ); break ; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); EndPaint(hWnd, &ps); break ; case WM_DESTROY: PostQuitMessage(0 ); break ; } return DefWindowProc(hWnd, message, wParam, lParam); }
获取、修改控件文本 1.GetWindowText 函数用于将指定窗口的标题文本(如果存在)拷贝到一个缓存区内;如果指定的窗口是一个控件,则拷贝控件的文本。
1 2 3 4 5 Int GetWindowText ( HWND hWnd, LPTSTR lpString, Int nMaxCount ) ;
2.类似的函数:SetWindowText
,用来设置窗口标题或者控件文本。
1 2 3 4 BOOL SetWindowText ( HWND hwnd, LPCTSTR lpString ) ;
如下代码显示鼠标点击次数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;HINSTANCE hInst; int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow ) { static TCHAR szClassName[] = TEXT("Win32Demo" ); HWND hwnd; MSG msg; WNDCLASS wndclass; hInst = hInstance; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL , IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL , IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szClassName; RegisterClass(&wndclass); hwnd = CreateWindow( szClassName, TEXT("Welcome" ), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500 , 300 , NULL , NULL , hInstance, NULL ); ShowWindow(hwnd, iCmdShow); UpdateWindow(hwnd); while (GetMessage(&msg, NULL , 0 , 0 )) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc ( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { PAINTSTRUCT ps; HDC hdc; static int iClick = 0 ; static TCHAR szTextBuf[20 ]; static HWND hStatic; switch (message) { case WM_CREATE: hStatic = CreateWindow( L"static" , L"LALALALALALALA" , WS_CHILD | WS_VISIBLE | WS_BORDER | SS_CENTER | SS_CENTERIMAGE , 20 , 20 , 200 , 100 , hWnd, (HMENU)1 , hInst, NULL ); break ; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); EndPaint(hWnd, &ps); break ; case WM_RBUTTONDOWN: iClick++; wsprintf(szTextBuf, TEXT("%d" ), iClick); SetWindowText(hStatic, szTextBuf); break ; case WM_DESTROY: PostQuitMessage(0 ); break ; } return DefWindowProc(hWnd, message, wParam, lParam); }
1.对于窗口函数中的变量,如果是在 WM_CREATE 消息中赋值,但在其他消息中使用,那么一般声明为静态变量,这样下次执行窗口函数时依然有效。尤其是这种窗口会不断变化的函数
Windows CreateFont:创建自己的字体 创建字体使用 CreateFont 函数,它的原型是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 HFONT CreateFont ( int cHeight, int cWidth, int cEscapement, int cOrientation, int cWeight, DWORD bItalic, DWORD bUnderline, DWORD bStrikeOut, DWORD iCharSet, DWORD iOutPrecision, DWORD iClipPrecision, DWORD iQuality, DWORD iPitchAndFamily, LPCSTR pszFaceName ) ;
注意:字体也是一种 GDI 对象,使用完后也要在 WM_DESTROY 消息中删除。
1.创建完字体后并不能立即使用,还需要手动触发 WM_SETFONT 消息(设置字体的消息),让Windows 将当前字体设置为我们创建的字体。
2.发送消息使用 SendMessage 函数,它可以让我们在必要时主动向窗口发送各种消息,原型为:
1 2 3 4 5 6 LRESULT SendMessage ( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) ;
1.按钮既是标准控件,也是子窗口,窗口类名是button
。
2.Button 控件样式以BS
开头
样式
说明
BS_LEFT
文本居左。
BS_RIGHT
文本居右。
BS_CENTER
文本水平居中(默认为 BS_CENTER)。
BS_BOTTOM
文本位于按钮底部。
BS_TOP
文本位于按钮顶部。
BS_VCENTER
文本垂直居中(默认为 BS_VCENTER)。
BS_FLAT
扁平样式。默认情况下按钮具有3D阴影效果。
BS_MULTILINE
允许显示多行文本。也就是说当文本过长时会自动换行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 LRESULT CALLBACK WndProc ( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; static HFONT hFont; static HWND hBtn; switch (message){ case WM_CREATE: hFont = CreateFont( -15 , -7.5 , 0 , 0 , 400 , FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY, FF_DONTCARE, TEXT("微软雅黑" ) ); hBtn = CreateWindow( TEXT("button" ), TEXT("这是按钮" ), WS_CHILD | WS_VISIBLE | WS_BORDER | BS_FLAT, 30 , 20 , 150 , 50 , hWnd, (HMENU)2 , hInst, NULL ); SendMessage(hBtn, WM_SETFONT, (WPARAM)hFont, NULL ); break ; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); EndPaint(hWnd, &ps); break ; case WM_DESTROY: DeleteObject(hFont); PostQuitMessage(0 ); break ; default : return DefWindowProc(hWnd, message, wParam, lParam); } return 0 ; }
如上代码就设置了一个内容是“这是按钮”的控件
关于如何捕获到按钮的消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 LRESULT CALLBACK WndProc ( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; static HFONT hFont; static HWND hBtn; static HWND hStatic; switch (message){ case WM_CREATE: hFont = CreateFont( -15 , -7.5 , 0 , 0 , 400 , FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY, FF_DONTCARE, TEXT("微软雅黑" ) ); hStatic = CreateWindow( TEXT("static" ), TEXT("你好,欢迎来到C语言中文网" ), WS_CHILD | WS_VISIBLE | WS_BORDER , 30 , 20 , 150 , 80 , hWnd, (HMENU)1 , hInst, NULL ); hBtn = CreateWindow( TEXT("button" ), TEXT("点击这里试试" ), WS_CHILD | WS_VISIBLE | WS_BORDER | BS_FLAT, 30 , 110 , 150 , 50 , hWnd, (HMENU)2 , hInst, NULL ); SendMessage(hStatic,WM_SETFONT,(WPARAM)hFont,NULL ); SendMessage(hBtn, WM_SETFONT, (WPARAM)hFont, NULL ); break ; case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); switch (wmId){ case 2 : SetWindowText( hStatic, TEXT("你点击了下面的按钮" ) ); break ; default : return DefWindowProc(hWnd, message, wParam, lParam); } break ; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); EndPaint(hWnd, &ps); break ; case WM_DESTROY: DeleteObject(hFont); PostQuitMessage(0 ); break ; default : return DefWindowProc(hWnd, message, wParam, lParam); } return 0 ; }
Windows edit控件(编辑框控件) 1.相当于是控制台程序的scanf函数
2.编辑框控件的窗口类名是edit
。除了子窗口常用的风格 WS_CHILD、WS_VISIBLE、WS_BORDER 外,edit 控件也有自己的风格,都是以ES
开头。
风格
说明
ES_AUTOHSCROLL
当输入文字超出横向显示范围时自动滚动(一般必选)。
ES_AUTOVSCROLL
当输入文字超出纵向显示范围时自动滚动。
ES_CENTER
文本居中对齐。
ES_LEFT
文本左对齐(默认)。
ES_RIGHT
文本右对齐。
ES_MULTILINE
是否允许多行输入。
ES_PASSWORD
是否为密码输入框,如果指明该风格则输入的文字显示为“* ”。
ES_READONLY
是否为只读。禁止用户输入或修改编辑控件中的文本。
ES_UPPERCASE
显示大写字符。
ES_LOWERCASE
显示小写字符。
ES_LOWERCASE
将用户输入到编辑控件的字符全部转换为小写。
ES_UPPERCASE
将用户输入到编辑控件的字符全部转换为大写。
ES_MULTILINE
指明了一个多行编辑控件(缺省的是单行的)。 1) 如果指定了ES_AUTOVSCROLL风格,编辑控件将显示尽可能多的文本,并且当用户按下ENTER键时会自动地垂直滚动文本。 2) 如果没有指定ES_AUTOVSCROLL风格,则编辑控件将显示尽可能多的行,如果在按下ENTER键却没有更多的行要显示的话,就发出蜂鸣声。 3) 如果指定了ES_AUTOHSCROLL风格,当光标到达控件的右边时,多行编辑控件会自动地水平滚动文本。如果要开始一个新行,用户必须按下ENTER键。 4) 如果没有指定ES_AUTOHSCROLL风格,控件会在有必要时自动将单词折合到下一行的开始。如果按下ENTER键,则另起一行。折回单词的位置是由窗口的大小决定的。如果窗口的大小发生改变,折回单词的位置也会反生改变,将会重新显示文本。
实例程序,登录框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;HINSTANCE hInst; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szClassName[] = TEXT("HelloWin" ); HWND hwnd; MSG msg; WNDCLASS wndclass; hInst = hInstance; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon(NULL , IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL , IDC_ARROW); wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szClassName; RegisterClass(&wndclass); hwnd = CreateWindow( szClassName, TEXT("Welcome" ), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500 , 300 , NULL , NULL , hInstance, NULL ); ShowWindow(hwnd, iCmdShow); UpdateWindow(hwnd); while (GetMessage(&msg, NULL , 0 , 0 )) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; static HFONT hFont; static HWND hLabUsername; static HWND hLabPassword; static HWND hEditUsername; static HWND hEditPassword; static HWND hBtnLogin; TCHAR szUsername[100 ]; TCHAR szPassword[100 ]; TCHAR szUserInfo[200 ]; switch (message) { case WM_CREATE: hFont = CreateFont(-14 , -7 , 0 , 0 , 400 , FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY, FF_DONTCARE, TEXT("微软雅黑" ) ); hLabUsername = CreateWindow(TEXT("static" ), TEXT("用户名:" ), WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE | SS_RIGHT , 0 , 20 , 70 , 26 , hWnd , (HMENU)1 , hInst , NULL ); hLabPassword = CreateWindow(TEXT("static" ), TEXT("密码:" ), WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE | SS_RIGHT , 0 , 56 , 70 , 26 , hWnd, (HMENU)2 , hInst, NULL ); hEditUsername = CreateWindow(TEXT("edit" ), TEXT("" ), WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL , 80 , 20 , 200 , 26 , hWnd, (HMENU)3 , hInst, NULL ); hEditPassword = CreateWindow(TEXT("edit" ), TEXT("" ), WS_CHILD | WS_VISIBLE | WS_BORDER | ES_PASSWORD | ES_AUTOHSCROLL , 80 , 56 , 200 , 26 , hWnd, (HMENU)4 , hInst, NULL ); hBtnLogin = CreateWindow(TEXT("button" ), TEXT("登录" ), WS_CHILD | WS_VISIBLE | WS_BORDER | BS_FLAT, 80 , 92 , 200 , 30 , hWnd, (HMENU)5 , hInst, NULL ); SendMessage(hLabUsername, WM_SETFONT, (WPARAM)hFont, NULL ); SendMessage(hLabPassword, WM_SETFONT, (WPARAM)hFont, NULL ); SendMessage(hEditUsername, WM_SETFONT, (WPARAM)hFont, NULL ); SendMessage(hEditPassword, WM_SETFONT, (WPARAM)hFont, NULL ); SendMessage(hBtnLogin, WM_SETFONT, (WPARAM)hFont, NULL ); break ; case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); switch (wmId) { case 5 : GetWindowText(hEditUsername, szUsername, 100 ); GetWindowText(hEditPassword, szPassword, 100 ); wsprintf(szUserInfo, TEXT("C语言中文网提示:\r\n您的用户账号:%s\r\n您的用户密码:%s" ), szUsername, szPassword); MessageBox(hWnd, szUserInfo, TEXT("信息提示" ), MB_ICONINFORMATION); break ; default : return DefWindowProc(hWnd, message, wParam, lParam); } break ; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); EndPaint(hWnd, &ps); break ; case WM_DESTROY: DeleteObject(hFont); PostQuitMessage(0 ); break ; default : return DefWindowProc(hWnd, message, wParam, lParam); } return 0 ; }
修改static控件背景颜色和文字颜色 1.当 static 控件或具有 ES_READONLY 风格的 edit 控件被绘制时,会向父窗口发送 WM_CTLCOLORSTATIC 消息。如果我们在窗口过程中处理该消息,就必须返回一个画刷句柄,Windows 会使用该画刷来绘制控件背景(子窗口背景)。
所以,改变空间颜色的方法就是处理 WM_CTLCOLORSTATIC 消息。
修改文字背景颜色 1.修改文字背景颜色使用 SetBkColor 函数,它的原型为:
1 2 3 4 COLORREF SetBkColor ( HDC hdc, COLORREF crColor ) ;
2.修改文字前景色(文字颜色)使用 SetTextColor 函数,它的原型为:
1 2 3 4 COLORREF SetTextColor ( HDC hdc, COLORREF crColor ) ;
这里我们修改的是 static 控件中的文本颜色,所以需要获取 static 控件的设备环境句柄。
非常巧妙的是,发送 WM_CTLCOLORSTATIC 消息时,wParam 参数表示的就是 static 控件的设备环境句柄(lParam 表示控件句柄)。
透明背景 实现透明背景需要设置文本背景颜色透明,同时返回没有颜色的画刷。
1.SetBkMode 函数可以用来设置文本的背景模式,它的原型为:
1 2 3 4 int SetBkMode ( HDC hdc, int iBkMode ) ;
iBkMode 有下面两种取值:
取值
说明
OPAQUE
使用当前背景颜色来填充背景。
TRANSPARENT
背景透明。
2.GetStockObject (NULL_BRUSH); 语句可以返回没有颜色的画刷。示例代码:
1 2 3 4 5 case WM_CTLCOLORSTATIC: hdcStatic = (HDC)wParam; SetTextColor( hdcStatic, RGB(0x41 , 0x96 , 0x4F ) ); SetBkMode(hdcStatic, TRANSPARENT); return (INT_PTR)GetStockObject(NULL_BRUSH);
Windows单选按钮、复选框、分组框控件 单选按钮和复选框都是一种特殊的按钮,窗口类名称都是button
单选按钮的样式为BS_AUTORADIOBUTTON
,复选框的样式为BS_AUTOCHECKBOX
。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 CreateWindow( TEXT("button" ), TEXT("单选按钮" ), WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON, 235 , 40 , 100 , 26 , hWnd, (HMENU)7 , hInst, NULL ); CreateWindow( TEXT("button" ), TEXT("复选框" ), WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTOCHECKBOX, 95 , 70 , 50 , 26 , hWnd, (HMENU)9 , hInst, NULL );
想为单选按钮分组 ,可以增加WS_GROUP
样式。设置了 WS_GROUP 样式的单选框为一组中的首元素,随后的所有单选按钮都和它在同一组,直到下一个设置了 WS_GROUP 样式的单选按钮。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc; static HFONT hFont; static HWND labSex; static HWND radioMale; static HWND radioFemale; static HWND labMarriage; static HWND radioMarried; static HWND radioSingle; static HWND radioSecrecy; static HWND labPet; static HWND checkboxDog; static HWND checkboxCat; static HWND checkboxFish; static HWND checkboxOther; switch (message){ case WM_CREATE: hFont = CreateFont( -14 , -7 , 0 , 0 , 400 , FALSE, FALSE, FALSE,DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY, FF_DONTCARE, TEXT("微软雅黑" ) ); labSex = CreateWindow( TEXT("static" ), TEXT("你的性别:" ), WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE | SS_RIGHT, 10 , 10 , 80 , 26 , hWnd, (HMENU)1 , hInst, NULL ); radioMale = CreateWindow( TEXT("button" ), TEXT("男" ), WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON | WS_GROUP, 95 , 10 , 50 , 26 , hWnd, (HMENU)2 , hInst, NULL ); radioFemale = CreateWindow( TEXT("button" ), TEXT("女" ), WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON, 150 , 10 , 50 , 26 , hWnd, (HMENU)2 , hInst, NULL ); labMarriage = CreateWindow( TEXT("static" ), TEXT("婚姻状况:" ), WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE | SS_RIGHT, 10 , 40 , 80 , 26 , hWnd, (HMENU)4 , hInst, NULL ); radioMarried = CreateWindow( TEXT("button" ), TEXT("已婚" ), WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON | WS_GROUP, 95 , 40 , 65 , 26 , hWnd, (HMENU)5 , hInst, NULL ); radioSingle = CreateWindow( TEXT("button" ), TEXT("未婚" ), WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON, 165 , 40 , 65 , 26 , hWnd, (HMENU)6 , hInst, NULL ); radioSecrecy = CreateWindow( TEXT("button" ), TEXT("保密" ), WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON, 235 , 40 , 100 , 26 , hWnd, (HMENU)7 , hInst, NULL ); labPet = CreateWindow( TEXT("static" ), TEXT("你的宠物:" ), WS_CHILD | WS_VISIBLE | SS_CENTERIMAGE | SS_RIGHT, 10 , 70 , 80 , 26 , hWnd, (HMENU)8 , hInst, NULL ); checkboxDog = CreateWindow( TEXT("button" ), TEXT("狗" ), WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTOCHECKBOX, 95 , 70 , 50 , 26 , hWnd, (HMENU)9 , hInst, NULL ); checkboxCat = CreateWindow( TEXT("button" ), TEXT("猫" ), WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTOCHECKBOX, 150 , 70 , 50 , 26 , hWnd, (HMENU)10 , hInst, NULL ); checkboxFish = CreateWindow( TEXT("button" ), TEXT("鱼" ), WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTOCHECKBOX, 205 , 70 , 50 , 26 , hWnd, (HMENU)11 , hInst, NULL ); checkboxOther = CreateWindow( TEXT("button" ), TEXT("其他" ), WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTOCHECKBOX, 260 , 70 , 65 , 26 , hWnd, (HMENU)11 , hInst, NULL ); SendMessage(labSex, WM_SETFONT, (WPARAM)hFont, NULL ); SendMessage(radioMale, WM_SETFONT, (WPARAM)hFont, NULL ); SendMessage(radioFemale, WM_SETFONT, (WPARAM)hFont, NULL ); SendMessage(labMarriage, WM_SETFONT, (WPARAM)hFont, NULL ); SendMessage(radioMarried, WM_SETFONT, (WPARAM)hFont, NULL ); SendMessage(radioSingle, WM_SETFONT, (WPARAM)hFont, NULL ); SendMessage(radioSecrecy, WM_SETFONT, (WPARAM)hFont, NULL ); SendMessage(labPet, WM_SETFONT, (WPARAM)hFont, NULL ); SendMessage(checkboxDog, WM_SETFONT, (WPARAM)hFont, NULL ); SendMessage(checkboxCat, WM_SETFONT, (WPARAM)hFont, NULL ); SendMessage(checkboxFish, WM_SETFONT, (WPARAM)hFont, NULL ); SendMessage(checkboxOther, WM_SETFONT, (WPARAM)hFont, NULL ); break ; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); EndPaint(hWnd, &ps); break ; case WM_DESTROY: PostQuitMessage(0 ); break ; default : return DefWindowProc(hWnd, message, wParam, lParam); } return 0 ; }
分组框控件 分组框控件也是一种特殊的按钮,它的样式为BS_GROUPBOX
,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 hGroupSex = CreateWindow( TEXT("button" ), TEXT("你的性别" ), WS_CHILD | WS_VISIBLE | BS_GROUPBOX, 20 , 20 , 300 , 70 , hWnd, (HMENU)1 , hInst, NULL ); hRadioMale = CreateWindow( TEXT("button" ), TEXT("男" ), WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON , 15 , 30 , 50 , 26 , hGroupSex , (HMENU)2 , hInst, NULL ); hRadioFemale = CreateWindow( TEXT("button" ), TEXT("女" ), WS_CHILD | WS_VISIBLE | BS_LEFT | BS_AUTORADIOBUTTON, 80 , 30 , 50 , 26 , hGroupSex, (HMENU)3 , hInst, NULL );
注意父子窗口关系:需要注意的是:单选按钮的父窗口为分组框控件 hGroupSex,而不是顶级窗口 hWnd。
判断单选按钮和复选框是否被选中 对于一组单选按钮,只有一个选项能被选中,最好的办法是将这组按钮作为参数传入一个函数,通过函数返回值判断哪个按钮被选中了。
但遗憾的是,在Windows中不能一次性获得一组按钮的选中状态,只能一个一个地遍历。复选框也是如此。
解决:可以通过SendMessage
函数发送BM_GETCHECK
消息来获取按钮的选中状态,返回 BST_CHECKED 表示按钮被选中,返回 BST_UNCHECKED 是未被选中。
SendMessage 原型为:
1 2 3 4 5 6 LRESULT WINAPI SendMessage ( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam ) ;
注意:发送 BM_GETCHECK 消息时,wParam 和 lParam 两个参数必须都为 0。
如果被选中,那么会获取按钮文本并保存到缓冲区:
1 2 3 4 if ( SendMessage(hCheckBox, BM_GETCHECK, 0 , 0 ) == BST_CHECKED ){ GetWindowText(hCheckBox, szBuffer, 10 ); }
程序部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; static TCHAR szBufSex[10 ]; static TCHAR szBufMarriage[10 ]; static TCHAR szBufPet[20 ]; static TCHAR szBufSubmit[100 ]; static TCHAR szBufTmp[10 ]; static HWND btnSubmit; switch (message){ case WM_CREATE: btnSubmit = CreateWindow(TEXT("button" ), TEXT("提 交" ), WS_CHILD | WS_VISIBLE | WS_BORDER | BS_FLAT, 95 , 110 , 200 , 36 , hWnd, (HMENU)13 , hInst, NULL ); SendMessage(btnSubmit, WM_SETFONT, (WPARAM)hFont, NULL ); break ; case WM_COMMAND: wmId = LOWORD(wParam); wmEvent = HIWORD(wParam); if ( wmEvent== BN_CLICKED){ switch (wmId){ case 2 : case 3 : GetWindowText((HWND)lParam, szBufSex, 10 ); break ; case 5 : case 6 : case 7 : GetWindowText((HWND)lParam, szBufMarriage, 10 ); break ; case 9 : case 10 : case 11 : case 12 : memset (szBufPet, 0 , sizeof (szBufPet)); if ( SendMessage(checkboxDog, BM_GETCHECK, 0 , 0 ) == BST_CHECKED ){ GetWindowText(checkboxDog, szBufTmp, 10 ); wsprintf(szBufPet, TEXT("%s %s" ), szBufPet, szBufTmp); } if ( SendMessage(checkboxCat, BM_GETCHECK, 0 , 0 ) == BST_CHECKED ){ GetWindowText(checkboxCat, szBufTmp, 10 ); wsprintf(szBufPet, TEXT("%s %s" ), szBufPet, szBufTmp); } if ( SendMessage(checkboxFish, BM_GETCHECK, 0 , 0 ) == BST_CHECKED ){ GetWindowText(checkboxFish, szBufTmp, 10 ); wsprintf(szBufPet, TEXT("%s %s" ), szBufPet, szBufTmp); } if ( SendMessage(checkboxOther, BM_GETCHECK, 0 , 0 ) == BST_CHECKED ){ GetWindowText(checkboxOther, szBufTmp, 10 ); wsprintf(szBufPet, TEXT("%s %s" ), szBufPet, szBufTmp); } break ; case 13 : wsprintf(szBufSubmit, TEXT("你的性别:%s\n婚姻状况:%s\r\n你的宠物:%s" ), szBufSex, szBufMarriage, szBufPet); MessageBox(hWnd, szBufSubmit, TEXT("信息提示" ), MB_ICONINFORMATION); break ; default : return DefWindowProc(hWnd, message, wParam, lParam); } } return DefWindowProc(hWnd, message, wParam, lParam); } return 0 ; }
1.用户点击控件时会产生 WM_COMMAND 消息,这时 wParam 参数的低16位表示控件ID,高16位表示控件通知码,lParam 表示控件句柄。
2.对于单选按钮,根据控件ID判断当前按钮是否被选中,是的话就获取按钮文本,保存到缓冲区。
3.对于复选框,根据控件ID判断当前复选框是否被选中,是的话就需要遍历所有复选框 ,检测它们的选中状态,并将所有被选中的复选框的文本保存到缓冲区。
PS:单选按钮可以不发送 BM_GETCHECK 消息,因为一组中只能有一个被选中,就是当前被点击的这个。而复选框就不同了,一组中可以有多个被选中,所以必须发送 BM_GETCHECK 消息检测其他复选框的选中状态。