Win32编程API 基础篇 -- 2.一个简单的窗口 根据英文教程翻译
一个简单的窗口
例子:简单的窗口
有时人们在IRC提问,”我应该怎样制作一个窗口”。。。嗯,这恐怕不是完全这么简单好回答!其实这并不难一旦你明白你在做什么,但在你得到一个可展示的窗口之前还有一些事情需要我们去做,我们只需要简单地聊聊快速做下笔记,这个问题就能被很简单的回答。
我很喜欢先动手再学习。。。一下就是一个简单的窗口的程序,我们将会简短的对它进行解释说明。
#include <windows.h> const char g_szClassName[] = "myWindowClass"; // Step 4: the Window Procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage();
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return ;
} int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg; //Step 1: Registering the Window Class
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = ;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = ;
wc.cbWndExtra = ;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); if(!RegisterClassEx(&wc))
{
MessageBox(NULL, "Window Registration Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return ;
} // Step 2: Creating the Window
hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
g_szClassName,
"The title of my window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, , ,
NULL, NULL, hInstance, NULL); if(hwnd == NULL)
{
MessageBox(NULL, "Window Creation Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return ;
} ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd); // Step 3: The Message Loop
while(GetMessage(&Msg, NULL, , ) > )
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
你可以利用这个最简单的窗口程序中的大多数内容,创建一个实际功能窗口,如果你成功编译了第一个小程序,那么这个样例程序也应该能够运行。
步骤1:注册窗口类
一个窗口类存储了一种窗口类型的信息,包括控制窗口的窗口程序,窗口的大小图标以及背景颜色等。通过这种方式,你可以只注册一次类,然后无需再次指定这些属性创建多个窗口,但是如果需要的话,你设置的大多数类的属性具体到某一个窗口上都是可以修改的。
一个窗口类跟一个C++类没有任何关系。
const char g_szClassName[] = "myWindowClass";
上面的变量存储了一个窗口类的值,我们会马上使用它来注册系统的窗口类。
WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX);
wc.style = ;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = ;
wc.cbWndExtra = ;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); if(!RegisterClassEx(&wc))
{
MessageBox(NULL, "Window Registration Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return ;
}
这是我们在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调用时我们经常会这么做),但在这里我会简要解释。
HWND hwnd; hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
g_szClassName,
"The title of my window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, , ,
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()在某些时候可能会失败即使你是一个经验丰富的程序员,原因很简单,有很多很容易犯的错误。只有你学会如何快速定位找到这些 错误,至少给自己找出哪里出错的机会,并且一直检查返回值!
if(hwnd == NULL)
{
MessageBox(NULL, "Window Creation Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return ;
}
在创建并检查确保我们有一个有效的可展示的窗口之后,使用WinMain()中的最后一个参数,然后更新它确保在屏幕上它能正确地重绘本身。
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
nCmdShow参数是可选的,你可以用SW_SHOWNORMAL来代替它,并且从头到尾使用,但是使用传递给WinMain()的参数让任何使用你程序的人可以指定是否设置窗口可见、最大化、最小化等。。。你会在windowsd的快捷方式的属性中发现这些选项,这个参数是关于如何进行选择的。
步骤3:消息环
这是整个程序的核心,几乎所有的程序都是通过这个点来进行控制的。
while(GetMessage(&Msg, NULL, , ) > )
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
GetMessage()从你的应用的消息队列中接收消息。任何时候,用户移动鼠标、敲击键盘,点击窗口上的菜单或者其他动作时,系统会生成消息并插入程序的消息队列,通过调用GetMessage(),你会请求下一个从队列中移除可用的消息,并且返回给你。如果没有消息,GetMessage()会锁住挂起,如果你不熟悉这个词,这意味着它一直等待直到有一个消息,然后返回给你。
TranslateMessage()做了一些额外的处理键盘事件,将我们敲击键盘转化给字符;最后DispatchMessage()将消息发送到消息产生的窗口,这可能是我们的主窗口,也可能是其他窗口,或者在某些情况下被屏幕后其他系统或程序创建的窗口。这不需要你关心,我们要关心的是我们得到消息然后把它发送出去,系统负责剩下的把消息发送到合适正确的窗口。
步骤4:窗口程序(消息处理程序)
如果说消息环是程序的核心,那么消息处理程序就是程序的大脑,这里是所有的消息被发送到的窗口中进行处理的地方。
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage();
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return ;
}
窗口程序(消息处理程序)会被每个消息调用,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
Win32编程API 基础篇 -- 2.一个简单的窗口 根据英文教程翻译的更多相关文章
- Win32编程API 基础篇 -- 1.入门指南 根据英文教程翻译
入门指南 本教程是关于什么的 本教程的目的是向你介绍使用win32 API编写程序的基础知识(和通用的写法).使用的语言是C,但大多数C++编译器也能成功编译,事实上,教程中的绝大多数内容都适用于任何 ...
- Win32编程API 基础篇 -- 4.消息循环
消息循环 理解消息循环 为了编写任何即使是最简单的程序,了解windows程序的消息循环和整个消息发送结构是非常有必要的.既然我们已经尝试了一点消息处理的东西,我们应该对整个程序有更深入的理解,如果你 ...
- Win32编程API 基础篇 -- 3.消息处理 根据英文教程翻译
消息处理 例子:窗口点击 好的,现在我们已经得到一个窗口了,但我们什么也做不了除了DefWindowProc()允许窗口大小被调整,最大最小化等...这不是很激动人心啊 在接下来的一小节中我将向你展示 ...
- Win32编程API 基础篇 -- 6.菜单和图标
菜单和按钮 例子:菜单1 本小节仅仅向你展示如果向你的窗口中加入一个基本的菜单,通常你会用到一个提前制作好的菜单资源,这会是一份.rc文件并且会被编译链接进你的.exe可执行程序中.这是具体的流程做法 ...
- Win32编程API 基础篇 -- 5.使用资源
使用资源 你可能想参考教程结尾的附近,为了获得跟VC++和BC++资源相关的信息. 在我们讲得更加深入之前,我将大致讲解一下资源的主题,这样在每个小节中我就不必再去重讲一遍了.在这一小节中,你不需要编 ...
- 小白也能看懂的Redis教学基础篇——做一个时间窗限流就是这么简单
不知道ZSet(有序集合)的看官们,可以翻阅我的上一篇文章: 小白也能看懂的REDIS教学基础篇--朋友面试被SKIPLIST跳跃表拦住了 书接上回,话说我朋友小A童鞋,终于面世通过加入了一家公司.这 ...
- ASP.NET Web API 基础篇1
ASP.NET Web API 直到我膝盖中了一箭[1]基础篇 无题 蓦然回首,那些年,我竟然一直很二. 小时候,读武侠小说的时候,看到那些猪脚,常常会产生一种代入感,幻想自己也会遭遇某种奇遇,遇到悬 ...
- 我拖拖拖--H5拖放API基础篇
不要搞错,本文不是讲如何拖地的.看过<javascript精粹>朋友应该知道,他实现拖放的过程比较复杂,现在时代不同了,我们用H5的新的拖放API就能非常方便的实现拖放效果了.最近在园子见 ...
- (转)Android高性能编程(1)--基础篇
关于专题 本专题将深入研究Android的高性能编程方面,其中涉及到的内容会有Android内存优化,算法优化,Android的界面优化,Android指令级优化,以及Android应用内存占 ...
随机推荐
- golang——字符串与编码
1.字符编码 (1)ASCII码 一个字节表示的英文.数字.标点符号等字符. 国际标准ASCII码为0-127即128个字符,二进制最高位为0,其余为扩展ASCII码. (2)GB2312 两字节,主 ...
- ACM_01背包2
背包4 Time Limit: 2000/1000ms (Java/Others) Problem Description: 有n个重量和价值分别为Wi,Vi的物品,现从这些物品中挑选出总量不超过W的 ...
- NLog简单配置与使用
对项目添加NLog 安装完成后,在项目里面会自动引入该引入的dll,并且会添加如下两个文件 NLog的配置主要是在这个config文件里.当然也可以将这个文件里面的nlog节点复制到项目配置文件App ...
- webpack学习汇总
一. 安装 window : 附件 --- 命令提示符 1:node -------- http://pan.baidu.com/s/1boFor3D node -v : 查看版本: npm conf ...
- sql server 行转列 要注意的问题 pivot
select * from ( select mvqr.VoteQuestionId,mvqr.AnswerSolution from JY_MemberVoteQuestionRef as ...
- Intellij使用心得(四) -- 导入Eclipse的代码格式化文件
https://my.oschina.net/flashsword/blog/137598
- 盒子模型,top和margin-top
1. 标准盒子模型: width只是内容的宽度. 元素的总宽度=width + padding*2 +border*2 +margin*2. IE盒子模型: width=内容的宽度 + padding ...
- Android Error:Failed to resolve: com.afollestad:material-dialogs:
背景: 同事把Android项目直接考给了我...我在Android Studio上运行,然后提示: Error:Failed to resolve: com.afollestad:material- ...
- PKI中常用编码:ASN.1 DER BER Base64
迟到了两年的笔记... 在PKI的应用中,常会用到以下几个编码概念: ASN.1(Abstract Syntax Notation One, 抽象语法标记) 定义:A standard interfa ...
- 前端axios发送的数据后端接收不到(没有自动依赖注入)可能的原因
前端请求头content-type没有进行正确设置,后端无法解析该类型数据,比如说: 后端若想接收json类型的数据,则需要配置相应的转换器,(spring中可使用@RequestBody注解),若没 ...