终于到了令人激动的时刻, 尽管在前一阶段的学习中我们已经学习了如何在客户区中绘制简单的图形以及如何使用键盘和鼠标, 但是距离 Windows意义上的软件 似乎还是有点遥远, 而今天, 我们要做的就是将这个距离再缩短一大步! 这阶段要学习的就是 子窗口控件 的使用。
在其他一些 Windows应用软件上我们经常能够看到一些大致相同的按钮、复选框、组合框、列表框等控件, 这些控件很有可能就是使用 标准子窗口控件 来实现的。
一、子窗口的创建
在讲解 "标准子窗口控件" 的使用之前我们首先应该知道如何去创建一个子窗口, 因为这些 "子窗口控件" 实际上都是通过创建一个子窗口的形式来进行创建的, 因此我们应该把理解的重点放在 "子窗口" 上, 而不是 "控件" 上。
子窗口的创建可以将整个客户区划分为多个矩形区域, 并且每个子窗口都可以有自己的句柄、窗口过程和客户区, 每个子窗口过程只接收与自身窗口有关的鼠标消息、鼠标消息的参数 lParam 中包含的坐标是相对于 子窗口 客户区的左上角的。简单的说, 子窗口具有一个普通窗口的一切特性。
子窗口的创建同样是使用 CreateWindow 函数进行创建的, 下面我们通过一个示例来认识这个过程:
代码已折叠, 点击展开:
View Code
[ 代码: Demo_01_CreateChildWindow.c ]
尽管这段代码有将近100行, 但是我相信有了前面的基础, 这段代码也是能够轻松理解的。
这段代码演示的是在父窗口的(10, 10)位置处创建一个大小为 200x200 的子窗口, 效果如下:
首先从整体上把握下这段代码:
第一步: 声明父窗口回调函数和子窗口的回调函数:
LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ; LRESULT CALLBACK ChildWndProc( HWND, UINT, WPARAM, LPARAM ) ; //子窗口窗口过程
第二步: 定义WinMain函数, 在WinMain函数中建立父窗口的 wndclass 窗口类并注册:
if( !RegisterClass(&wndclass) ) { MessageBox( NULL, TEXT("无法注册窗口类!"), szAppName, MB_OK | MB_ICONERROR ) ; return 0 ; }
第三步: 改变父窗口 wndclass 类中的部分属性, 使其成为子窗口的 wndclass, 并注册子窗口窗口类:
wndclass.lpszClassName = szChildClass ; wndclass.cbWndExtra = sizeof(long) ; wndclass.lpfnWndProc = ChildWndProc ; RegisterClass( &wndclass ) ; //注册子窗口窗口类
第四步: 显示窗口、进入消息循环:
ShowWindow( hwnd, iCmdShow ) ; UpdateWindow( hwnd ) ; while( GetMessage(&msg, NULL, 0, 0) ) { TranslateMessage( &msg ) ; DispatchMessage( &msg ) ; }
第五步: 定义父窗口回调函数 WndProc, 在处理 WM_CREATE 消息时创建子窗口:
case WM_CREATE: //在接收到 WM_CREATE 消息时创建一个子窗口 childHwnd = CreateWindow( szChildClass, TEXT("子窗口"), WS_CHILDWINDOW | WS_VISIBLE | WS_OVERLAPPEDWINDOW, 10, 10, //在父窗口客户区(0, 0)位置创建一个子窗口 200, 200, //子窗口的大小为0x0 hwnd, //父窗口句柄 (HMENU)1, //子窗口ID (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE ), NULL ) ; return 0 ;
第六步: 定义子窗口回调函数 ChildWndProc:
LRESULT CALLBACK ChildWndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) { switch(message) { case WM_LBUTTONDOWN: //处理子窗口的 WM_LBUTTONDOWN 消息 RedrawWindow(hwnd, NULL, NULL, RDW_INTERNALPAINT ); MessageBox( hwnd, TEXT("鼠标左键在子窗口客户区中按下!"), TEXT("子窗口消息"), MB_OK ); return 0 ; } return DefWindowProc( hwnd, message, wParam, lParam ) ; }
一些细节把握:
1>. 全局的子窗口类名
在第四行中的代码 TCHAR szChildClass[] = TEXT("ChildClass") ; 我们把子窗口类名称设为全局这是因为在 WinMain 函数中和父窗口的回调函数中都需要用到它的名字。
2>. 在注册子窗口的窗口类时, 没有再重新定义一个 WNDCLASS 类型的变量, 而是简单的复用了一下父窗口中的 wndclass, 其中有3个成员与父窗口不同:
lpszClassName, 即子窗口类的名称 ;
cbWndExtra 被设置成一个 long 型数据所占的存储单元大小(4字节), 这个成员通知 Windows 在内部结构中给基于这个窗口类的每个窗口预留4个字节的额外存储空间, 以用来给用户为每个窗口保存不同的信息 ;
lPfnWndProc 成员被设置成 ChildWndProc, 表示子窗口类的窗口过程 。
3>. 创建子窗口时的 (HINSTANCE) GetWindowLong(hwnd, RDW_ERASE )
实际上 CreateWindow 函数在创建子窗口时需要 hInstance 句柄, 而GetWindowLong函数的作用就是获得有关指定窗口的信息, 函数也获得在额外窗口内存中指定偏移位地址的32位度整型值, GetWindowLong的原型:
LONG GetWindowLong( HWND hwnd, int nIndex ) ;
nIndex 为索引值, 它可以是以下标识符之一:
GWL_EXSTYLE //获得扩展窗口风格 GWL_STYLE //获得窗口风格 GWL_WNDPROC //获得窗口过程的地址 GWL_HINSTANCE //获得应用事例的句柄 GWL_HWNDPARENT //获得父窗口句柄 GWL_ID //获得窗口标识 GWL_USERDATA //获得与窗口有关的32位值。 DWL_DLGPROC //获得对话框过程的地址, 或一个代表对话框过程的地址的句柄 DWL_MSGRESULT //获得在对话框过程中一个消息处理的返回值 DWL_USER //获得应用程序私有的额外信息
在子窗口的窗口过程的处理上, 我们仅仅处理了一个 WM_LBUTTONDWON 消息, 当鼠标在子窗口客户区中按下左键时就弹出一个对话框告诉用户"鼠标左键在子窗口客户区中按下!"。
这些就是创建一个子窗口的演示。使用子窗口的程序设计有助于程序的结构化和模式化, 提高代码的复用率。
如果你已经将上面示例中的代码理解的话, 那么恭喜你! 你已经又向成功迈向了一大步, 因为标准子窗口控件的使用比我们自己创建子窗口还要简单!
二、使用标准子窗口控件的按钮类
按钮类: 按钮类不仅仅是指我们所常见的按钮控件, 他是一类控件的统称, 包括: 按钮、复选框、单选按钮、组合框。以创建一个按钮作为示例, 创建一个按钮并不需要我们再去定义一个WNDCLASS类再去注册它, 也不需要再去为这个控件指定子窗口过程函数, 因为这些已经在一些相关的头文件中给完成了, 我们只需要做相关的调用即可:
case WM_CREATE: //在接收到 WM_CREATE 消息时创建一个按钮 btnHwnd = CreateWindow( TEXT("button"), TEXT("创建按钮"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, //按钮样式 10, 10, //在父窗口客户区(10, 10)位置创建一个按钮 100, 30, //按钮的大小为100x30 hwnd, //父窗口句柄 (HMENU)1, //按钮控件ID (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE ), NULL ) ; return 0 ;
已折叠, 点击展开全部:
View Code - CreateButton
[代码: Demo_02_CreateButton.c]
这样就完成了一个按钮的创建, 效果:
尽管这个按钮看起来还有一些问题, 比如为什么按钮的风格那么难看, 以及按钮上的文字也不怎么美观, 没关系, 这些都是可以解决的, 按钮的风格问题等把标准控件学习完毕后一次性解决, 这里先把目前我们需要解决的事处理掉再关注这些。
当尝试点击这个按钮时只是觉得他向下凹了一下, 然后又恢复了, 好像什么也没做一样, 是的, 实际上他做了很多事情, 比如这个凹陷后又恢复的效果就是系统帮我们完成的, 庆幸的是这个效果是windows帮我们完成的, 要不然我们又要花费更多的代码来完成这个效果。
1>. 子窗口传递信息给父窗口
如果我们要使用这个按钮, 我们只需要捕获这个按钮向父窗口发送来的消息即可, 这个消息就是 WM_COMMAND, 每当用户在子窗口上进行一些操作时, 系统就会像父窗口发来 WM_COMMAND 这个消息, 通过 wParam 和 lParam 中的值我们还可以进一步的了解用户进行的是什么操作。
WM_COMMAND 消息中 wParam 和 lParam 值含义如下:
LOWORD(wParam) | 子窗口ID |
HIWORD(wParam) | 通知码 |
lParam | 子窗口句柄 |
通知码的意义就是进一步给出消息的意思, 这些通知码
#define BN_CLICKED 0 //单击#define BN_PAINT 1 //被重绘#define BN_HILITE 2 //被选择#define BN_UNHILITE 3 //高亮被移除#define BN_DISABLE 4 //被禁用#define BN_DOUBLECLICKED 5 //被双击#define BN_PUSHED BN_HILITE#define BN_UNPUSHED BN_UNHILITE#define BN_DBLCLK BN_DOUBLECLICKED#define BN_SETFOCUS 6 //被设置焦点#define BN_KILLFOCUS 7 //失去焦点
实际上这些通知码大部分我们不会用到, 起码目前是这样, 通知码的6(BN_SETFOCUS) 和 7(BN_KILLFOCUS) 只有当按钮样式中包含 BS_NOTIFY 时才会被发送。
如果要处理按钮的一些消息, 只需像这样做既可:
case WM_COMMAND: switch(LOWORD(wParam)) //判断子窗口ID, 根据子窗口ID做出不同响应 { case 1: //处理ID为1的子窗口消息 switch(HIWORD(wParam)) //通过HIWORD(wParam)进一步判断消息类型 { case BN_CLICKED: //处理的按下通知码 [进行相关的处理] break ; /*case 其他通知码*/ } break ; /*case 其他子窗口ID*/ } return 0 ;
一个示例: 处理按钮的单击消息:
代码已折叠, 点击展开:
View Code
[代码: Demo_03_DealWithButtonMessage.c]
2>. 父窗口传递信息给子窗口
子窗口可以向父窗口发送消息, 父窗口同样也可以向子窗口发送消息, 在 WINUSER.H 中定义了8个专用于按钮的消息:
#define BM_GETCHECK 0x00F0 //获取单选按钮或复选框的状态#define BM_SETCHECK 0x00F1 //设置单选按钮或复选按钮的状态#define BM_GETSTATE 0x00F2 //获取按钮的状态#define BM_SETSTATE 0x00F3 //设置按钮的状态#define BM_SETSTYLE 0x00F4 //设置按钮样式#define BM_CLICK 0x00F5 //模拟用户点击一个按钮#define BM_GETIMAGE 0x00F6 //获取按钮图片的句柄#define BM_SETIMAGE 0x00F7 //设置按钮的新图像
使用 SendMessage函数即可将这些消息发送给子窗口, SendMessage 函数原型:
LRESULT SendMessage( HWND hWnd, //子窗口句柄 UINT Msg, //消息 WPARAM wParam, //wParam值 LPARAM lParam //lParam值 );
三、按钮类中的更多成员
要改变按钮的样式, 只需在创建子窗口时添加相关的按钮样式即可, 除了上面的演示的 BS_PUSHBUTTON 按钮样式, 还有更多的样式如下:
对这些按钮的相关常用操作:
1>. 按钮(Push Button)
BS_PUSHBUTTON 和 BS_DEFPUSHBUTTON 样式的按钮我们可以强制改变他的显示状态(正常或凹陷), 只需要向这个按钮发送个消息改变他的状态即可。
将按钮凹陷下去:
SendMessage( btnHwnd, BM_SETSTATE, 1, 0 ) ;
将按钮正常显示:
SendMessage( btnHwnd, BM_SETSTATE, 0, 0 ) ;
也可以通过一个 BM_GETSTATE 的消息获取按钮的当前状态, 被按下时返回 TRUE, 没有被按下时返回 FLASE。
2>. 复选框(Check Box )
①. 将复选框前打勾
SendMessage( btnHwnd, BM_SETCHECK, 1, 0 ) ;
②. 取消打勾
SendMessage( btnHwnd, BM_SETCHECK, 0, 0 ) ;
也可以通过一个 BM_GETCHECK 的消息获取按钮的当前状态, 被打勾时返回 非0或TRUE, 没有被打勾时返回 0或FLASE。
BS_3STATE 和 BS_3AUTOSTATE 类型的复选框还具有第三种状态, 打的勾为灰色, 当 wParam 消息被设为2时这个类型的复选框就会被设为这种状态。
3>. 单选按钮(Radio Button)
单选按钮的窗口样式为 BS_RADIOBUTTON 和 BS_AUTORADIOBUTTON, 后者只能用于对话框。单选按钮没有切换状态, 当用户按下一个单选按钮时即使再次按下, 他的样式也不会再变化, 使用 SendMessage 向单选按钮发送消息可改变其中的标记:
①. 标记
SendMessage( btnHwnd, BM_SETCHECK, 1, 0 ) ;
②. 取消标记
SendMessage( btnHwnd, BM_SETCHECK, 0, 0 ) ;
一些对窗口的通用操作(包括子窗口):
1>. 改变按钮文本
通过调用 SetWindowText 可以改变按钮中显示的文本:
SetWindowText( hwnd, pszString ) ;
对于普通的窗口来说, 该函数能改变窗口标题栏中的文本, 而对于按钮控件改变的即为显示的文本。
2>. 获取窗口的当前文本
iLength = GetWindowText( hwnd, pszBuffer, iMaxLength ) ;
iMaxLength用来限制缓冲区 pszBuffer 所能接收的最长文字数量。
3>. 隐藏窗口
ShowWindow( hwndChild, SW_HIDE ) ;
4>. 显示窗口
ShowWindow( hwndChild, SW_SHOWNORMAL ) ;
5>. 判断窗口是否可见
IsWindowVisible( hwndChild ) ;
非0为可见, 0为不可见。
6>. 禁用窗口
EnableWindow( hwndChild, FALSE ) ;
禁用窗口后相关的控件的字符串将变成灰色, 表示不可用, 同时窗口也不再响应鼠标或键盘的输入。
7>. 启用窗口
EnableWindow( hwndChild, TRUE ) ;
8>. 判断窗口是否可用
IsWindowEnabled( hwndChild ) ;
启用返回非0, 禁用返回0。
示例中的代码下载: http://files.cnblogs.com/mr-wid/Learn_WinC_day_13.rar