上次,我们一起写了一个Windows窗口程序,这个窗口程序虽然非常简单,但是,代码仍然很多,相信,一定会有很多初学者看见这些代码而感到头疼。不用怕,现在,我们就一起来分析一下这些代码,相信通过我们共同的努力,一定可以克服这些难题。

首先,我们要做的第一件事情就是包含windows.h头文件,这个不再解释了,因为,我们前面已经解释过。

之后,我们定义了一个WinMain函数和一个窗口处理函数MainWndProc。

相信,很多人读到窗口处理函数,就一定会发晕,不知道它是一个什么东东,如果大家想要明白窗口处理函数,就必须先明白Windows下的消息机制,现在,我先简要介绍一下什么是消息机制,如果想要深入研究的人,可以看看《Windows核心编程》,windows的消息机制是借由消息系统来完成的。

Windows的消息系统是由3个部分组成的:

1)消息队列:Windows为所有的应用程序都维护一个消息队列,应用程序必须从消息队列中获取消息,然后分派给某个窗口处理函数处理。

2)消息泵:通过这个消息泵,应用程序可以从消息队列中获取消息,然后,再把它分派给适当的窗口,最终由该窗口的窗口处理函数来处理它。

3)窗口处理函数:每个窗口都有一个窗口处理函数,它的任务就是处理发给该窗口的消息,处理完消息后,它通常要返回一个值给Windows,告诉Windows该消息处理是否成功。

相信通过上面的了解,大家大概对消息机制会有一个整体的把握,下面,我将以一个键盘的按键消息为例,告诉大家这个消息是如何产生,流转,处理。

当我们按下键盘上的一个字符后,比如a,这个时候,键盘的硬件会检测到这个动作,然后通知系统,系统知道后,会为它产生一个消息:WM_CHAR,之后,系统会将这个消息发送到当前程序的消息队列,比如当前程序是记事本,那么这个时候,记事本程序的消息队列中会出现一个WM_CHAR消息,当记事本程序的消息泵从消息队列提取到这个字符的按键消息后,会调用当前窗口的处理函数来处理这个字符消息,当窗口处理函数处理WM_CHAR时,会在屏幕上显示这个字符,我们这里,这个字符是a,这就是整个消息处理的全过程。

好了,现在,我们言归正传,继续分析接下来的程序。WinMain程序,这个是所有Windows程序的入口点,它是必须要有的,上次,我们已经讲过,这里不再提及。

接下来,我们来一起看一下这个WinMain函数都做了什么?

为了帮助大家理解WinMain函数,我们这里先使用自然语言进行概括性的描述。   

int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
1、定义一个WNDCLASS或者WNDCLASSEX变量wcx;
2、设置WNDCLASSEX变量wcx;
3、利用wcx创建注册一个窗口类MainWClass,窗口类就好比一个模版,Windows系统会参考这个模版,为我们创建窗口;
4、用我们创建的窗口类MainWClass,创建一个窗口;
5、调用ShowWindow和UpdateWindow显示和更新窗口;
6、创建一个消息泵,这个消息泵的任务就是不断从消息队列中提取消息,并将消息传递给当前窗口的处理函数,
当消息泵接收到WM_QUIT退出消息后,消息泵就会退出;
7、程序返回。
 }

好了,现在,我们继续分析WinMain函数的内容:

1)定义一个WNDCLASS或者WNDCLASSEX变量wcx,这个目标是通过如下的代码来完成的:

WNDCLASSEX wcx;

2)设置窗口类,具体实现是通过如下的代码:

wcx.cbSize = sizeof(wcx);           // 这个参数设置wcx的大小;
wcx.style = CS_HREDRAW |CS_VREDRAW; // 告诉系统,当窗口大小改变时,重绘整个窗口;
wcx.lpfnWndProc = MainWndProc; // 告诉系统,当前窗口类的消息处理函数是MainWndProc;
wcx.cbClsExtra = 0; // 这个大家先不用管,设置为0就可以;
wcx.cbWndExtra = 0; // 这个大家也先不用管,设置为0就可以;
wcx.hInstance = hinstance; // 告诉系统当前这个窗口属于哪个程序,系统会根据这个参数,将该窗口的消息发送到这个程序的消息队列;
wcx.hIcon = LoadIcon(NULL,IDI_APPLICATION); // 窗口图标:大家也先不用管,使用这个IDI_APPLICATION值,就可以;
wcx.hCursor = LoadCursor(NULL,IDC_ARROW); //设置这个窗口的光标样式,大家也先不用管,设置为IDC_ARROW,就可以;
wcx.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // 设置当前窗口的背景颜色,这里是白色WHITE_BRUSH;
wcx.lpszMenuName = NULL; //当前窗口的菜单,我们这个窗口没有菜单,所以,只需要设置为NULL,就可以;
wcx.lpszClassName = "MainWClass"; // 告诉系统,我们这个窗口的类名;
wcx.hIconSm = (HICON)LoadImage(hinstance, // 当前窗口的小图标,大家先这么写,先不用管具体什么意思,后面会介绍。
MAKEINTRESOURCE(5),
IMAGE_ICON,
GetSystemMetrics(SM_CXSMICON),
GetSystemMetrics(SM_CYSMICON),
LR_DEFAULTCOLOR);

