今天, 我们要学习的就是 子窗口控件 的使用。
在其他一些 Windows应用软件上我们经常能够看到一些大致相同的按钮、复选框、组合框、列表框等控件, 这些控件很有可能就是使用 标准子窗口控件 来实现的。
子窗口的创建
在讲解 “标准子窗口控件” 的使用之前我们首先应该知道如何去创建一个子窗口, 因为这些 “子窗口控件” 实际上都是通过创建一个子窗口的形式来进行创建的, 因此我们应该把理解的重点放在 “子窗口” 上, 而不是 “控件” 上。
子窗口的创建可以将整个客户区划分为多个矩形区域, 并且每个子窗口都可以有自己的句柄、窗口过程和客户区, 每个子窗口过程只接收与自身窗口有关的鼠标消息、鼠标消息的参数 lParam 中包含的坐标是相对于 子窗口 客户区的左上角的。简单的说, 子窗口具有一个普通窗口的一切特性。
子窗口的创建同样是使用 CreateWindow 函数进行创建的, 下面我们通过一个示例来认识这个过程:
这段代码演示的是在父窗口的(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 消息, 当鼠标在子窗口客户区中按下左键时就弹出一个对话框告诉用户"鼠标左键在子窗口客户区中按下!"。
这些就是创建一个子窗口的演示。使用子窗口的程序设计有助于程序的结构化和模式化, 提高代码的复用率。
如果你已经将上面示例中的代码理解的话, 那么恭喜你! 你已经又向成功迈向了一大步, 因为标准子窗口控件的使用比我们自己创建子窗口还要简单!
代码实例
#define _CRT_SECURE_NO_WARNINGS 1
/* -------------------------------------------------------------------
中年人学C语言Windows程序设计
--------------------------------------------------------------------*/
#include <windows.h>
#include<strsafe.h>
//#include"sysmets.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM); //子窗口窗口过程
TCHAR szChildClass[] = TEXT("ChildClass"); //子窗口类名字
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
//主函数
// 四个参数:
//hInstance : 程序当前实例的句柄(handle to current instance),以后随时可以用GetModuleHandle(0)来获得
//hPrevInstance : 前一个实例的句柄(handle to previous instance),在Win32中,每一个进程都有一个独立的4G地址空间,从0到2G属于进程私有,对其他进程来说是不可见的。所以,在Win32中,hPrevInstance总是为NULL。
//szCmdLine : 指向以 / 0结尾的命令行,不包括EXE本身的文件名(pointer to command line),以后随时可以用GetCommandLine()来获取完整的命令行。
//iCmdShow : 指明应该以什么方式显示主窗口(show state of window)。
{
static TCHAR szAppName[] = TEXT("CreateChildWindow");
HWND hwnd;//句柄
MSG msg;//消息
WNDCLASS wndclass;//定义窗口类结构
HBRUSH hCulorBrush = CreateSolidBrush(RGB(200, 200, 200));//设定背景颜色
//结构成员:
wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口类型
wndclass.lpfnWndProc = WndProc; //窗口过程(必须是回调函数)
wndclass.cbClsExtra = 0;//预留的额外空间,一般为0
wndclass.cbWndExtra = 0;//预留的额外空间,一般为0
wndclass.hInstance = hInstance;//应用程序的实例句柄
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);//为所有基于该窗口类的窗口设定一个图标
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);//为所有基于该窗口类的窗口设定一个鼠标指针
//wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);//指定窗口背景色
wndclass.hbrBackground = hCulorBrush;//指定窗口背景色
wndclass.lpszMenuName = NULL; //指定窗口菜单
wndclass.lpszClassName = szAppName; //指定窗口类名
//定义WinMain函数, 在WinMain函数中建立父窗口的 wndclass 窗口类并注册:
if (!RegisterClass(&wndclass))
/* RegisterClass:注册在随后调用CreateWindow函数和CreateWindowEx函数中使用的窗口类。
参数为lpWndClass,指向一个WNDCLASS结构的指针
返回值:ATOM的宏定义
typedef WORD ATOM;
typedef unsigned short WORD;
关于RegisterClassEx:参数lpwcx指向一个WNDCLASSEX结构的指针如果函数成功,返回这个窗口类型的标识号;
如果函数失败,返回值为0。若想获得更多错误信息,请调用GetLastError函数。*/
{
MessageBox(NULL, TEXT("这个程序需要在Windows系统执行!"), szAppName, MB_ICONERROR);
return 0;
}
//改变父窗口 wndclass 类中的部分属性, 使其成为子窗口的 wndclass, 并注册子窗口窗口类:
wndclass.lpszClassName = szChildClass;//赋值子窗口类名字
wndclass.cbWndExtra = sizeof(long);
wndclass.lpfnWndProc = ChildWndProc;
RegisterClass(&wndclass); //注册子窗口窗口类
//实例化创建主窗口
hwnd = CreateWindow(szAppName,// 窗口类名称
TEXT("中年人学C语言Windows程序设计--子窗口演示"),// 窗口标题
WS_OVERLAPPEDWINDOW | WS_VSCROLL,// 窗口风格,或称窗口格式
CW_USEDEFAULT,// 初始 x 坐标
CW_USEDEFAULT, // 初始 y 坐标
CW_USEDEFAULT,// 初始 x 方向尺寸
CW_USEDEFAULT,// 初始 y 方向尺寸
NULL,// 父窗口句柄
NULL,// 窗口菜单句柄
hInstance,// 程序实例句柄
NULL);// 创建参数
/*_In_说明该参数是输入的,_opt_说明该参数是可选参数
函数成功返回窗口句柄,否则返回NULL*/
//显示窗口
ShowWindow(hwnd, iCmdShow);
/* 第一次调用时应使用WinMain的参数nCmdShow作为参数
如果窗口之前可见,则返回非0否则返回0*/
//更新窗口
UpdateWindow(hwnd);
/* 绕过消息队列直接向窗口过程发送WM_PAINT消息
函数调用成功返回非0*/
//消息循环
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
/*作用:从当前线程的消息队列里取出一个消息并放入MSG结构中,不能获得其他线程的消息
若消息队列为空,函数会一直等待到有消息到来才有返回值
返回值:
函数出现错误则返回 - 1,
获得WM_QUIT消息返回0
否则返回非0*/
return msg.wParam;
}
//主窗口回调函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
回调函数
参数:
hwnd : 窗口句柄
message : 消息ID
wParam和lParam:消息参数
//int i;
//size_t j;
//HDC hdc;//定义设备环境句柄
static TCHAR szBuffer[128];
//TEXTMETRIC tm;
// SCROLLINFO si;
/* PAINTSTRUCT ps;
RECT rect;*/
//size_t iStrLength;
static int cxChar, cyChar, cxClient, cyClient;
static HWND childHwnd; //子窗口句柄
switch (message)
{
//定义父窗口回调函数 WndProc, 在处理 WM_CREATE 消息时创建子窗口:
case WM_CREATE: //在接收到 WM_CREATE 消息时创建一个子窗口
childHwnd = CreateWindow(szChildClass, TEXT("子窗口"),
WS_CHILDWINDOW | WS_VISIBLE | WS_OVERLAPPEDWINDOW,
10, 10, //在父窗口客户区(10, 10)位置创建一个子窗口
200, 200, //子窗口的大小为200x200
hWnd, //父窗口句柄
(HMENU)1, //子窗口ID
(HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE),
NULL);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
//子窗口 回调函数
LRESULT CALLBACK ChildWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HWND btnHwnd; //子窗口句柄
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);
}
效果实例
在子窗口中点击鼠标左键,跳出对话框。