Windows编程系列——第二讲:创建窗口(上)
1.窗口
上一节我们创建了一个Windows桌面应用程序。这个程序可以直接编译运行,运行后如图:
这就是Windows最基本的元素——窗口。下面介绍它的基本元素:
首先,窗口最外侧是边框;最上方是标题栏;标题栏的左侧是图标,紧挨着是标题(就是图片中的”WindowsProject1”);标题栏右侧依次是最小化按钮、最大化按钮、关闭按钮;标题栏的下方是菜单,准确的说是”下拉菜单”。中间很大的空白区域就是客户区。
可以说,对话框、复选框、滚动条、文本框都是各种各样的窗口,或者更准确的说,是”子窗口”、”控件窗口”或”子窗口控件”。
接下来,我们对照代码,解释代码的含义
2.入口函数
初学C/C++我们知道,main()函数是程序的入口;而在编写Windows应用程序时,要求入口函数名是WinMain,对应的Unicode则是wWinMain,如下:
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
这就是入口函数。
你可能会问,wWinMain前面有一个修饰APIENTRY是什么东西?我们可以在系统头文件windef.h中找到它的定义,它和后面马上会出现的CALLBACK都是__stdcall(注意是两个连续的下划线)。这是一种函数调用方式,规定了参数传递(例如从左到右还是从右到左)、退出时的操作等细节问题。与__stdcall对应的还有__cdecl、__fastcall、__thiscall等等,初学者不必深究。
言归正传,WinMain或wWinMain函数有四个参数,我们去MSDN搜一下这个函数:
点击第一个条目。下面是参数解释:
- hInstance是指当前应用程序的实例句柄。
- hPrevInstance.现在这个参数不再使用了,永远为NULL。以前用它来跟踪应用程序的前一个实例。
- lpCmdLine。类似于main(int argc,char*argv)中的argv,接收命令行参数。
- nCmdShow,主窗口初始化时的显示方式,即如何打开程序的窗口。此参数可以取以下值:
参数 | 值 | 含义 |
---|---|---|
SW_MAXIMIZE | 3 | 最大化指定的窗口 |
SW_MINIMIZE | 6 | 最小化指定的窗口并且激活在Z序中的下一个顶层窗口 |
SW_SHOW | 5 | 在窗口原来的位置以原来的尺寸激活和显示窗口 |
SW_HIDE | 0 | 隐藏窗口并激活其他窗口 |
SW_SHOWMINNOACTIVE | 7 | 窗口最小化,激活窗口仍然维持激活状态 |
SW_SHOWNA | 8 | 以窗口原来的状态显示窗口。激活窗口仍然维持激活状态 |
SW_RESTORE | 9 | 激活并显示窗口。如果窗口最小化或最大化,则系统将窗口恢复到原来的尺寸和位置。在恢复最小化窗口时,应用程序应该指定这个标志 |
SW_SHOWMAXIMIZED | 3 | 激活窗口并将其最大化 |
SW_SHOWMINIMIZED | 2 | 激活窗口并将其最小化 |
SW_SHOWNOACTIVATE | 4 | 以窗口最近一次的大小和状态显示窗口。激活窗口仍然维持激活状态 |
SW_SHOWNORMAL | 1 | 激活并显示一个窗口。如果窗口被最小化或最大化,系统将其恢复到原来的尺寸和大小。应用程序在第一次显示窗口的时候应该指定此标志 |
关于返回值,我把翻译结果贴出来:如果该函数成功, 则在接收到 WM_QUIT 消息时终止, 它应返回该消息的wParam参数中包含的退出值。如果函数在输入消息循环之前终止, 它应该返回零。至于什么意思,我暂时不去弄懂。
3.消息和窗口函数
创建窗口后,就要对窗口的行为负责。例如:当用鼠标拖动窗口的边框时,窗口的大小会随之改变;当用户拖拽窗口的标题栏时,窗口会随之移动;当点击最大化按钮时,窗口会最大化……
但是,应用程序如何知道用户在窗口上的动作呢?是不是应用程序完成了这些动作?不是。是Windows本身而不是应用程序处理了窗口的所有命令,只不过Windows会以“消息”的形式通知应用程序:“窗口被拖拽啦”、“窗口最大化啦”。在接收到这些“消息”后,应用程序会根据需要重新绘制窗口。
那么,Windows是怎么向应用程序发送消息的呢?对于程序来说,所谓发送消息就是调用某一个函数,并把消息的内容作为函数的参数进行传递。对于Windows应用程序来说,除了要求必须有主函数(Win Main)外,还要求必须有一个窗口函数。当Windows向应用程序发送消息时,它会调用程序中的窗口函数。窗口函数原型如下:
LRESULT CALLBACK WndProc(
HWND hWnd,//窗口句柄
UINT uMsg,//消息类型
WPARAM wParam,//消息参数
LPARAM lParam //消息参数)
注意官方文档和一些参考书中也会把窗口函数定义为WindowProc。
第一个参数表示接收消息的窗口的句柄;
第二个参数表示消息的类型,例如WM_LBUNTTONDOWN(鼠标左键按下)、WM_CLOSE(关闭窗口)、WM_KEYDOWN(键盘按下)、WM_TIMER(定时器消息),等等;
第三、四个参数针对不同的消息会有不同的含义,例如针对WM_MOUSEMOVE(鼠标移动)消息,两个参数就用来表示鼠标位置;而针对WM_KEYUP(按键弹起)消息,两个参数就用来表示键码信息。
下一讲我们会详细讲述窗口函数。
4.进队消息与不进队消息
WinMain里面有一个消息循环,它调用GetMessage从消息队列中取出消息,并且调用DispatchMessage讲消息发送给窗口函数。
// 主消息循环:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
Windows把消息分为“进队消息”和“不进队消息”:
进队消息是指Windows把消息放入程序的消息队列中,通过消息循环把消息发送给窗口函数
不进队消息在Windows调用窗口函数时直接发送给窗口函数
殊途同归,窗口函数都将获得窗口的所有信息——包括进队和不进队的。
进队消息主要是各种输入,例如各种按键消息、字符消息、鼠标消息、定时器消息以及刷新消息和退出消息。
不进队消息来自于调用特定的API函数。例如,
当WinMain函数调用CreateWindow函数时,Windows将创建窗口并给窗口函数发送一个WM_CREATE消息
;
当WinMain函数调用ShowWindow函数时,Windows将创建窗口并给窗口函数发送一个WM_SIZE和WM_SHOWWINDOWS消息
;
当WinMain函数调用UpdateWindow函数时,Windows将创建窗口并给窗口函数发送一个WM_PAINT消息
。
总结一下,各种消息都是以一种有序的、同步的方式进出的,窗口函数可以处理它们(也可以调用DefWindowProc进行默认的处理)。在这里,“有序的、同步的”的含义是当窗口函数在处理一个消息时,不会被其他的消息所中断。
5.MessageBox
MessageBox的作用是弹出一个小的对话框,向用户显示短信息,并将用户的选择返回给调用者。尝试在代码中添加如下一行代码:
// TODO: 在此处放置代码。
MessageBox(NULL, L"Hello World!", L"My First Windows APP", MB_OKCANCEL);
运行结果如下图:
函数原型:
int MessageBox(
HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
);
解释一下函数参数:hWnd参数是指父窗口句柄,这里为NULL,表示没有父窗口;lpText参数是将要显示的字符串;lpCaption参数是对话框的标题栏名称;uType参数用来指定对话框的内容和行为。
这里针对第四个参数说明一下,我们在MSDN中可以找到这个参数的详细说明。例如上面的实例中,我们使用了MB_OKCANCEL,表示对话框出现“确定”和“取消”两个按钮。这里,MB_OKCANCEL是一个字符常量。更多的uType常量可以点击这里查看。