3)利用wcx创建注册一个窗口类MainWClass,具体通过如下的代码来完成:

if (!RegisterClassEx(&wcx))
{
return 1;
}

4)用我们创建的窗口类MainWClass,创建一个窗口,具体通过如下代码来完成:

hwnd = CreateWindow(
"MainWClass", // 窗口类名
"我们的第二个程序", // 窗口标题
WS_OVERLAPPEDWINDOW, // 窗口样式
CW_USEDEFAULT, // 水平位置X:默认
CW_USEDEFAULT, // 垂直位置Y:默认
CW_USEDEFAULT, // 宽度:默认
CW_USEDEFAULT, // 高度:默认
(HWND)NULL, // 父窗口:无
(HMENU)NULL, // 菜单:使用窗口类的菜单
hinstance, // 应用程序实例句柄
(LPVOID)NULL); // 窗口创建时数据:无

5)调用ShowWindow和UpdateWindow显示和更新窗口,具体通过如下代码来完成:

ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);

6)创建一个消息泵,具体通过如下的代码来完成:

while ((fGotMessage = GetMessage(&msg, (HWND)NULL, 0, 0)) != 0 && fGotMessage != -1)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

7)程序返回,具体通过如下的代码来完成:

return msg.wParam; //这个msg通常是WM_QUIT,这个wParam是这个消息的退出码,通常由PostQuitMessage来设置,主要是通知系统,该程序是否正常退出。

相信,通过上面这段描述,大家一定对这个例子中的WinMain函数有了一个整体的把握,现在,我们趁热打铁,再一起分析一下我们这个窗口类的窗口处理函数。

现在,我先告诉大家窗口处理函数的格式,具体如下:

LRESULT CALLBACK 窗口处理函数名(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)

大家只需要记住,窗口处理函数的格式必须这么写,只有函数名可以改变,其它的都不可以改变,这个是系统规定的,记住就可以,其中WPARAM和LPARAM是Windows消息中的两个参数,当窗口处理函数被调用的时候,hwnd代表了当前的窗口,uMsg代表了当前的消息,在Windows中,每个消息都用一个整数来表示。wParam和lParam是这个消息中的两个参数。

下面,我先给出消息处理函数的一般流程:

LRESULT CALLBACK 窗口消息处理函数名(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
switch (uMsg)
{
case 消息1:
{
消息处理函数1;
break;
}
case 消息2:
{
消息处理函数2;
break;
}
...
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}

上面描述了消息处理函数的一般流程,我们按照这个一般流程来讲解一下我们的窗口处理函数,具体如下:

我们的窗口处理函数,只响应了一个消息WM_DESTROY,这个消息是在我们点击窗口的关闭按钮是产生的,当我们的消息泵接收到这个消息后,就会将它发给我们的窗口处理函数,然后,我们的窗口处理函数调用函数ExitThread,其中传递的是线程的退出码,线程正常退出,通常都设置为0,当执行完这个函数后,整个线程会结束运行,我们这里的线程只有一个,就是WinMian所在的线程,当这个线程退出后,整个程序就会结束运行。

对于其它的消息,我们都使用系统提供的默认消息处理函数来处理,这个函数的原型如下:  

LRESULT DefWindowProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM IParam);

在这个函数中,hWnd就是当前窗口的句柄,Msg就是当前要处理的消息,wParam和lParam就是当前消息中的两个参数。

好了,今天讲了很多,相信,大家读完后,应该会对我们的第2个Windows程序有所理解,希望大家多实践,如果有暂时不明白的内容,可以先放下,因为主要的内容,我已经告诉你们了,次要的后边会根据需要讲解。

