windows最大的进步就是可视化的窗口显示信息,我们可以自定义一个窗口,并处理窗口的消息。windows程序的驱动机制称为消息驱动,既程序通过被动的响应消息来与用户交互。用户通过输入设备向系统输入事件,系统检测到事件或者系统本身定义的事件条件被触发,通过发送消息的方式通知程序,而程序对不同的消息采用不同的处理之后再反馈给用户,这个流程被称为消息处理框架。
/*------------------------------------------------------------
HELLOWIN.C -- Displays "Hello, Windows 98!" in client area
(c) Charles Petzold, 1998
------------------------------------------------------------*/
//引入头文件
#include <windows.h>
//消息处理函数用于回调
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
//入口函数
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = 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 = szAppName ; //类型名
//注册窗口
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
//创建窗口
hwnd = CreateWindow (szAppName, // window class name
TEXT ("The Hello Program"), // window caption
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
CW_USEDEFAULT, // initial x size
CW_USEDEFAULT, // initial y size
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL) ; // creation parameters
//显示窗口,并立即重画
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_CREATE: //窗口创建消息
PlaySound (TEXT ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ;
return 0 ;
case WM_PAINT: //绘制消息
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
DrawText (hdc, TEXT ("Hello, Windows 98!"), -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) ;
}
程序主要包含两个函数WinMain 和 WndProc。WinMain为主函数在主函数中完成了消息处理框架的设置。WndProc声明在前,被赋值给了wndclass.lpfnWndProc用于处理窗口消息。CreateWindow创建这个类型的窗口后所有的消息都被发送到WndProc中去处理。先看窗口如何构建,在考虑消息如何驱动。
//定义窗口类型
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 = szAppName ; //类型名
窗口类风格可以通过|运算组合:
#define CS_VREDRAW 0x0001 //窗口高度变化时将被重绘
#define CS_HREDRAW 0x0002 //窗口宽度变化时将被重绘
#define CS_KEYCVTWINDOW 0x0004
#define CS_DBLCLKS 0x0008 //不忽略鼠标双击的消息
#define CS_OWNDC 0x0020 //给用该类建立的每一个窗口分配独立的设备 DC
#define CS_CLASSDC 0x0040 //让属于该类的所有窗口共享一个设备 DC
#define CS_PARENTDC 0x0080 //允许窗口的子窗口继承一些共同特性
#define CS_NOKEYCVT 0x0100
#define CS_NOCLOSE 0x0200 //禁用系统菜单的 Close命令,同时窗口没有关闭按钮
#define CS_SAVEBITS 0x0800 //当窗口被覆盖时, 用位图缓存被覆盖区, 从而避免 WM_PAINT 消息, 一般用于菜单或对话框
#define CS_BYTEALIGNCLIENT 0x1000 //通过字节对齐, 增强客户区的绘制性能
#define CS_BYTEALIGNWINDOW 0x2000 //通过字节对齐, 增强窗口的绘制性能
#define CS_GLOBALCLASS 0x4000 //全局窗口类, 一般用于 DLL; 没有此选项, 窗口类和窗口建立函数中指定的实例句柄须相同
#define CS_IME 0x00010000
hInstance为程序句柄,这个窗口类型只在本程序中有效。LoadIcon和LoadCursor分别用户载入图标和鼠标手势图片,GetStockObject获得一个画刷,默认的情况下窗口重绘时会用这个画刷刷新背景。lpszClassName所指定的类型名要和CreateWindows的参数必须相同。调用RegisterClass注册完之后就可以调用CreateWindows来创建窗口了。
CreateWindow中的window style限定了窗口的风格CreateWindowEx还支持扩展风格:
窗口风格(Window style)
WS_BORDER 有边框窗口
WS_CAPTION 必须和WS_BORDER风格配合,但不能与WS_DLGFRAME风格一起使用。指示窗口包含标题要部分。
WS_CHILD 说明窗口为子窗口,不能应用于弹出式窗口风格(WS_POPUP)。
WS_CHILDWINDOW 同WS_CHILD。
WS_CLIPCHILDREN 绘制父窗口时,不绘制子窗口的裁剪区域。使用在建立父窗口时。
WS_CLIPSIBLINGS 剪裁相关的子窗口,这意味着,当一个特定的子窗口接收到重绘消息时,WS_CLIPSIBLINGS风格将在子窗口要重画的区域中去掉与其它子窗口重叠的部分。(如果没有指定WS_CLIPSIBLINGS风格,并且子窗口有重叠,当你在一个子窗口的客户区绘图时,它可能会画在相邻的子窗口的客户区中。)只与WS_CHILD风格一起使用。
WS_DISABLED 创建一个初始状态为禁止的窗口。
WS_DLGFRAME 创建一个窗口,具有双重边界,但是没有标题条。
WS_GROUP 指定一组控件中的第一个,用户可以用箭头键在这组控件中移动。在第一个控件后面把WS_GROUP风格设置为FALSE的控件都属于这一组。下一个具有WS_GROUP风格的控件将开始下一组(这意味着一个组在下一组的开始处结束)。
WS_HSCROLL 创建一个具有水平滚动条的窗口。
WS_ICONIC:创建一个初始状态为最小化状态的窗口。与WS_MINIMIZE风格相同。
WS_MAXIMIZE 创建一个最大化的窗口。
WS_MAXIMIZEBOX 创建一个具有最大化按钮的窗口。
WS_MINIMIZE 创建一个初始状态为最小化的窗口。仅与WS_OVERLAPPED风格一起使用。
WS_MINIMIZEBOX 创建一个具有最小化按钮的窗口。
WS_OVERLAPPED 创建一个重叠窗口。重叠窗口通常具有标题条和边界。
WS_OVERLAPPEDWINDOW 创建一个具有WS_OVERLAPPED,WS_CAPTION,WS_SYSMENU,WS_THICKFRAME,WS_MINIMIZEBOX和WS_MAXIMIZEBOX风格的重叠式窗口。
WS_POPUP 创建一个弹出式窗口,不能与WS_CHILD风格一起使用。
WS_POPUPWINDOW 创建一个具有WS_BORDER,WS_POPUP和WS_SYSMENU风格的弹出窗口。为了使控制菜单可见,必须与WS_POPUPWINDOW一起使用WS_CAPTION风格。
WS_SIZEBOX:创建一个可调边框的窗口,与WS_THICKFRAME风格相同。
WS_SYSMENU 创建一个在标题条上具有控制菜单的窗口。仅对带标题条的窗口使用。
WS_TABSTOP 指定了一些控件中的一个,用户可以通过TAB键来移过它。TAB键使用户移动到下一个用WS_TABSTOP风格定义的控件。
WS_THICKFRAME 创建一个具有厚边框的窗口,可以通过厚边框来改变窗口大小。
WS_TILED:产生一个层叠的窗口。一个层叠的窗口有一个标题和一个边框。与WS_OVERLAPPED风格相同。
WS_TILEDWINDOW:创建一个具有WS_OVERLAPPED,WS_CAPTION,WS_SYSMENU,MS_THICKFRAME风格的窗口。
WS_VISIBLE 创建一个最初可见的窗口。
WS_VSCROLL 创建一个具有垂直滚动条的窗口。
扩展窗口风格(Extended window style)
WS_EX_ACCEPTFILES 指明用这个风格创建的窗口能够接受拖放文件。
WS_EX_APPWINDOW - 当窗口可见时将一个顶层窗口放置在任务栏上。
WS_EX_CLIENTEDGE 指明窗口具有3D外观,这意味着,边框具有下沉的边界。
WS_EX_CONTEXTHELP 在窗口的标题条中包含问号。当用户单击问号时,鼠标光标的形状变为带指针的问号。如果用户随后单击一个子窗口,子窗口将接收到一个WM_HELP消息。
WS_EX_CONTROLPARENT 允许用户用TAB键遍历窗口的子窗口。
WS_EX_DLGMODALFRAME 指明一个具有双重边界的窗口,当你在dwStyle参数中指定了WS_CAPTION风格标志时,它可以具有标题条(可选)。
WS_EX_LEFT 指定窗口具有左对齐属性。这是缺省值。
WS_EX_LEFTSCROLLBAR 将垂直滚动条放在客户区的左边。
WS_EX_LTRREADING 按照从左到右的方式显示窗口文本。这是缺省方式。
WS_EX_MDICHILD 创建一个MDI子窗口。
WS_EX_NOPARENTNOTIFY 指定用这个风格创建的子窗口在被创建或销毁的时候将不向父窗口发送WM_PARENTNOTIFY消息。
WS_EX_OVERLAPPEDWINDOW 组合了WS_EX_CLIENTEDGE和WS_EX_WIND-OWEDGE风格。
WS_EX_PALETTEWINDOW 组合了WS_EX_WINDOWEDGE和WS_EX_TOPMOST风格。
WS_EX_RIGHT 赋予窗口右对齐属性。这与窗口类有关。
WS_EX_RIGHTSCROLLBAR 将垂直滚动条(如果有)放在客户区的右边。这是缺省方式。
WS_EX_RTLREADING 按照从右到左的顺序显示窗口文本。
WS_EX_STATICEDGE 创建一个具有三维边界的窗口,用于不接受用户输入的项。
WS_EX_TOOLWINDOW 创建一个工具窗口,目的是被用作浮动工具条。工具窗口具有标题条,比通常的标题条要短,窗口的标题是用小字体显示的。工具窗口不出现在任务条或用户按下ALT+TAB时出现的窗口中。
WS_EX_TOPMOST 指定用这个风格创建的窗口必须被放在所有非顶层窗口的上面,即使这个窗口已经不处于激活状态,它还是保留在最上面。应用程序可以用SetWindowsPos成员函数来加入或去掉这个属性。
WS_EX_TRANSPARENT 指定了用这个风格创建的窗口是透明的。这意味着,在这个窗口下面的任何窗口都不会被这个窗口挡住。用这个风格创建的窗口只有当它下面的窗口都更新过以后才接收WM_PAINT消息。
WS_EX_WINDOWEDGE 指定了具有凸起边框的窗口。
CreateWindow中的initial position size 指定了窗口最初的位置(相对于屏幕或者父窗口)和大小CW_USEDEFAULT由系统决定。
CreateWindow返回一个句柄指代窗口对象,并使用ShowWindow和UpdateWindow另窗口显示出来。ShowWindow和UpdateWindow中的参数都使用的窗口句柄,这样就把窗口对象隐藏起来了,不直接操作对象而操作句柄保证了窗口对象的安全。ShowWindow设置了窗口的显示状态但不会马上改变,只是在消息队列里插入一个WM_PAINT消息,只有当这个消息被处理的时候才会其作用。而UpdateWindow强制窗口马上刷新。ShowWindow的第一个参数指定了窗口,而第二个参数指定窗口的显示状态:
#define SW_HIDE 0 //隐藏窗口并激活其他窗口
#define SW_SHOWNORMAL 1 //激活并显示一个窗口。如果窗口被最小化或最大化,系统将其恢复到原来的尺寸和大小。应用程序在第一次显示窗口的时候应该指定此标志
#define SW_NORMAL 1 //同SW_SHOWNORMAL
#define SW_SHOWMINIMIZED 2 //激活窗口并将其最小化
#define SW_SHOWMAXIMIZED 3 //最大化指定的窗口
#define SW_MAXIMIZE 3 //同SW_SHOWMAXIMIZED
#define SW_SHOWNOACTIVATE 4 //以窗口最近一次的大小和状态显示窗口。激活窗口仍然维持激活状态
#define SW_SHOW 5 //在窗口原来的位置以原来的尺寸激活和显示窗口
#define SW_MINIMIZE 6 //最小化指定的窗口并且激活在Z序中的下一个顶层窗口
#define SW_SHOWMINNOACTIVE 7 //窗口最小化,激活窗口仍然维持激活状态
#define SW_SHOWNA 8 //以窗口原来的状态显示窗口。激活窗口仍然维持激活状态
#define SW_RESTORE 9 //激活并显示窗口。如果窗口最小化或最大化,则系统将窗口恢复到原来的尺寸和位置。在恢复最小化窗口时,应用程序应该指定这个标志
#define SW_SHOWDEFAULT 10 //依据在STARTUPINFO结构中指定的SW_FLAG标志设定显示状态,STARTUPINFO 结构是由启动应用程序的程序传递给CreateProcess函数的
#define SW_FORCEMINIMIZE 11 //即使拥有窗口的线程被挂起也会最小化。在从其他线程最小化窗口时才使用这个参数
#define SW_MAX 11 //同SW_FORCEMINIMIZE
窗口创建完成之后的while循环是整个消息机制的驱动,GetMessage,TranslateMessage,DispatchMessage这三个API相互协作不停的获取系统或者用户的输入(GetMessage)转化为可以被程序识别的消息(TranslateMessage)分发并做出处理(DispatchMessage)。提取和转化与系统的其他部分关联并不特别密切,而难点在于如转发和处理消息。我们庸俗的理解DispatchMessage的处理,是找到消息的目标窗口,根据这个窗口的类型WNDCLASS的lpfnWndProc指针,并调用这个指针所指向的函数,此处即为WndProc。
WndProc只处理了三个消息,窗口创建消息(WM_CREATE),绘制消息(WM_PAINT),窗口销毁消息(WM_DESTROY),对于处理完的消息返回0,而不需要处理的调用DefWindowProc使用系统默认的方式处理。PlaySound播放声音文件,而PostQuitMessage在消息队列中插入WM_QUIT消息,当GetMessage发现这条消息的时候返回一个false,并结束循环。而对绘制消息的处理如下:
//获取窗体hwnd的绘图设备
hdc = BeginPaint (hwnd, &ps) ;
//获取窗体hwnd的绘图范围
GetClientRect (hwnd, &rect) ;
//使用绘图设备输出文字
DrawText (hdc, TEXT ("Hello, Windows 98!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
//释放绘图设备
EndPaint (hwnd, &ps) ;
PS:对于windows的一些宏定义有如下规律: