#include <windows.h>
/* 导入包含文件WINDOWS.H,此文件包含了其它的Windows头文件 */
/* WINDEF.H 基本类型定义 */
/* WINNT.H 支持Unicode的类型定义 */
/* WINBASE.H 内核函数 */
/* WINUSER.H 用户接口函数 */
/* WINGDI.H 图形设备接口函数 */
/************************************************************************/
/* 窗口对象的过程处理函数 */
/* LRESULT: 简单定义为LONG(long) */
/* CALLBACK:__stdcall,指在Windows本身和用户的应用程序之间发生的函数调 */
/* 用的特殊调用序列。 */
/* HWND: 窗口句柄,32位数字,该参数为接受消息的窗口的句柄, */
/* CreateWindow函数的返回值。 */
/* UINT: unsigned int 无符号整型32位, */
/* 该参数为MSG结构中的message域相同,表示该消息的数字 */
/* WPARAM: UINT,32位消息参数 */
/* LPARAM: LONG,32位消息参数 */
/************************************************************************/
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, PSTR lpszCmdLine, int iCmdShow)
{
/* 定义窗口类 */
/* TCHAR:char */
/* _T和TEXT宏,功能一致,通常没用,在Unicode系统中, */
/* 自动把后面的字符串转换为宽字符串 */
/************************************************************************/
TCHAR tcClassName[] = TEXT("My Window"); //窗口类名字符串
/************************************************************************/
/* WNDCLASS:窗口类结构,定义了窗口的一般特性,可以创建不同的窗口 */
/* typedef struct */
/* { */
/* UINT style ; */
/* WNDPROC lpfnWndProc ; */
/* int cbClsExtra ; */
/* int cbWndExtra ; */
/* HINSTANCE hInstance ; */
/* HICON hIcon ; */
/* HCURSOR hCursor ; */
/* HBRUSH hbrBackground ; */
/* LPCTSTR lpszMenuName ; */
/* LPCTSTR lpszClassName ; */
/* } */
/* WNDCLASS, * PWNDCLASS ; */
/************************************************************************/
HWND hWnd;
MSG msg;
WNDCLASS wc; //窗口类属性描述结构
wc.lpszClassName = tcClassName; //窗口类名
wc.lpszMenuName = NULL; //窗口类菜单资源名
wc.lpfnWndProc = WndProc; //窗口对象的过程处理函数,指向函数的指针
wc.hInstance = hInstance; //当前进程对象句柄,接收于WinMain参数
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); //窗口背景刷子对象
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); //图标对象
wc.hCursor = LoadCursor(NULL, IDC_ARROW); //光标对象
wc.cbClsExtra = 0; //同类窗口对象公共数据区大小
wc.cbWndExtra = 0; //当前窗口对象私有数据区大小
wc.style = CS_HREDRAW | CS_VREDRAW; //窗口类风格,水平或垂直尺寸改变后刷新
if (!RegisterClass(&wc)) //注册窗口类,参数为指向WNDCLASS结构的指针
{
/************************************************************************/
/* 窗口类注册不成功的错误信息 */
/* MessageBox 消息框 */
/* 参数一:窗口句柄,如果没有则为NULL */
/* 参数二:消息框主体显示的字符串 */
/* 参数三:消息框标题栏上的字符串 */
/* 参数四:winuser.h中定义的MB_开始的常数组合,消息框风格:按钮,图标 */
/* 为0,则只有ok按钮 */
/* 返回值:返回IDOK(1)。 */
/* 还可以返回IDYES、IDNO、IDCANCEL、IDABORT、IDRETRY、IDIGNORE等 */
/************************************************************************/
MessageBox(NULL, TEXT("RegisterClassError!"), TEXT("Error"), MB_ICONERROR);
return 0; //如果注册失败,返回并终止程序
}
/************************************************************************/
/* 定义窗口对象属性,指定有关窗口的更详细信息 */
/************************************************************************/
TCHAR tcWindowCaptionName[] = TEXT("Win32 API"); //窗口对象标题名称
CREATESTRUCT cs; //窗口对象属性描述结构,定义在WINUSER.H
cs.lpszClass = tcClassName; //窗口类名
cs.lpszName = tcWindowCaptionName; //窗口对象标题名称,显示在标题栏
cs.style = WS_OVERLAPPEDWINDOW; //窗口对象风格
cs.x = 100; //窗口对象在屏幕上的x坐标
cs.y = 100; //窗口对象在屏幕上的y坐标
cs.cx = 400; //窗口对象的宽度
cs.cy = 300; //窗口对象的高度
cs.hwndParent = NULL; //窗口对象的父窗口句柄
cs.hMenu = NULL; //窗口对象的菜单句柄或子窗口编号
cs.hInstance = hInstance; //当前进程的实例句柄,WinMain参数
cs.lpCreateParams = NULL; //创建参数指针,可以访问以后想要引用的程序中的数据
/************************************************************************/
/* 创建窗口对象 */
/* 定义窗口句柄hWnd,值为CreateWindows函数的返回值。 */
/* 即创建成功返回窗口的句柄,否则返回NULL */
/************************************************************************/
hWnd = CreateWindow(cs.lpszClass,
cs.lpszName,
cs.style,
cs.x,
cs.y,
cs.cx,
cs.cy,
cs.hwndParent,
cs.hMenu,
cs.hInstance,
cs.lpCreateParams);
if (hWnd == NULL) //判断创建是否成功
{
/************************************************************************/
/* 窗口对象创建不成功的错误提示 */
/************************************************************************/
MessageBox(NULL, TEXT("CreateWindowError!"), TEXT("Error!"),MB_ICONERROR);
return 0;
}
/************************************************************************/
/* 显示窗口对象 */
/* 此时Windows内部已经创建了这个窗口。已经分配内存。 */
/* 但是要显示在显示器上还需要调用两个函数。 */
/* ShowWindows(hwnd,iCmdShow) */
/* 第一个参数是刚刚用CreateWindow创建的窗口的句柄。 */
/* 第二个参数是传给WinMain的iCmdShow。用来确定最初如何在屏幕上显示窗口。*/
/* 也可以自定义选择以选项: */
/* SW_SHOWNORMAL //常规 */
/* SW_SHOWMAXIMIZED //最大化 */
/* SW_SHOWMINNOACTIVE //只显示在任务栏 */
/* UpdateWindow(hWnd) */
/* 导致客户区域被绘制。通过给窗口过程(Wndproc)发送一个WM_PAINT消息实现 */
/************************************************************************/
ShowWindow(hWnd, iCmdShow);
UpdateWindow(hWnd);//立即刷新窗口对象
/************************************************************************/
/* 消息检索,消息循环 */
/* MSG:消息结构,被定义在WINUSER.H */
/* 消息循环以GetMessage调用开始,它从消息队列中取出一个消息 */
/* 这一调用传递给Windows一个指向msg的MSG结构指针。 */
/* 第二、三、四个参数为NULL或者0表示程序接收自己创建的所有窗口的所有消息*/
/* Windows用从消息队列中取出的下一个消息填充msg结构的各个域 */
/* MSG:消息结构: */
/* typedef struct tagMSG */
/* { */
/* HWND hwnd ; //消息发向的窗口的句柄。 */
/* UINT message ; //消息标识符,一个数值,定义在Window头文件中 */
/* WPARAM wParam ; //一个32位的消息参数,含义根据消息不同而不同 */
/* LPARAM lParam ; //同上 */
/* DWORD time ; //消息放入消息队列时的时间 */
/* POINT pt ; //消息放入消息队列时的鼠标坐标 */
/* } */
/* MSG, * PMSG ; //结构名 */
/************************************************************************/
while (GetMessage(&msg, NULL, 0, 0))
{
/************************************************************************/
/* 检索消息;当检索到WM_QUIT(其值为0x0012)消息时,从消息循环中退出 */
/************************************************************************/
TranslateMessage(&msg);//将msg结构传递给Windows,进行虚拟键盘消息的转换
DispatchMessage(&msg); //发送消息,由此操作系统调用相应的窗口过程处理消息
}
/************************************************************************/
/* 主窗口返回 */
/************************************************************************/
return msg.wParam;
}
/************************************************************************/
/* 窗口对象的过程处理函数 */
/* 四个参数与MSG结构中的前四个参数相同。 */
/* 程序通常不直接调用窗口过程,由Windows本身调用。 */
/* 程序可以通过SendMessage函数调用自己的窗口过程 */
/************************************************************************/
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
/************************************************************************/
/* 对消息进行分类处理 */
/* WINUSER.H为每个消息定义以WM为前缀的标识符 */
/*一般Windows程序元用switch和case结构来处理,此时必须返回0。 */
/*窗口过程不处理的其它消息必须传递给DefWindowProc函数, */
/*窗口过程返回该函数返回值 */
/************************************************************************/
switch (iMsg)
{
/************************************************************************/
/* 客户区的绘制消息,窗口客户区域无效时刷新 */
/************************************************************************/
case WM_PAINT:
{
/************************************************************************/
/* PAINTSTRUCT:绘图结构,定义在WINUSER.H中 */
/* RECT:矩形结构 */
/* HDC:设备描述表句柄 */
/************************************************************************/
PAINTSTRUCT ps;
HDC hDC;
RECT rect;
/* 对于WM_PAINT的处理几乎总是从一个BeginPaint函数开始的: */
/* hDC = BeginPaint(hWnd, &ps) */
/* 而已一个EndPaint函数结束 */
/* EndPaint(hWnd, &ps) */
/* 两个调用中第一个参数是程序的窗口句柄, */
/* 第二个参数是指向类型为PAINTSTRUCT的结构指针 */
/************************************************************************/
hDC = BeginPaint(hWnd, &ps); //获取显示设备对象及绘制描述属性
GetClientRect(hWnd, &rect); //获取当前窗口对象客户区矩形
SetBkMode(hDC,TRANSPARENT); //设置背景方式
SetTextColor(hDC, RGB(255, 0, 0)); //设置文本颜色
/************************************************************************/
/* 绘制文本 */
/* DrawText函数,第一个参数是从BeginPaint返回的设备描述表句柄 */
/* 第二个参数是要输出的文本 */
/* 第三个参数是-1,表示文本串是以字节0终结的。 */
/* 第四个参数要绘制的矩形区域 */
/* 最后一个参数是系列标志位,定义在WINDUSER.H中,水平、垂直中央,单行 */
/************************************************************************/
DrawText(hDC, TEXT("Hello, Win32!"), -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
EndPaint(hWnd, &ps); //客户区绘制结束,归还显示设备对象
return 0;
}
case WM_DESTROY: //接收到WM_DEFTROY消息
{
/************************************************************************/
/* 发送WM_QUIT消息,通知线程消息检索循环,清除窗口主程序可以退出 */
/* PostQuitMessage(0)函数在消息队列里插入一个WM_QUIT消息 */
/************************************************************************/
PostQuitMessage(0);
return 0;
}
}
/************************************************************************/
/* 程序末处理的消息交给window系统的缺省窗口对象的过程处理函数处理 */
/************************************************************************/
return DefWindowProc(hWnd, iMsg, wParam,lParam);
}
我们看上面的流程,可以总结出创建一个窗口的流程:
(1).注册窗口类(RegisterClass)。在注册之前,要先填写RegisterClass的参数WNDCLASSEX结构。
(2)建立窗口(CreateWindow)。
(3)显示窗口(ShowWindows)。
(4)刷新窗口客户区(UpdateWindow)。
(5)进入无限的消息获取和处理的循环。首先获取消息(GetMessage),如果有消息到达,则将消息分派到回调函数处理(DispatchMessage),如果消息是WM_QUIT,则退出循环。
二.那么为什么要按照这样的顺序做呢?我们来用下图阐述一下原理:
(1).当我们按了键盘和shu标后,此时会产生一个消息(包含消息的类型,发生的时间,位置等),并放入到系统消息队列中。此时windows会检查消息发生的位置,如果发现这个消息刚好位于某个应用程序的窗口内的时候,就将这个消息放于应用程序的消息队列中。如图c所示。
(2).当应用程序还没有来取消息的时候,消息就暂时保留在消息队列里,当程序中的消息循环执行到GetMessage的时候,控制权转移到GetMessage所在的USER32.DLL中(箭头1),USER32.DLL从程序消息队列中取出一条消息(箭头2),然后把这条消息返回应用程序(箭头3)。
(3).然后应用程序将处理这条消息,但方法不是自己直接调用窗口过程来完成,而是通过DispatchMessage间接调用窗口过程,Dispatch的英文含义是“分派”,之所以是“分派”,是因为一个程序可能建有不止一个窗口,不同的窗口消息必须分派给相应的窗口过程。当控制权转移到USER32.DLL中的DispatchMessage时,DispatchMessage找出消息对应窗口的窗口过程,然后把消息的具体信息当做参数来调用它(箭头5),窗口过程根据消息找到对应的分支去处理,然后返回(箭头6),这时控制权回到DispatchMessage,最后DispatchMessage函数返回应用程序(箭头7)。这样,一个循环就结束了,程序又开始新一轮的GetMessage。
(4). 应用程序之间也可以互发消息,PostMessage是把一个消息放到其他程序的消息队列中,如图4.4中箭头d所示,目标程序收到了这条消息就把它放入该程序的消息队列去处理;而SendMessage则越过消息队列直接调用目标程序的窗口过程(如图4.4中箭头I所示),窗口过程返回以后才从SendMessage返回(如图4.4中箭头II所示)。
窗口过程是由Windows回调的,Windows又是怎么知道往哪里回调呢?答案是我们在调用RegisterClassEx函数的时候告诉了Windows。
三.理解了原理之后,我们来仔细分析上面创建一个窗口的程序。
(1).为什么要使用注册窗口类?
例如:在一个窗口中,可能有不同的按钮,它们的工作原理都是一样的。但是各个按钮可能都有不同的表现形式,比如大小,颜色等。所以在这种情况下,我们需要将创建窗口的共性提取出来,然后再设置每个具体的窗口。这样就更加符合面向对象的原理。
(2).当应用程序取得消息后,DispatchMessage是如何知道要发给谁去处理呢?
这是因为在注册窗口类时,已经指定了。否则windows不可能知道。
(3).注册窗口类后,就要创建窗口了,建立窗口以后,传回来的是窗口句柄,要把它先保存起来,这时候,窗口虽已建立,但还没有在屏幕上显示出来,要用ShowWindow把它显示出来,ShowWindow也可以用在别的地方,主要用来控制窗口的显示状态(显示或隐藏),大小控制(最大化、最小化或原始大小)和是否激活(当前窗口还是背后的窗口),它用窗口句柄做第一个参数,第二个参数则是显示的方式
(4).窗口建立后,我们需要在窗口的客户区显示内容,这就要用到UpdateWindow, 它实际上就是向窗口发送了一条WM_PAINT消息
三.消息循环
函数会在这里返回取到的消息,hWnd参数指定要获取哪个窗口的消息,例子中指定为NULL,表示获取的是所有本程序所属窗口的消息,wMsgFilterMin和wMsgFilterMax为0表示获取所有编号的消息。
GetMessage函数从消息队列里取得消息,填写好MSG结构并返回
TranslateMessage将MSG结构传给Windows进行一些键盘消息的转换,当有键盘按下和放开时,Windows产生WM_KEYDOWN和WM_KEYUP或WM_SYSKEYDOWN和WM_SYSKEYUP消息,但这些消息的参数中包含的是按键的扫描码,转换成常用的ASCII码要经过查表,很不方便,TranslateMessage遇到键盘消息则将扫描码转换成ASCII码并在消息队列中插入WM_CHAR或WM_SYSCHAR消息,参数就是转换好的ASCII码,如此一来,要处理键盘消息的话只要处理WM_CHAR消息就好了。遇到别的消息则TranslateMessage不做处理。
例如:如果我们按一个健盘上的健,如果不用TranslateMessage,则会处理WM_KEYDOWN和WM_KEYUP消息,否则,就只会产生WM_CHAR消息。
最后,由DispatchMessage将消息发送到窗口对应的窗口过程去处理。窗口过程返回后DispatchMessage函数才返回,然后开始新一轮消息循环。
四. WM_PAINT消息
在最初创建窗口时,整个客户区域是无效的。因为程序还没有在窗口上画什么东西。当调 用updatawindow后,会发送一个WM_PAINT消息(第一个WM_PAINT消息),它会指示在窗口的客户区内画一些东西。
那么当我们改变窗口大小时,使整个窗口无效,迫使windows刷新。
当窗口与另一个窗口重叠时,当移开后,使被重叠的那部分无效。
当最小化窗口后,当窗口重新恢复到原始状态时,windows并不保存客户区的内容(因为那样工作量太大),只是使之无效而已。
一旦客户区域失效,窗口过程就会接收一个新的WM_PAINT消息,此时使用GetClientRect获得变化后的客户区哉。并在新窗口的中央显示文本。
因为按钮是子窗口,是系统已定义好的,所以不需要使用注册窗口类注册,也不需要写消息处理,直接在窗口的WM_CREATE中创建就可以了,在上一讲的窗口过程中加入如下代码:
case WM_CREATE:
{
CreateWindow(TEXT("BUTTON"),TEXT("按钮(&A)"),WS_CHILD | WS_VISIBLE |BS_PUSHBUTTON,10,10,65,22,hWnd,(HMENU)1000,hInst,NULL);
return 0;
}
这样就实现了在sdk中加入控件的功能,非常简单吧!!
同样的道理,如果我们想增加一个文本框控件,仅需要加入:
///下面一个是编辑框按钮,你可以在其中输入文字,具有一个简单的写字板的
///功能,可以复制、粘贴、剪切等操作
///
CreateWindow(
"edit", //在这里设置此按钮为edit类型,表示将控件设定为编辑框控件
"试试", //字符型数组在前面已经定义,在这里显示它的内容
WS_CHILD | WS_VISIBLE,
10,
160,
568,
130,
hWnd,
(HMENU)1001,
hInst,
NULL);
备注:设定按钮的类型为button,其它类型有静态控件static,
滚动条控件scrollbar,编辑框控件edit,列表框控件listbox