Win32编程API 基础篇 -- 2.一个简单的窗口 根据英文教程翻译
一个简单的窗口
例子:简单的窗口
有时人们在IRC提问,”我应该怎样制作一个窗口”。。。嗯,这恐怕不是完全这么简单好回答!其实这并不难一旦你明白你在做什么,但在你得到一个可展示的窗口之前还有一些事情需要我们去做,我们只需要简单地聊聊快速做下笔记,这个问题就能被很简单的回答。
我很喜欢先动手再学习。。。一下就是一个简单的窗口的程序,我们将会简短的对它进行解释说明。
1 #include <windows.h>
2
3 const char g_szClassName[] = "myWindowClass";
4
5 // Step 4: the Window Procedure
6 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
7 {
8 switch(msg)
9 {
10 case WM_CLOSE:
11 DestroyWindow(hwnd);
12 break;
13 case WM_DESTROY:
14 PostQuitMessage(0);
15 break;
16 default:
17 return DefWindowProc(hwnd, msg, wParam, lParam);
18 }
19 return 0;
20 }
21
22 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
23 LPSTR lpCmdLine, int nCmdShow)
24 {
25 WNDCLASSEX wc;
26 HWND hwnd;
27 MSG Msg;
28
29 //Step 1: Registering the Window Class
30 wc.cbSize = sizeof(WNDCLASSEX);
31 wc.style = 0;
32 wc.lpfnWndProc = WndProc;
33 wc.cbClsExtra = 0;
34 wc.cbWndExtra = 0;
35 wc.hInstance = hInstance;
36 wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
37 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
38 wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
39 wc.lpszMenuName = NULL;
40 wc.lpszClassName = g_szClassName;
41 wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
42
43 if(!RegisterClassEx(&wc))
44 {
45 MessageBox(NULL, "Window Registration Failed!", "Error!",
46 MB_ICONEXCLAMATION | MB_OK);
47 return 0;
48 }
49
50 // Step 2: Creating the Window
51 hwnd = CreateWindowEx(
52 WS_EX_CLIENTEDGE,
53 g_szClassName,
54 "The title of my window",
55 WS_OVERLAPPEDWINDOW,
56 CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
57 NULL, NULL, hInstance, NULL);
58
59 if(hwnd == NULL)
60 {
61 MessageBox(NULL, "Window Creation Failed!", "Error!",
62 MB_ICONEXCLAMATION | MB_OK);
63 return 0;
64 }
65
66 ShowWindow(hwnd, nCmdShow);
67 UpdateWindow(hwnd);
68
69 // Step 3: The Message Loop
70 while(GetMessage(&Msg, NULL, 0, 0) > 0)
71 {
72 TranslateMessage(&Msg);
73 DispatchMessage(&Msg);
74 }
75 return Msg.wParam;
76 }
你可以利用这个最简单的窗口程序中的大多数内容,创建一个实际功能窗口,如果你成功编译了第一个小程序,那么这个样例程序也应该能够运行。
步骤1:注册窗口类
一个窗口类存储了一种窗口类型的信息,包括控制窗口的窗口程序,窗口的大小图标以及背景颜色等。通过这种方式,你可以只注册一次类,然后无需再次指定这些属性创建多个窗口,但是如果需要的话,你设置的大多数类的属性具体到某一个窗口上都是可以修改的。
一个窗口类跟一个C++类没有任何关系。
1 const char g_szClassName[] = "myWindowClass";
上面的变量存储了一个窗口类的值,我们会马上使用它来注册系统的窗口类。
1 WNDCLASSEX wc;
2
3 wc.cbSize = sizeof(WNDCLASSEX);
4 wc.style = 0;
5 wc.lpfnWndProc = WndProc;
6 wc.cbClsExtra = 0;
7 wc.cbWndExtra = 0;
8 wc.hInstance = hInstance;
9 wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
10 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
11 wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
12 wc.lpszMenuName = NULL;
13 wc.lpszClassName = g_szClassName;
14 wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
15
16 if(!RegisterClassEx(&wc))
17 {
18 MessageBox(NULL, "Window Registration Failed!", "Error!",
19 MB_ICONEXCLAMATION | MB_OK);
20 return 0;
21 }
这是我们在WinMain()中用来注册窗口类的代码,我们会填写WNDCLASSEX结构的成员,然后调用RegisterClassEx()
影响窗口类的结构成员如下:
cbSize
结构体的大小
style
类的风格,不要跟窗口的风格混淆,通常设置为0
lpfnWndProc
指向窗口类的窗口消息处理程序的指针
cbClsExtra
在内存中为这个类分配的额外的数据的数据量的大小,通常为0
cbWndExtra
在内存中为每一个这个类的窗口分配的额外的数据的数据量的大小,通常为0
hInstance
应用程序实例句柄(我们在. WinMain())的第一个参数。
hIcon
大图标(通常是32x32),当我们按下Alt+Tab时
hCursor
显示在窗口中的光标
hbrBackground
设置窗口颜色的背景刷
lpszMenuName
被用在这个类的窗口中的菜单资源的名字
lpszClassName
窗口类的名字
hIconSm
小(通常是16 x16)图标在任务栏显示在窗口的左上角。
不要担心,如果你不是很确切理解这些属性的意思,关于窗口类的属性值将在后面提到更多。另外你要记住不要尝试记住这些属性,这没有什么意义,我很少尝试记住类的属性,或函数参数,这完全是浪费精力和时间的做法。如果你需要调用某个函数,那么只需要花费几秒时间就能在帮助文档中找到它的相关资料,如果你连帮助文档都没有,那快去找。你没有任何损失,最终你会知道你用的最多的函数的参数。
注册完窗口类后我们调用RegisterClassEx()并检查是否有出错,如果注册失败会弹出一个消息弹窗提醒我们并且通过返回0终止接下来程序WinMain()的运行。
步骤2:创建窗口
一旦类注册成功,我们就能用它来创建窗口。你应该理解CreateWindowEx()的参数(当我们使用一个新的API调用时我们经常会这么做),但在这里我会简要解释。
1 HWND hwnd;
2
3 hwnd = CreateWindowEx(
4 WS_EX_CLIENTEDGE,
5 g_szClassName,
6 "The title of my window",
7 WS_OVERLAPPEDWINDOW,
8 CW_USEDEFAULT, CW_USEDEFAULT, 240, 120,
9 NULL, NULL, hInstance, NULL);
参数的说明如下:
WS_EX_CLIENTEDGE
扩展的窗口风格,在例子中我已经给创建的窗口设置了一个凹内边框,如果你想看到设置不设置的区别,你可以设置为0,你可以设置一下其他值,看看有什么区别。
g_szClassName:
设置窗口类名,这个参数告诉系统要创建一个哪种类型的窗口,在这里我们想创建一个刚才注册的窗口类,所以使用g_szClassName这个类名。
"The title of my window"
很明显啦这个就是窗口的标题
WS_OVERLAPPEDWINDOW
设置窗口的样式参数,有很多这种类型的参数,你应该把他们找出来然后测试一下又什么区别,关于这些的讲解会在后面被覆盖。
W_USEDEFAULT, CW_USEDEFAULT, 240, 120,
这四个参数是用来设置窗口的大小和位置的,前面两个参数是窗口左上角的X和Y坐标,后面两个参数分别代表宽度和高度。我把窗口的X、Y坐标设置成W_USEDEFAULT, CW_USEDEFAULT,这样是为了让窗口在屏幕上自适选择显示窗口。记住屏幕的左边是 一个0的X坐标值,并且向右增加;屏幕的顶端是一个0的Y坐标值,并且向底部增加。单位是像素,这是屏幕可以显示的最小单位。
NULL, NULL, hInstance, NULL
上面四个分别对应父窗口句柄,菜单处理,应用程序实例句柄,窗口创建数据的指针。在windows中,在你屏幕上的窗口会按父和子窗口的等级安排。当你在一个窗口中看到一个按钮时,这个按钮在它被包含的父窗口中,相当于一个孩子,通过这个例子,父句柄被设 置为NULL,因为我们创建的这个窗口没有父母,这是我们的主或顶层窗口。菜单是空的因为我们还没有设置菜单;实例句柄被设置成传递给WinMain()的第一个参数;创建数据(我几乎不适用)可用于发送额外的数据给我们要创建的窗口,这里我们也设为NULL。
如果你想知道这个神奇的NULL是什么,它只是简单定义为0而已。事实上,在C语言中它被定义为((void*)0),因为它是用来使用指针的,因此使用NULL可能会报警,取决于编译器和报警级别设置,可以忽略警告,或者使用0替代NULL
注意多检查返回值,这有利于减少出错,很多人遇到一些莫名其妙的错误,这很可能是因为没有设置好返回值。CreateWindow()在某些时候可能会失败即使你是一个经验丰富的程序员,原因很简单,有很多很容易犯的错误。只有你学会如何快速定位找到这些 错误,至少给自己找出哪里出错的机会,并且一直检查返回值!
1 if(hwnd == NULL)
2 {
3 MessageBox(NULL, "Window Creation Failed!", "Error!",
4 MB_ICONEXCLAMATION | MB_OK);
5 return 0;
6 }
在创建并检查确保我们有一个有效的可展示的窗口之后,使用WinMain()中的最后一个参数,然后更新它确保在屏幕上它能正确地重绘本身。
1 ShowWindow(hwnd, nCmdShow);
2 UpdateWindow(hwnd);
nCmdShow参数是可选的,你可以用SW_SHOWNORMAL来代替它,并且从头到尾使用,但是使用传递给WinMain()的参数让任何使用你程序的人可以指定是否设置窗口可见、最大化、最小化等。。。你会在windowsd的快捷方式的属性中发现这些选项,这个参数是关于如何进行选择的。
步骤3:消息环
这是整个程序的核心,几乎所有的程序都是通过这个点来进行控制的。
1 while(GetMessage(&Msg, NULL, 0, 0) > 0)
2 {
3 TranslateMessage(&Msg);
4 DispatchMessage(&Msg);
5 }
6 return Msg.wParam;
GetMessage()从你的应用的消息队列中接收消息。任何时候,用户移动鼠标、敲击键盘,点击窗口上的菜单或者其他动作时,系统会生成消息并插入程序的消息队列,通过调用GetMessage(),你会请求下一个从队列中移除可用的消息,并且返回给你。如果没有消息,GetMessage()会锁住挂起,如果你不熟悉这个词,这意味着它一直等待直到有一个消息,然后返回给你。
TranslateMessage()做了一些额外的处理键盘事件,将我们敲击键盘转化给字符;最后DispatchMessage()将消息发送到消息产生的窗口,这可能是我们的主窗口,也可能是其他窗口,或者在某些情况下被屏幕后其他系统或程序创建的窗口。这不需要你关心,我们要关心的是我们得到消息然后把它发送出去,系统负责剩下的把消息发送到合适正确的窗口。
步骤4:窗口程序(消息处理程序)
如果说消息环是程序的核心,那么消息处理程序就是程序的大脑,这里是所有的消息被发送到的窗口中进行处理的地方。
1 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
2 {
3 switch(msg)
4 {
5 case WM_CLOSE:
6 DestroyWindow(hwnd);
7 break;
8 case WM_DESTROY:
9 PostQuitMessage(0);
10 break;
11 default:
12 return DefWindowProc(hwnd, msg, wParam, lParam);
13 }
14 return 0;
15 }
窗口程序(消息处理程序)会被每个消息调用,HWND参数是你消息作用的窗口的句柄。这很重要,因为你可能有同个类的两个或更多的窗口,这些窗口使用同一个窗口程序(WinProc()),这就需要hwnd参数来区分不同窗口。举个例子,我们得到一个WM_CLOSE的消息,这个消息会关闭窗口,因为我们使用了窗口句柄参数,这个消息的处理就不会影响到其他窗口,只有这个消息应用的窗口才会被影响和处理。
当我们按下窗口右上方的X按钮或触发组合键Alt-F4时,WM_CLOSE消息被发送,这会导致窗口被默认摧毁,但是我喜欢显式地处理它,因为这是进行清理检查的最佳时机,或者在程序退出之前询问用户是否要保存文件等。
当我们调用DestroyWindow()时,系统发送WM_DESTROY消息给要摧毁的窗口,在这种情况下即我们的窗口,然后在最终从系统中移除我们的窗口之前摧毁所有的子窗口,在我们的小程序中这个唯一的一个窗口,所以我们什么都不需要做,只需要调用PostQuitMessage()方法来退出。这个方法会发送WM_QUIT消息给消息环,我们永远都不会接收到这一消息,因为它会导致GetMesage()返回false,正如你在消息环的代码里看到的,当这个发生时我们会停止处理消息然后返回最终的结果代码,WM_QUIT的wParam恰好是我们传递给PostQuitMessage()的值。当你的程序被设计用来被另一个程序调用并且你想返回一个指定的值返回值才有意义。
步骤5:没有步骤5
唷,就是酱紫!如果我还没有解释得很清楚,那多看几遍,在我们进入更有用的程序之前希望你会更加清楚。
PS.由于本人英文水平所限,只能翻译到这个程度了,有纰漏还望多多指出,附上本篇翻译的英文原版教程地址:http://www.winprog.org/tutorial/simple_window.html