第2个Wiindows程序讲解的更多相关文章

  1. OpenGL学习-------visual studio 2010配置和第一个OpenGL程序讲解

    OpenGL作为当前主流的图形API之一,它在一些场合具有比DirectX更优越的特性. 1.与C语言紧密结合. OpenGL命令最初就是用C语言函数来进行描述的,对于学习过C语言的人来讲,OpenG ...

  2. 第一个Windows程序讲解

    上次,我们一起写了第一个Windows程序,虽然程序非常简单,但是它却可以帮助大家建立学好windows开发的信心. 今天,就让我帮助大家分析一下这个程序的内容.首先,我们的程序包含了一个头文件:wi ...

  3. fpga Verilog hdl 按键消抖 部分程序讲解

    module debounce(clk_in,rst_in,key_in,key_pulse,key_state); input clk_in;//system clock input rst_in; ...

  4. C#可定制的数据库备份和恢复程序 (讲解流程)

    可定制的数据库备份和恢复程序 tashanzhishi [原作] 关键字 数据库 备份 恢复 出处 在我们做数据库系统的程序时,经常需要为客户做一个数据库的备份和恢复程序,特别是对于一些非专业的数据库 ...

  5. C指针(3)——指向指针的指针(程序讲解)

    int **q可以分成两部分,即int* 和 (*q),后面的 “q” 中的* 表示q是一个指针变量,前面的int*表示指针变量q只能存放int*型变量的地址.int** q表示为指针变量q只能存放i ...

  6. C指针(2)——指针在函数中的应用(程序讲解)

    3-1.c指针用作函数参数 #include<stdio.h> typedef unsigned char uint8_t; //类型自定义,通过typedef语句重新把unsigned ...

  7. C指针(1)——指针在数组中的应用(程序讲解)

    2-1.c数组指针的定义: #include <stdio.h> int main() { char str[]="China Beijing Fujian"; //定 ...

  8. php微信支付接口开发程序

    php微信支付接口开发程序讲解 微信支付接口现在也慢慢的像支付宝一个可以利用api接口来实现第三方网站或应用进行支付了, 下文整理了一个php微信支付接口开发程序并且己测试,有兴趣的朋友可进入参考. ...

  9. iBrand 开源电商小程序 (Laravel API+ webpack + gulp + 原生小程序)

    iBrand 社交电商产品正式进入开源过程中了,我们制定了详细的开源计划,目前已经发布了 V1 的版本,后续的版本也在陆续整理完善中. 各个版本功能明细如下图: 3 个版本计划在今年春节前全部完成,可 ...

随机推荐

  1. Web日程管理FullCalendar

    fullcalendar是一款jQuery日程管理控件,提供了丰富的属性设置和方法调用,官网下载地址http://fullcalendar.io/download,眼下最新版本号是2.3.2. 仅仅要 ...

  2. [CSS3] Using CSS Combinators to Identify Siblings and Descendants in CSS

    CSS combinators allows us to reference the DOM relationship between two or more elements in CSS. < ...

  3. JAVA集合差异

    接口 简述 实现 操作特性 成员要求 Set 成员不能重复 HashSet 外部无序地遍历成员 成员可为任意Object子类的对象,但如果覆盖了equals方法,同时注意修改hashCode方法. T ...

  4. Java数字、货币值和百分数等的格式化处理

    如果我们用下列语句输出一个数 System.out.println(123456.789); 将会在Console看到输出 123456.789 那么如何得到123,456.789这种格式化的输出呢? ...

  5. My.Ioc 代码示例——Lifetime 和 ILifetimeScope

    很多 Ioc 框架在创建对象的过程中,都会采取某种方式来缓存/复用/释放已构建的对象.在 My.Ioc 中,这个目的是通过 Lifetime/ILifetimeScope 来实现的.其中,Lifeti ...

  6. SQL Server 存储过程分页

    每每面试,总会有公司问到分页.在下不才,在这里写几种分页,望路过的各位大神尽情拍砖. 先从创建数据库说起.源码如下 一.创建数据库 /********************************* ...

  7. EBS基础—表的后缀

    1._ALL或无后缀:基表,所有对数据操作最终都是对基表的操作,表包含所有不同经营单位的信息,多组织环境. 2._B/_T:也是一种基表.一些数据和验证存储在此表中. 3._TL:语言的基表,TL表支 ...

  8. iOS微信支付

    SDK接入 服务器签名版本 官方已经是建议使用服务器签名来接入微信支付,实际上从安全上考虑,确实是每个客户端不应该知道RAS密钥,也不需要每个客户端都写一遍签名的算法. 服务端接入流程文档:https ...

  9. 重温web服务器--细说Tomcat服务器

    从大学开始接触java web的开发时就开始使用tomcat部署web项目,对它的理解仅仅停留在"这是个开源免费的servlet容器"的阶段,后来也接触了一些tomcat的体系,原 ...

  10. python自学笔记

    python自学笔记 python自学笔记 1.输出 2.输入 3.零碎 4.数据结构 4.1 list 类比于java中的数组 4.2 tuple 元祖 5.条件判断和循环 5.1 条件判断 5.2 ...