首页
社区
课程
招聘
[原创]最简单的MFC入门教程---WinMain函数基础篇
发表于: 2010-5-24 14:31 44870

[原创]最简单的MFC入门教程---WinMain函数基础篇

2010-5-24 14:31
44870
最简单的MFC入门教程
                            blog同步更新中:http://blog.csdn.net/xiaobo68688
您可以随便复制本教程,但是请务必保证本教程的完整性,如果您发现有任何技术方面的漏洞请和xiaobo68688@qq.com联系,或者只是想和我交个朋友也OK!
        Kunsa拜上!~~~~
这几天萌生了一些写一点东西的想法,以来把自己的知识和大家共享一下,二来是对自己知识的一个总结。就算是一举两得吧。我写的这些文章都是结合我的切身体会,大言不惭地说:我也经历过一些实际的开发,所以我很能体会到实际工作中的一些漏洞,同时,我又是一个初学者,因此我更能理解初学者在哪些地方会出现问题。如果大家支持的话,我争取将这个教程做下去,与其说是教程,倒不如说是从网上搜集的一些资料,但是这些东西都是经过我的整理的,绝对适合初学者学习!

学学侯捷老师:勿在浮沙筑高台
        像大多数教程一样,我们需要了解一下Windows的SDK编程方式,刚刚突然萌生了一个不成熟的想法,不知道对不对,大家应该对控制台下的编程有了一些概念,SDK的编程方式就好像是控制台下的C,没有封装(或者说封装稍微少一些),而MFC的编程方式就好像是控制台下的C++,动不动就来个类,来个对象,来个虚函数,+_+。
        所以我们有必要把SDK的编程方式搞清楚,把SDK下的窗口创建自己实现一遍。再次说明,这些技术文档是纯入门,但是我尽量让您在入门后达到精通。
        先来一个最简单的SDK程序,显示一个HelloWorld的MessageBox(MessageBox是一个API的名字,API全称为:Application Programming Interface,即:应用编程接口,我将在下边进行详细说明)。
       
        #include <windows.h>
        int WINAPI WinMain(
                                           HINSTANCE hInstance,               
                                           HINSTANCE hPrevInstance,
                                           LPSTR lpCmdLine,
                                           int nCmdShow
                                           )
        {
       
                MessageBox(NULL,L"dd",L"dddd",MB_OK);
                return 0;
       
        }
       
        首先是头文件,我们注意到SDK和控制台下的编程包含的头文件是不一样的,百度百科上边这样说:
        WINDOWS.H是主要的含入档案,它包含了其他Windows表头档案,这些表头档案的某些也包含了其他表头档案。这些表头档案中最重要的和最基本的是:
          WINDEF.H 基本型态定义。
          WINNT.H 支援Unicode的型态定义。
          WINBASE.H Kernel函式。
          WINUSER.H 使用者介面函式。
          WINGDI.H 图形装置介面函式。
          这些表头档案定义了Windows的所有资料型态、函式呼叫、资料结构和常数识别字,它们是Windows文件中的一个重要部分。
       
        如此看来,windows.h这个头文件包含了我们编程的一些基本需要的东西。在深入理解之前,先记住吧。
        然后就是main函数的入口,我们会发现,和控制台的main函数是不一样的,但是功能和控制台下的类似,都是程序的入口点,参数稍微多了点,我们从第一个开始看。
        HINSTANCE hInstance:MSDN上边这样解释:[in] Handle to the current instance of the application. 表示该程序当前运行的实例句柄(您可能对句柄的概念不是很清楚,我会在下文写出),同一个程序可以在同时运行多个实例,操作系统会为每个实例分配一个实例句柄,该实例句柄就传给这个参数。
        HINSTANCE hPrevInstance:MSDN:[in] Handle to the current instance of the application.在同一个计算机上运行同一个程序的时候计算机会为它分配不同的实例句柄(关于句柄我会在下边讲解),这个参数就是存储的程序本身的实例句柄。
        HINSTANCE hPrevInstance:MSDN:[in] Handle to the previous instance of the application. This parameter is always NULL. If you need to detect whether another instance already exists, create a uniquely named mutex using the CreateMutex function. CreateMutex will succeed even if the mutex already exists, but the function will return ERROR_ALREADY_EXISTS. This indicates that another instance of your application exists, because it created the mutex first. However, a malicious user can create this mutex before you do and prevent your application from starting. To prevent this situation, create a randomly named mutex and store the name so that it can only be obtained by an authorized user. Alternatively, you can use a file for this purpose. To limit your application to one instance per user, create a locked file in the user's profile directory.这个说的有点长,其实就是当程序不是第一个实例的时候,这个参数存储的是上一个实例的实例句柄,当是第一个实例的时候,存储的是NULL。
        LPSTR lpCmdLine:MSDN:[in] Pointer to a null-terminated string specifying the command line for the application, excluding the program name. To retrieve the entire command line, use the GetCommandLine function.这个很好理解,该参数里边存储着传递给应用程序的参数串,举个例子:打开c盘的1.txt文件的时候,使用的是notepad(记事本),那么传递给notepad这个实例的参数串就是c:\1.txt。
         int nCmdShow:[in] Specifies how the window is to be shown. 指定该窗口的显示方式:最大化,最小化,正常甚至是隐藏等。
        看完了参数再回来看WinMain前边的WINAPI,我们通过“转到定义”可以发现WINAPI其实是 #define WINAPI __stdcall 。 关于__stdcall 百度百科上边这样解释: 被__stdcall修饰关键字修饰的函数,其参数都是从右向左通过堆栈传递的(__fastcall的前面部分由ecx,edx传),函数调用在返回前要清理堆栈,但由调用者还是被调用者清理不一定。 其实就是约定了一个堆栈平衡的方式。
        下边我们说一下句柄,句柄在Windows编程里边是一个很重要的概念,但是重要并不代表难理解,摘自百度百科:WINDOWS程序中并不是用物理地址来标识一个内存块,文件,任务或动态装入模块的。相反,WINDOWS API给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作。在《WINDOWS编程短平快》(南京大学出版社)一书中是这么说的:句柄是WINDOWS用来标识被应用程序所建立或使用的对象的唯一整数,WINDOWS使用各种各样的句柄标识诸如应用程序实例,窗口,控制,位图,GDI对象等等。WINDOWS句柄有点象C语言中的文件句柄。从上面的2个定义中我们可以看到,句柄是一个标识符,是拿来标识对象或者项目的。它就像我们的车牌号一样,每一辆注册过的车都会有一个确定的号码,不同的车号码各不相同,但是也可能会在不同的时期出现两辆号码相同的车,只不过它们不会同时处于使用之中罢了。应用程序几乎总是通过调用一个WINDOWS函数来获得一个句柄,之后其他的WINDOWS函数就可以使用该句柄,以引用相应的对象。在WINDOWS编程中会用到大量的句柄,比如:HINSTANCE(实例句柄),HBITMAP(位图句柄),HDC(设备描述表句柄),HICON(图标句柄)等等。这当中还有一个通用的句柄,就是HANDLE。 初学者会认为句柄和指针有很多共同点,其实却是是有很多共同点,但是还是有不一样的,句柄只是一个标识(仅仅起到代表作用),而指针是指向。两者不同!但是又有相似的地方,比如,Windows中想要操作一个对象就要先得到它的句柄,当然,得到句柄的方法也有很多,我们在接下来遇到的时候再详细讲吧。
        这样我们就完成了一个最简单的Windows环境下的程序。呼呼。好累!

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 0
支持
分享
最新回复 (16)
雪    币: 261
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
谢谢楼主分享,学习下
2010-6-3 13:15
0
雪    币: 233
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
写文章不容易!
拜读了!
2010-6-22 14:14
0
雪    币: 41
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
下来学习了!感谢楼主
2010-6-24 09:49
0
雪    币: 203
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
感觉用处不大,但也谢谢了.
2010-6-24 16:46
0
雪    币: 260
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
回帖是美德~
2010-6-27 23:05
0
雪    币: 285
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
谢谢分享!!
2010-6-29 08:56
0
雪    币: 16
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
DOS到WINDOWS是个很艰难的过程
2010-8-11 11:05
0
雪    币: 278
活跃值: (709)
能力值: ( LV15,RANK:520 )
在线值:
发帖
回帖
粉丝
9
顶哈!希望多多更新
2010-8-18 20:39
0
雪    币: 90
活跃值: (91)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
我补充点
===============
Win32学习笔记 第一章 开始

作者: 姜学哲(netsail0@163.net)

教材: Windows程序设计(第五版)北京大学出版社
[美]Charles Petzold 著
北京博彦科技发展有限公司 译  ¥:160
参考资料:
Windows 应用程序设计原理_方法_技术(因为是PDF格式的EBOOK,作者等不详)
新编Windows API 参考大全  电子工业出版社  ¥:98
C++ Primer(第三版)中国电力出版社 Stanley B.Lippman & Josee Lajoie 著 潘爱民 张丽 译 ¥:128
TURBO C实用大全 徐金梧 杨德斌 徐科 编 ¥:42

环境: windows2000 server + Internet Explorer 6.0 + DirectX7.0 + Visual C++ 6.0

图们江计算机程序编制小组(chulsoft.xiloo.com)版权所有,转载请说明出处
------------------------------------------------------------------
【第一章 开始】

爽完了"星际争霸"后不禁有些遗憾。如果星际的地图再大一点,人口限制再多一点就好了。地图尺寸应该是8人地图的81倍,人口应该是6000。目前来说这是不可能的事情。没有任何一台机子能承受得了。我是想指挥一场真正的战争。就像想当年刘邓大军和国民党军打仗一样。其实星际已经很优秀了,我不能再要求什么。

说到这里,我想到了我们最常用的操作系统Windows系列。我去过很多论坛,包括Linux论坛在内,绝大多数网友们非常喜欢骂微软公司和该公司的所有产品。以前我也骂过,后来意识到了自己的错误后,再也不骂了。有很多人用着盗版的WINDOWS和IE上网骂微软。有种的用LINUX上网骂微软行不?要不买个正版再骂也可以呀。

我最喜欢的公司是微软。因为在微软程序员的地位是非常高的,特别是总部。微软是两个天才程序员创建的。向往程序员的我没有理由不喜欢微软。

给微软拍了这么多的马屁,怎么能不懂WINDOWS系统呢?所以我要学习WIN32。彻底了解微软公司的产品。

学习WIN32,也就是读Windows程序设计(第五版)要有三个先决条件。

首先我们应该从用户的角度熟悉WINDOWS系统。意思是说会使用WINDOWS。我想这一点很多人都能做到。

第二,应该了解C语言。为什么?WINDOWS是用C写的,书中是用C语言讲解的,这就是原因。当然如果您愿意,也可以用Pascal写。不过您得找另一本书了。书名应该是Windows程序设计(Pascal版)。

第三,应该有一个Visual C++ 6.0。我也不指望您用的是正版。一万多RMB,比我的电脑还贵。

书上说,我们可以没有任何图形用户界面的编程经验。very good!

微软出版的书嘛,当然免不了大大地夸耀一下辉煌的历史了。

下面来看一看WIN32版的"Hello World",哦,对不起!应该是"Hello Windows98!"才对。

#include<windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
MessageBox(NULL, TEXT("Hello, Windows98!"), TEXT("HelloMsg"), 0);

return 0;
}

C语言中入口应该是main(),但是到了WIN32就变了,WinMain()代替了main()。对于从来没有接触过WIN32的初学者来讲,上面的程序可能会使您一头雾水。

MessageBox()是对话框函数。是Windows系统提供的。这就是传说中的Windows API(应用程序接口)。函数功能是显示一个对话框。对话框所显示的内容就是第二个参数TEXT(Hello, Windows98!)。第三个参数中的字符串会出现在标题栏中。

有关TEXT(),这是一个宏定义,也就是用define定义的。这是为了兼容UNICODE字符集而做的改动。以后不管是什么时候,您最好把字符串都用TEXT()括起来,有关UNICODE,那是第二章的问题,所以在第一章这个问题根本就不成为问题。

头文件windows.h中包含了其它的头文件,这些头文件中的一部分又包含了另外的一些头文件。

◆WINDEF.H
◆WINNT.H
◆WINBASE.H
◆WINUSER.H
◆WINGDI.H

这些头文件定义了Windows的所有数据类型,函数调用,数据结构和常数标识符。

WinMain()前面的WINAPI在WINDEF.H中定义如下:

#define WINAPI __stdcall

具体的含义我不知道。可能后面会有详细讲解吧。

#define MB_OK                   0x00000000L
#define MB_OKCANCEL             0x00000001L
#define MB_ABORTRETRYIGNORE     0x00000002L
#define MB_YESNOCANCEL          0x00000003L
#define MB_YESNO                0x00000004L
#define MB_RETRYCANCEL          0x00000005L

上面的是MessageBox()的第四个参数选项。想了解他们的具体含义很简单。上面的HelloMsg程序中MessageBox()的第四个参数是零,您可以把那个零换成上面六个常量中的一个,比如:

MessageBox(NULL, TEXT("Hello Windows98!"), TEXT("HelloMsg"), MB_OKCANCEL);

重新编译后您会发现对话框中多了一个'取消'按钮。如果您使用的是英文版,两个按钮分别是 OK & CANCEL。您可以一个一个的试。

#define MB_ICONHAND             0x00000010L
#define MB_ICONQUESTION         0x00000020L
#define MB_ICONEXCLAMATION      0x00000030L
#define MB_ICONASTERISK         0x00000040L

这些都是对话框中的图标选项。可以用C语言中“|”运算符与前面所提到的六个选项中的一个结合起来,比如:

MessageBox(NULL, TEXT("Hello Windows98!"), TEXT("HelloMsg"), MB_OKCANCEL | MB_ICONHAND);

运行上面的程序后您将会看到原先的对话框中多出了一个红色的图标,那是一个表示出错时用的“叉叉”,这个图标我不喜欢,我比较喜欢感叹号,所以把程序改成:

MessageBox(NULL, TEXT("Hello Windows98!"), TEXT("HelloMsg"), MB_OKCANCEL | MB_ICONASTERISK);

运行后您可以非常Lucky地看到对话框中出现了一个白底蓝字的感叹号。

就这样,第一章的内容算是学完了。是不是有股非常非常强烈的,想要写应用软件的冲动?不要着急,慢慢来。下一章简单了解一下Unicode。

==============================

【第二章 Unicode】

佛曰:众生皆平等。但是人却可以利用较高等的智慧站在了众多生物的至高点。吃早饭的时候我看着被做成菜的那条鱼,想到它也是生命。所以说到底,还是有些等级之分。学习WIN32过程中让我知道了Unicode的存在。Unicode最终会取代ASCII码成为标准。但是之前还有很长一段路要走。虽然Unicode的优势很明显,但是因为历史遗留问题,ASCII会存在很长一段时间。

学习C语言的时候您应该接触过ASCII码。学习本章的内容需要ASCII知识。

学习Unicode有必要了解字符集的历史。从最早的象形字开始,我们使用字符文字已经有近6000年了。19世纪的几个发明家发明了电报,当时在电报中使用的代码是Morse代码。字母表中的每个字符对应于一系列短和长的脉冲。

计算机是处理的数据其实是一系列1和0。每一段数字都代表一种字符。这就是ASCII码。7位数的ASCII码对于美国的字符集支持得很好。不幸的是地球上有一百多个国家和地区,2000多个民族。对于美国以外的用户来讲在计算机中显示自己国家的文字困难重重。

尤其是中国,日本,朝鲜更是如此。以中国为例,有数也数不清的汉字。办法总是有的。人们引入了"代码页"和"双字节字符集"的概念。这种编码方式非常庞大和复杂,不利于维护。这个时候Unicode应运而生了。

Unicode的解决方案非常简单。既然不能用7位或者8位数值表示,那么我们应该试一下更宽的值。例如16位,这样就允许表示65536个字符。Unicode和ASCII是兼容的。也就是说,前128个字符的数值是相同的。

Unicode的最大好处是只有一个字符集。当然Unicode也有缺点,Unicode占用的内存是ASCII码的两倍。而且人们还不太习惯Unicode。

对于程序员来讲,8位的ASCII码和16位的Unicode是我们必须面对的问题。为了解决宽字符(16位)问题,Windows在头文件中定义了"新"数据类型。

typedef unsigned short wchar_t;

可以看出wchar_t其实是16位的无符号短整型数。Windows用这种方法存贮16位字符。

wchar_t *p = L"Hello!";

在"Hello!"前有一个大写字母L(代表long)。这将告诉编译器该字符串按宽字符保存。即每个字符占用2个字符。存贮该字符串需要14个字节。字符串末尾还有一个/0也需要2个字节。

我们都知道如何获得字符串的长度。

int iLength;
char *pc = "Hello!";
iLength = strlen(pc);

函数strlen()返回字符串的长度,长度将不包括末尾的/0。变量iLength将等于6,也就是字符串中的字符数。

接下来我们试着用strlen()检查宽字符的字符串。

wchar_t *pw = L"Hello!";
iLength = strlen(pw);

strlen()的参数应该是char类型的指针,但是现在却接受了一个unsigned short类型的指针。编译后您会发现iLength等于1。

why?字符串"Hello!"中的6个字符宽字符代码如下:

0x0048 0x0065 0x006c 0x006c 0x006f 0x0021

Intel处理器在内存中将其存为:

48 00 65 00 6c 00 6c 00 6f 00 21 00

strlen()的工作过程是遇到0就结束。因为0表示一个字符串的结束。当读完"48"后strlen()遇到的是0,所以strlen()返回1。

由上例可以看出C语言的函数无法正确处理宽字符。在参数中有字符串的函数全部需要重写。strlen()的宽字符版本是wcslen(),并且在STRING.H中和WCHAR.H中均有声明.

现在我们知道,要得到宽字符串的长度,可以调用

iLength = wcslen(pw);

该函数返回将返回字符串中的字符数6。请记住,改成宽字节后字符串的字符长度不变,只是字节长度改变了。千万不要混淆。

因为Unicode占用两倍的存储空间,所以宽字节运行库中的函数比常规的函数大。所以最好是建立两个版本的程序,一个处理ASCII字符串,另一个处理Unicode字符串。但是这样以来又引来了另一个小问题。因为每一个实现特定功能的函数都有两个版本,所以名字不好记。不管是ASCII版本还是Unicode版本,都用相同的名字该多好啊?幸好这个问题已经得到了解决。

解决办法是使用Visual C++包含的TCHAR.H头文件。该头文件不是标准C的一部分。为了与标准C的头文件分别开来,该头文件内定义的每个函数和宏定义的前面都有一条下划线。

TCHAR.H为需要字符串的标准运行库函数提供了一系列的替代名称。有时这些名称被称为"通用"函数名,因为它们既可以指向函数的Unicode版本,也可以指向ASCII版本。
以_tcslen()为例如果定义了_UNICODE的标识符,并且程序中包含了TCHAR.H,那么_tcslen()就定义为wcslen():

#define _tcslen wcslen

如果没有定义_UNICODE,则_tcslen()被定义为strlen()。

#define _tcslen strlen

TCHAR.H还用一个新的数据类型TCHAR来解决两种字符数据类型的问题。如果定义了_UNICODE标识符,那么TCHAR就是wchar_t:

typedef wchar_t TCHAR;

否则TCHAR就是char:

typedef char TCHAR;

还记得第一章里出现过的TEXT()吗?那是为了兼容UNICODE字符集所做的改动。下面就来看看TEXT()在头文件中是怎么定义的。

#define __T(x)    L##x

后面的L##x您可能看不懂。很少有书提到它。但那确实是标准C预处理的一部分。这一对"##"称为粘贴号(token paste)。看来我们对标准C的了解还不够。是时候买本"The C Programming Language"了。它将字母L添加到宏参数上。

__T("Hello!") 等于 L##"Hello!" 等于 L"Hello!"。

此外还有两个宏与__T定义相同:

#define _T(x) __T(x)
#define _TEXT(x) __T(x)

WINNT.H头文件中还定义了一个宏,该宏也跟__T一样,将L添加到字符串前。

#ifdef UNICODE
#define __TEXT(quote) L##quote
#else
#define __TEXT(quote) quote    //quote就是用引号的意思
#endif

#define TEXT(quote) __TEXT(quote)

在本书中使用的就是TEXT(quote)。现在您知道了为什么会把字符串用TEXT()括起来了吧。what?还不明白?您可以先恶补标准C,然后再多读几遍。再不会那就是天份问题了。不过,连像我这样的村民都能看懂,您没理由不会啊!

◎第三十四页

scrnsize是可以检测出您当前显示器分辨率的东东,比如我的是1024*768

有关这个程序,运行一次就可以了,不要想看懂它,让他见鬼去吧。我们要看懂的是第三章的HelloWin。可能看完几遍后您对第二章还是不太懂,没关系,一回生,两回熟。再多看几遍就可以了。

==============================

【第三章 HelloWin】

4月16日是值得纪念的日子。主页上传第十六天,我们的网站访问量是19500。我们什么都没有做,连搜索引擎都没有提交。所以感到很意外。怎么会有这么多人访问的,访客是怎么知道我们的网站的呢?真是匪夷所思。

经过了前两章的准备活动后要正式看WIN32代码了。在这之前我假设您已经具有C语言的知识。如果您没有C方面的知识,那么抓紧时间学吧。第三章的重点就是第三十九页开始的HelloWin了。我们的全部任务就是看懂它。可能是我特别低能+没耐心,几个月前第一次看到第三章的时候我选择了放弃,那时候觉得我这辈子都看不懂它,真是惭愧。

对我来讲它实在是太难了。因为很多新知识点对我来讲都是空白。头脑中一点概念都没有,真的是十足的“一头雾水”。因为感到WIN32太难,我又转向了DOS下的C,也就是TC2环境下的编程,学了一会儿,又觉得TC2也很难,又回到了WIN32,我也知道这种学习态度是不好的,虽然我这次从TC2转到WIN32的原因又是避重就轻,但是这次一定要学好WIN32,这再也不能像前几次那样了。

其实现在想起来,我缺的就是耐心,如果当时多看几遍就能看懂的,白白浪费了那么多时间。所以,如果您觉得这个程序太难,您一定要坚持多读几遍。过程是比较枯燥的。从一开始您就会碰到一些困难,比如Win32专用的术语。有些术语的含义简单,却很不好解释。

我们需要一段时间来适应。开头处您会碰到“窗口过程”,“窗口类”等术语。因为头脑中完全没有概念,所以当您看到这些术语的时候可能产生对人生的绝望。请珍稀您的生命,坚强一点,对付这些术语的方法是硬着头皮往下读。可以先不管具体的意思。只要在脑子里留点印象就可以了。等您看懂了HelloWin代码后再回过头来重读 N 遍,您会有意外的收获。

我是农民出身嘛!再加上智商也不是很高,读书只能用这种笨办法了。这种事要靠天份的。可能您看一遍就懂了也说不定。

当您读完了第三章后可能还有很多疑点。其中的相当一部分将在第四章中解释。所以当您看到了一个疑点,书中又没有详细说明时可以先跳过那段。学过后面几章后再回过头来看一看。这样做绝对不是浪费时间。

为了学好WIN32我曾经想过放弃"帝国时代"和"星际争霸",不不!!我死也不放弃!!我的最爱啊!可能随着学习的深入这些游戏会渐渐离我远去。对了,不知道星际什么时候出2代啊?

在前两章示例程序使用了MessageBox()来向用户输出文本。MessageBox()函数创建一个消息框窗口。在Windows中"窗口"一词有确切的含义。一个窗口就是屏幕上的一个矩形区域。它接收用户的输入,并以文本或图形的格式显示输出内容。

MessageBox()创建一个消息框窗口,但这只是一个功能有限的特殊窗口。这个窗口有一个带关闭按钮的标题栏、一个可选的图标、一行或多行文本,以及最多4个按钮。

MessageBox()虽然有用,但我们不能在消息框中显示图形,也不能在消息框中添加菜单。想要添加这些东东就需要创建自己的窗口,现在就开始。

书上说创建窗口很简单,只需要调用CreateWindow()即可。实际上Windows已经做好了大部分事情,我们所要做的只是记住API函数的名字和功能,然后到了实际的开发过程中照搬就可以了。这听起来似乎很简单,但是事实上还有很多工作需要程序员来做。

零件虽然做好了,但是组装起来还是得费一番功夫的。另外如果您的英语水平过得去,将对学习有很大的帮助。微软的MSDN是很好的东东。可惜我不会看啊。

进行Windows程序设计,实际上是进行一种面向对象的程序设计(OOP)。不要跟我说C不支持面向对象,我什么都不知道。

桌面上最明显的窗口就是应用程序窗口。这些窗口含有显示程序名称的标题栏,菜单,工具栏,滚动条。装饰对话框表面的还有各式各样的按钮,单选框,复选框,列表框,滚动条和文本输入区域。这些都是窗口。更确切地说这些都称为"子窗口","控件窗口","子窗口控件"。

其实按钮也是一种窗口。比如Windows的“开始”菜单按钮,它就是一个窗口,叫做“按钮窗口”。如果在不使用Windows API的情况下以C语言编写滚动条最快也得两个星期,而且写出来的滚动条一定是很难看的那种。对于像我们这样的菜鸟来说自己写滚动条函数并不是令人Happy的事情。非常Lucky的是Windows已经提供了一大串现成的滚动条函数。

当我们移动鼠标的时候,当我们敲键盘的时候,当我们单击鼠标左右键的时候…当…当…当…,会产生一种信号。这种信号代表某种操作。我们可以用一种信号表示鼠标的移动,用另一种信号表示鼠标左键的按下等等等等。Windows把这种信号称为“消息”。

当用户点击一个窗口时这个窗口就会收到一个“消息”。窗口以“消息”形式接收窗口的输入,窗口也用消息与其它窗口通讯。对于消息的理解将是学习编写Windows程序必须逾越的障碍之一。很重要啊!虽然消息这个东东并不是很难理解,但是确实是很陌生的东东。

按一个键盘会产生一个消息,移动鼠标产生一个消息,点击鼠标左键产生消息……总之Windows是靠消息来驱动的。到底什么是消息呢?等您看懂HelloWin程序之后大概也会明白。我只能说,多看几遍吧!!

我们可以用鼠标拖动窗口边框来改变窗口大小。程序会改变窗口中的内容来响应这种变化。重新调整窗口尺寸的工作是Windows处理的。应用程序没有这种负担。应用程序所要做的只是改变窗口的内容来响应这种变化。

应用程序是如何知道用户改变了窗口的尺寸的呢?有很多程序员已经习惯了字符模式程序。在字符模式下操作系统没有将此类消息通知给应用程序的机制。问题的关键在于理解Windows使用的体系结构。当用户改变窗口尺寸时Windows给程序发送一条消息,指出窗口的新尺寸。然后程序调整窗口中的内容,以反映尺寸的变化。

“Windows给程序发送消息”。这对于有过字符模式下编程经验的一些人来说有点不好理解。操作系统怎么会给程序发送消息呢?其实“Windows给程序发送消息”是指Windows调用程序中的一个函数,该函数的参数被设计为接收Windows和用户发出的消息。这种位于应用程序中的,被系统调用的函数被称为“窗口过程”。

我们对于应用程序调用操作系统是很好理解的。比如说在DOS下应用程序经常调用DOS中断。但是对于操作系统调用应用程序中的函数可能很不习惯。而这正是Windows面向对象体系结构的基础。

程序创建的每一个窗口都有一个相关的窗口过程。窗口过程是一个函数。这个函数可以在程序中,也可以在动态链接库中。Windows通过调用窗口过程来处理窗口发送的消息。窗口过程根据此消息进行处理,然后将控制返回给Windows。

窗口通常是在窗口类的基础上创建的。在阅读HelloWin的时候您会了解到什么是窗口类。窗口类中指定了处理该窗口的消息的窗口过程。多个窗口能够基于同一个窗口类,并且使用同一个窗口过程。例如,所有Windows程序中的所有按钮均基于同一个窗口类。我们所见过的所有的Windows按钮都使用一个窗口过程。

在窗口尺寸改变或窗口表面需要重画时由一种消息通知窗口。向窗口发送的消息由该窗口的窗口过程函数处理。

Windows程序开始执行后,Windows为该程序创建一个“消息队例”。这个消息队列用来存放该程序创建的各种不同的窗口消息。程序中有一小段代码,叫做“消息循环”,用来从队列中取出消息,并且将它们发送给相应的窗口过程。有些消息直接发送给窗口过程,不用放入消息队列中。

虽然我叽叽歪歪say了一堆,但是您可能还是不太明白。没关系,接下来我们看第一个Win32程序。这是本章的重点,而且是整个Win32的真正开始。这个代码具有代表性,您最好是手工敲几遍,这绝对有必要!这样脑子会留下一些印象。千万不要偷懒哦……

◎第三十九页:

/*------------------------------------------------------------
   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 ;                  // handle to previous instance 本参数针对16位程序,是前一个实例句柄,如果实例只有一个,则本参数值为空
                                                         //对于win32程序,本lpfnWndProc参数值为空
     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()就是窗口过程。Windows系统会调用它。

WinMain()前面有WINAPI,这个东东是以前从来没见过的。所以特地跑到论坛问了一下。有人说这是定义参数的调用顺序的。到底是什么意思我真的不明白。但是我知道在WINDEF.H中它的定义如下:

#define WINAPI __stdcall

在窗口过程函数前面有一个CALLBACK,他的定义如下:

#define CALLBACK __stdcall

窗口过程函数的类型是LRESULT,它的定义如下:

typedef long LRESULT;

但是请注意!千万不要把

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

写成:

long CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

虽然LRESULT的确是long型,但是在定义窗口过程函数时的格式不能随便改。

WPARAM LPARAM 是 long

#define    PASCAL            pascal
#define    NEAR              near
#define    FAR               far

typedef    unsigned int      UINT;
typedef    unsigned char     BYTE;
typedef    unsigned short    WORD;
typedef    unsigned long     DWORD;
typedef    long              LONG;
typedef    char              *PSTR;
typedef    char NEAR         *NPSTR;
typedef    char FAR          *LPSTR
typedef    void              VOID;
typedef    int               *LPINT;
typedef    LONG              (PASCAL FAR* FARPROC)();

随书光盘中有源代码和HelloWin声音文件。把整个HelloWin复制到您的硬盘中,然后去掉只读属性。然后编译运行一次程序。一定要去掉只读属性。

程序创建一个普通的应用程序窗口。在窗口客户区的中央显示"Hello, Windows98!"。程序的WinMain()部分具有代表性,在以后的很多程序中会重复出现N次,而且几乎是原封不动的。您会发现各个程序间不同的是窗口过程函数部分。

HelloWin程序在窗口客户区中央显示文本串。客户区就是程序自由绘图并且向用户交付可视输出的窗口区域。HelloWin程序中有一大片白色区域,那就是客户区。

和其它的程序一样,HelloWin创建的窗口可以移动,可以改变窗口尺寸。右上角有“最大化”,“最小化”和“关闭”按钮。无论用户如何改变窗口尺寸,程序都会自动将"Hello, Winodws98!"文本串重新定位在客户区域的中央。我将对实现上述功能的代码一一做介绍。

HelloWin调用了18个函数。这18个函数都是Windows API函数。如果您想了解这18个函数的细节,可以查阅Windows API参考手册。我们绝对有必要买一本Windows API的参考资料。如果您有好的API参考手册别忘了告诉我书名。如果您的英语水平很不错,也可以去看MSDN。

程序中有很多大写的标识符。这些标识符都是在Windows的头文件中定义的。

CS_HREDRAW  DT_VCENTER  SND_FILENAME  CS_VREDRAW  IDC_ARROW  WM_CREATE
CW_USEDEFAULT  IDI_APPLICATION  WM_DESTROY  DT_CENTER  MB_ICONERROR  
WM_PAINT  DT_SINGLELINE SND_ASYNC  WS_OVERLAPPEDWINDOW

以上都是简单的数值常量。标识符的前缀表示该常量所属的类别。

CS --- 类风格选项  class style
CW --- 创建窗口选项 creat windows
DT --- 绘制文本选项 draw text
IDI---图标ID号  ID of Icon
IDC---光标ID号  Id of cursor
MB ---消息框选项 messagebox
SND---声音选项 sound
WM ---窗口消息  windows message
WS ---窗口风格  windows style

MSG -- 消息结构
WNDCLASS -- 窗口类结构
PAINTSTRUCT -- 绘图结构
RECT -- 矩形结构

最后还有三个大写标识符,用于不同类型的“句柄”。

HINSTANCE --实例(程序自身)句柄
HWND -- 窗口句柄
HDC -- 设备描述表句柄

句柄在Windows中使用非常频繁。也是非常重要的一个概念。我们还将遇到图标句柄HICON、鼠标指针句柄HCURSOR、图形刷句柄HBRUSH。到底什么是句柄呢?

句柄是一种新的数据类型。菜单,窗口,图标,内存,设备,程序,位图等都被称为“对象”。也就是说菜单是菜单对象,窗口是窗口对象,内存是内存对象。对于这样的称呼您一定要习惯。句柄可以代表一个对象。我们用句柄引用一个对象。例如我们可以用设备描述表句柄引用一个设备(比如显示器)。当我们用一个设备描述表句柄引用显示器时,这个句柄代表的就是显示器。我们通过这个句柄使用显示器。

■常用的句柄类型

HANDLE------------------通用句柄类型
HWND--------------------标识一个窗口对象
HDC---------------------标识一个设备对象
HMENU-------------------标识一个菜单对象
HICON-------------------标识一个图标对象
HCURSOR-----------------标识一个光标对象
HBRUSH------------------标识一个刷子对象
HPEN--------------------标识一个笔对象
HFONT-------------------标识一个字体对象
HINSTANCE---------------标识一个应用程序模块的一个实例
HLOCAL------------------标识一个局部内存对象
HGLOBAL-----------------标识一个全局内存对象


也就是说HWND代表的是一个窗口对象,HDC代表的是一个设备对象。这个设备有可能是内存,也有可能是磁盘。句柄是一个数,通常为32位数,以十六进制形式表示。您明白我在说什么吗?不明白?看完这本书后可能就明白了,请坚持下去吧。

进入程序后第一个碰到的是一组变量定义。

static TCHAR szAppName[] = TEXT ("HelloWin") ;
HWND         hwnd ;                //窗口句柄
MSG          msg ;                 //消息结构
WNDCLASS     wndclass ;            //窗口类结构

第二个变量是一个窗口句柄。它代表一个窗口。那么它代表的是哪个窗口呢?现在还没有赋值啊,所以它哪个窗口都没有代表。

当然,如果您高兴第二个变量也可以定义成:

HWND aaa;  //或者:
HWND bbb;

和int, float等标准数据类型一样,变量名可以随便起。下面的两个也是同样道理:

MSG addf;
WNDCLASS sdakd221;

首先要定义窗口类,也就是给窗口类结构赋值。窗口类只是定义窗口大概的样子。so,所有基于此窗口类创建的窗口对象都会有窗口类中给出的特点。下面我们一个一个地分析窗口类的每一个域。

wndclass.style = CS_HREDRAW | CS_VREDRAW;

上面的语句表示:每当窗口的水平方向尺寸(CS_HREDRAW)或者垂直方向尺寸(CS_VREDRAW)改变后,要完全刷新窗口(刷新就是在屏幕上重画。我们所看到的所有图形界面都是"画"出来的。)。无论如何改变Hellowin的窗口尺寸我们都可以看到文本串“Hello Windows98!”仍然显示在窗口的中央,这两个标识符确保了这一点。

wndclass.lpfnWndProc = WndProc;

这条语句将这个窗口类的窗口过程函数指定为WndProc()。以后凡是基于此窗口类创建的所有新窗口都会把WndProc()当作自己的窗口过程函数。这一条很重要哦,一定要记住!

wndclass.cbClsExtra = 0;  //额外的附加的内存大小 这里为0,一般我们用不上,就为0

基于同一个窗口类创建的窗口对象的公共数据区大小。不太明白这是什么意思,我是菜鸟嘛。看字面上的意思好像是说所有基于此窗口类创建的窗口对象都会拥有一个公共的内存区。我的理解正确吗?有没有高手教我啊?

wndclass.cbWndExtra = 0;

当前窗口对象私有的数据区大小。

wndclass.hInstance = hInstance; //当前进程对象实例句柄  ,这里的hInstance就是winmain中的hInstance,是由操作系统分配给本实例程序的句柄,因为窗口类需要知道本窗口是属于哪个程序,就是本程序了

本程序的句柄。也就是代表HelloWin程序的句柄。如果让我细细讲来,可就难为我了。我还是个菜鸟啊!可能看完了整个书就会懂吧?这个是WinMain()的参数之一。

wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);

为所有基于此窗口类创建的窗口设置一个图标。想要加载自己画的图标时这个参数应该被设置为程序的实例句柄hInstance。如果想要获取预先定义的图标句柄我们可以把LoadIcon()的第一个参数设置为NULL。当第一个参数为NULL时,第二个参数有以下选项:

IDI_APPLICATION 默认的应用程序图标。您可以看看HelloWin的样子,那个方框就是了。
IDI_ASTERISK 星号
IDI_EXCLAMATION 惊叹号
IDI_HAND 手形图标
IDI_QUESTION 问号
IDI_WINLOGO Windows徽标


以上的标识符可以在WINUSER.H中找到。函数返回代表该图标对象的句柄。

wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);

LoadCursor()函数加载一个预先定义的光标,并返回该项光标的句柄。该项句柄被赋给WNDCLASS结构的hCursor域。当鼠标经过"基于此窗口类创建的窗口"时,它变成一个小箭头。不过因为之前的光标也是小箭头,所以我们看不出光标的变化。下面是预定义的鼠标指针标识符:

IDC_APPSTARTING 标准箭头及小沙漏
IDC_ARROW 标准箭头
IDC_CROSS 十字交叉
IDC_HAND (Windows2000)手形
IDC_HELP 箭头和问号
IDC_IBEAM 文本I形
IDC_ICON 空图标
IDC_NO 斜杠圈
IDC_SIZE 四向箭头
IDC_SIZEALL 四向箭头
IDC_SIZENESW 指向东北和西南的双向箭头
IDC_SIZENS 指向南北的双向箭头
IDC_SIZENWSE 指向西北和东南的双向箭头
IDC_SIZEWE 指向东西的双向箭头
IDC_UPARROW 垂直箭头
IDC_WAIT 沙漏


您可以把语句中的IDC_ARROW换成别的,然后再编译运行一次。当您把鼠标移到窗口上面时会看到不同的结果。试一试啊!!你可以先试一试IDC_CROSS。

wndclass.hCursor = LoadCursor(NULL, IDC_CROSS);

然后编译运行一次。把鼠标移到窗口对象上面,看看鼠标有什么变化?鼠标是不是变成十字形了?之前的LoadIcon()也是同样道理,您可以试着换一下第二个参数。看看有什么变化。

wndclass.hbrBackground = GetStockObject(WHITE_BRUSH);

设定基于此窗口类创建的窗口对象的背景颜色。hbr代表“handle to a brush(刷子句柄)”。刷子是图形学上的术语。指用来填充一个区域的着色像素模式。Windows有几个标准刷子,也称为备用(stock)刷子。上面所示的GetStockObject()调用将返回一个白色刷子的句柄。窗口客户区将完全为白色。这是一种及其普遍的做法。

wndclass.lpszMenuName = NULL;

指定窗口类菜单。HelloWin没有菜单,所以这项为NULL。

wndclass.lpszClassName = szAppName;

给这个窗口类取名字。以后就用这个名字认它了。

if (!RegisterClass (&wndclass))//为程序窗口注册窗口类
{
     MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                 szAppName, MB_ICONERROR) ;
     return 0 ;
}

创建一个窗口首先需要注册一个窗口类。RegisterClass()的功能是注册窗口类。但是注册窗口类并不是每次都能成功。所以应该有一个查错机制。

RegisterClass()注册失败后会返回0。所以当窗口类注册失败后if为真,接着执行MessageBox()。执行MessageBox()的结果是显示一个消息框,并显示"This program requires Windows NT!"。然后退出整个程序。这段英文的大概意思是说"本程序需要Windows NT的支持!"。如果您使用的是Windows98,当您运行附书光盘中的HelloWin.exe时可能有机会看到这个消息框。我的是Win2k,所以看不到啊。出现这个消息框表明注册窗口类失败。

窗口类定义了窗口的一般特征,可以使用同一窗口类创建许多不同的窗口。调用CreateWindow()创建窗口时指定有关窗口的更详细的信息。

为什么一个窗口的所有特征不能被一步到位指定呢?实际上以这种方式分开这些风格信息是非常方便的。例如,所有的按钮窗口都可以基于同样的窗口类来创建。与这个窗口类相关的窗口过程位于Windows内部。所有的按钮都是以同样的方式工作的。但是每一个按钮都有不同的尺寸,不同的屏幕位置,以及不同的文本串。这些不同的特征是CreateWindow()定义的一部分,而不是窗口类定义的。

hwnd = CreateWindow (szAppName, // 1指定一个窗口类,基于该窗口类创建窗口
                     TEXT ("The Hello Program"), // 2这个字符串会出现在标题栏中
                     WS_OVERLAPPEDWINDOW,        // 3本窗口风格
                     CW_USEDEFAULT,              // 4窗口的X坐标,更准确地说是窗口左上角的X坐标
                     CW_USEDEFAULT,              // 5窗口的Y坐标
                     CW_USEDEFAULT,              // 6窗口的宽度
                     CW_USEDEFAULT,              // 7窗口的高度
                     NULL,                       // 8窗口对象的父窗口句柄  这里没有
                     NULL,                       // 9窗口对象的菜单句柄或者子窗口编号  这里没有
                     hInstance,                  // 10当前进程的实例句柄,也就是说这个句柄代表的是程序自身。同时它也是WinMain()的参数之一
                     NULL) ;                     // 11窗口对象的参数指针句柄(我也不知道这是什么意思)

我们要关注的是第三个参数WS_OVERLAPPEDWINDOW

WINUSER.H中对WS_OVERLAPPDWINDOW定义如下。

#define WS_OVERLAPPDWINDOW (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)

您想搞清楚这一长串到底是什么东东吗?很…很…很Easy!这是定义该窗口对象的风格。从上面的语句里可以看出第三行的真正的内容是:

WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX

WS_OVERLAPPED   
WS_CAPTION
WS_SYSMENU           系统菜单 最大化最小化等
WS_THICKFRAME
WS_MINIMIZEBOX       最小化按钮
WS_MAXIMIZEBOX       最大化按钮

问题是书上没有说明上面六个WS_XXXX都是干什么用的,所以只能由我们自己想办法。

请将第三个参数WS_OVERLAPPDWINDOW换成:

WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX

接下来要做的就是把六个WS_XXXX一个一个地去掉,每去掉一次就重新编译一次,看看窗口会变成什么样子。首先去掉WS_SYSMENU,也就是说第三个参数变成了:

WS_OVERLAPPED | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX

然后编译一次,您会Lucky地看到您的HelloWin.exe已经是面目全非了,窗口右上角的三个按钮不见了,左上角的小图标也不见了,用鼠标右击标题栏时不会出现系统菜单。由此我们已经知道WS_SYSMENU的含义了。接下来让我们看看WS_MINIMIZEBOX,WS_MAXIMIZEBOX是干什么用的,改成如下所示后,再编译

WS_OVERLAPPED |WS_CAPTION | WS_SYSMENU | WS_THICKFRAME

您会High地看到右上角只有一个按钮了,最大化,最小化两个按钮消失于时间和空间的尽头……以此类推,您可以一个一个的试。

第四,第五个参数分别是窗口对象在屏幕上的x,y坐标,准确地说是窗口对象的左上角的坐标。

第六,第七个参数分别是窗口对象的宽度和高度。

第八个参数父窗口句柄。您不知道父窗口是什么东东?好,请打开记事本程序,按CTRL+O,会发现出现一个窗口,这个窗口总是在记事本上面 ,这个窗口是记事本的子窗口,而记事本就是父窗口。OK?还不知道?………………HelloWin没有父窗口,也就没有子窗口。所以这一项选的是NULL。

HelloWin没有菜单,所以第九项也是NULL。

第十项,书上写的是,hInstance是程序的实例句柄,也就是说这个句柄代表的是程序自身。同时它也是WinMain()的参数之一。

第十一项,窗口对象的参数指针句柄。看到这么长的名字是不是头晕了?我不知道具体含义sorry......

CreateWindow()返回被创建的窗口的句柄,该句柄被存放在变量hwnd中。Windows中的每个窗口都有一个句柄,程序用句柄来引用窗口。许多Windows函数都需要使用hwnd作为参数,这样,Windows才能知道函数是针对哪个窗口的。如果一个程序创建了许多窗口,则每个窗口都有一个句柄。窗口句柄是Windows程序处理的最重要的句柄之一。

就这样,CreateWindows()调用返回后窗口已经创建完毕。Windows已经分配了一块内存,用来保存在创建窗口过程中设置的全部信息。

但是我们仍然无法在屏幕上看到窗口。还需要两个函数显示窗口。一个是:

ShowWindow(hwnd, iCmdShow);

hwnd是刚刚用CreateWindow()创建的窗口对象的句柄。我们要好好看一看第二个参数。这也是WinMain()的第四个参数,int类型。那么,这是干什么用的呢?Come on Baby!

第二个参数有三个常量可供选择。

SW_SHOWNORMAL   SW_SHOWMAXIMIZED    SW_SHOWMINNOACTIVE

这些都是干什么用的???跟上次一样,改代码!请把

ShowWindow(hwnd, iCmdShow);

改成

ShowWindow(hwnd, SW_SHOWMAXIMIZED) 或是 ShowWindow(hwnd, SW_SHOWMINNOACTIVE)

运行一次您就Know了。虽然名字是ShowWindow(),但是我们还是不能Show到HelloWin生成的窗口。因为我们需要重新画屏幕,也就是刷新。

UpdateWindow(hwnd);

该函数的功能是刷新窗口。现在终于可以看见窗口了。

while (GetMessage (&msg, NULL, 0, 0))            //从消息队列中获取消息
{
     TranslateMessage (&msg) ;                   //转换某些键盘消息
     DispatchMessage (&msg) ;                    //从应用程序的消息循环中发送消息至相应的窗口过程。
}

Windows为当前运行的每个程序维护一个消息队列。这一点很重要。当您点击鼠标左右键的时候会发生消息,当您点了某个按钮的时候也会发生消息。总之,不管您干什么,都会产生消息。这些消息会进入消息队列中。GetMessage()负责从消息队列中取出消息。

TranslateMessage()在按键时系统产生虚拟键消息(VK_TAB等等)。在接收虚拟键代码时该函数将相应的WM_CHAR代码发送到应用程序消息队列中。具体的意思嘛我不懂啊。不过这个东东暂时好像不是很重要。
DispatchMessage()从应用程序的消息循环中发送消息至相应的窗口过程。

消息有入队与不入队之分。入队消息按照顺序一个一个送到窗口过程,就像排队买票一样。不入队消息的优先级是比较高的,可以直接发送给窗口过程。就好像领导不用排队买电影票一样。while循环处理的都是入队的消息。

msg变量是类型为MSG的结构,类型MSG在WINUSER.H中定义如下:

typedef struct tagMSG
{
    HWND hwnd; //向窗口过程函数发送该消息的窗口对象的句柄,也就是该消息的来源。
    UINT message; //消息标识符,也就是消息内容。
    WPARAM wParam; //消息的附加信息,取决于message的值。
    LPARAM lParam; //消息的附加信息,取决于message的值。
    DWORD time; //发送消息的时间。
    POINT pt; //发送消息时屏幕上鼠标的位置。
}
MSG, *PMSG;

POINT数据类型也是一个结构,它在WINDEF.H中定义如下:

typedef struct tagPOINT
{
    LONG x;
    LONG y;
}
POINT, *PPOINT;

消息循环以GetMessage()调用开始,它从消息队列中取出一个消息:

GetMessage(&msg, NULL , 0, 0);

在这里我们有必要了解一下GetMessage()。它的原型是:

GetMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax);

GetMessage()检索消息队列,然后把消息放入lpMsg指向的MSG结构中。让我们来LOOK一下函数的四个参数。

lpmsg  --> 指向MSG结构的指针
hWnd  --> 接收消息的窗口句柄。通常设置为NULL,检索属于当前程序的窗口消息以及使用PostThredMessage()调用产生的消息。有关PostThredMessage()以后再说。
wMsgFilterMin  --> 检索的最小消息值。一般情况下设置为0。
wMsgFilterMax  --> 检索的最大消息值。如果wMsgFilterMin和wMsgFilterMax都设置为0,则检索所有消息。一般情况下设置为0。  

返回值 BOOL型。检索到WM_QUIT消息时返回FALSE,否则为TRUE。应该继续消息循环直到GetMessage()返回FALSE以退出while循环,终止程序。

之前我们所讨论的都是准备性工作。注册窗口类,创建窗口,然后在屏幕上显示窗口,程序进入消息循环,从消息队列中取出一条消息,然后由DispatchMessage()将消息发送到相应的窗口过程中,WinMain()所做的只有这些。HelloWin实际的动作都是在它的窗口过程WndProc()中进行的。当用户改变了窗口的大小,或者移动了窗口,所有的这些消息都是由窗口过程来处理的。所以HelloWin的学习重点是它的窗口过程WndProc()。没有搞明白WndProc()等于没有学HelloWin。窗口过程函数确定了在窗口的客户区域中显示的内容,以及窗口怎样响应用户输入。

窗口过程说到底只是一个函数,所以我们可以任意给它取名字。在HelloWin中窗口过程的名字是WndProc()。一个Windows程序可以包含多个窗口过程,但是一个窗口类只能有一个窗口过程。

窗口过程总是定义为如下形式:

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

在定义窗口过程的时候除了函数名可以变之外其它的一定要原封不动。大家可以看到窗口过程的四个参数跟MSG结构的前四个域是一样的。DispatchMessage()从应用程序的消息循环中发送消息至相应的窗口过程。因为窗口过程是用来接收消息的,所以它的四个参数跟MSG结构的前四个域是一样的。

第一个参数是窗口句柄。这个句柄代表的是HelloWin的窗口。因为:

hwnd = CreateWindow( /* 此处省略…… */ );

CreateWindow()的返回值是新建窗口的句柄。上面的语句将新建窗口的句柄赋给了hwnd。所以hwnd代表的是HelloWin窗口。如果没有这个参数,鬼才知道窗口过程要处理的是哪个窗口的消息。前面已经提过,因为多个窗口使用同一个窗口过程,所以用第一个参数说明窗口过程要处理的是哪一个窗口发出的消息。HelloWin只有一个窗口对象。所以第一个参数只能是hwnd。

第二个参数message是消息的内容,比如WM_CREATE。WM_CREATE消息是CreateWindow()发出的。最后两个参数是32位的消息参数,它提供关于消息的更多信息。这些参数包含每个消息类型的详细信息。

一般来说,Windows程序员使用switch语句来处理窗口过程接收到的消息。窗口过程在处理完消息后必须返回0。窗口过程不予处理的其它所有的消息应该被传给DefWindowProc()。从DefWindowProc()返回的值必须由窗口过程返回。这句话可能有点不好理解。可以Look一下HelloWin的最后一个语句:

return DefWindowProc(hwnd, message, wParam, lParam);

现在明白了吗?好好想一想。这个应该很简单。

在HelloWin中WndProc()只选择处理三种消息:WM_CREATE, WM_PAINT, WM_DESTROY。窗口过程的结构如下:

switch(message)
{
case WM_CREATE:
    [处理 WM_CREATE 消息]
    return 0;

case WM_PAINT:
    [处理 WM_PAINT 消息]
    return 0;

case WM_DESTROY:
    [处理 WM_DESTROY 消息]
    return 0;
}

return DefWindowProc(hwnd, message, wParam, lParam);

调用DefWindowProc()来为窗口过程不予处理的所有消息提供默认处理,也就是说除了上述三种消息之外的所有消息都是由DefWindowProc()来处理。这是很重要的。

case WM_CREATE: //播放一个声音文件
    PlaySound(TEXT ("HelloWin.wav"), NULL, SND_FILENAME | SND_ASYNC) ;
    return 0 ;

窗口过程收到的第一个消息----也是WndProc()选择处理的第一个消息是----WM_CREATE。当Windows在WinMain()中处理CreateWindow()时WndProc()接收这个消息。更准确地讲,这个WM_CREATE是由CreateWindow()发出的。

当WM_CREATE产生后Windows调用WndProc(),将WndProc()的第一个参数设置为HelloWin窗口对象的窗口句柄。第二个参数设置为WM_CREATE消息。WndProc()处理完WM_CREATE之后将控制权返回给Windows。然后Windows继续执行WinMain()中CreateWindow()后面的语句。

在HelloWin程序中窗口过程收到WM_CREATE消息后播放HelloWin.wav。使用的是PlaySound()。从函数的名字上我们就大概可以看出它是用来播放声音文件的。

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 ;

WndProc()处理的第二个消息是WM_PAINT消息。这个消息在Windows程序设计中很重要。当窗口客户区域的部分或者全部变为“无效”,以致于必须“刷新”时,将由这个消息通知程序。

这里又出现了新名词---“无效”。客户区域怎么会变得无效呢?比如说,假设HelloWin窗口的坐标原来是(300, 400)。用户使用鼠标拖动了窗口,使窗口的坐标变成了(301,400)。既然窗口的坐标已经发生了变化,那么显示器上也必须重画这个窗口。我已经说过,屏幕上的图形界面都是画出来的,是位图文件(跟BMP一样)。新画出来的窗口坐标是(301,400)。用户又使用鼠标改变了窗口的位置,使得坐标变成了(302,400),这个时候显示器又得重画这个窗口,新画的窗口坐标当然是(302,400)。这一切都是一瞬间的事情。

当原来在显示器上显示的内容已经过时了,需要重画时,这个时候就说窗口的客户区域变得“无效”了。Windows必须重新在显示器上画窗口,以符合新的位置。

在最初创建窗口的时候整个客户区都是无效的。因为程序在显示器上没有画任何东东。当程序运行到UpdateWindow()时这个函数发出第一个WM_PAINT消息。

对WM_PAINT消息的处理几乎总是从一个BeginPaint()调用开始:

hdc = BeginPaint(hwnd, &ps);

以一个EndPaint()调用结束:

EndPaint (hwnd, &ps);

在BeginPaint()调用中如果客户区域背景还没有被删除,Windows会负责删除。它使用注册窗口类的wndclass.hbrBackground域中指定的刷子来删除背景。在HelloWin中这是一个白色的备用刷子。所以Windows将把窗口背景设置为白色。BeginPaint()调用使得整个客户区有效,并返回一个“设备描述表句柄”。设备描述表句柄是指物理输出设备。比如显示器或者是打印机。在窗口的客户区域显示文本和图形需要设备描述表句柄。不能用BeginPaint()返回的设备描述表句柄在客户区之外绘图。想要在客户区外绘图需要其它函数返回的设备描述表句柄。

EndPaint()释放设备描述表句柄,使之该设备描述表句柄不再有效。

在用户改变HelloWin的尺寸后客户区变得无效。读者应该还记得在wndclass.style域设置为标志CS_HREDRAW和CS_VREDRAW,这一风格指示Windows当窗口尺寸发生变化后使得整个窗口无效。然后窗口过程接收到一个WM_PAINT消息。

在移动窗口使几个窗口互相重叠时Windows不保存一个窗口中被另一个窗口所遮盖的部分。当原先被遮盖的部分又露出来以后它就被标志为无效,窗口过程接收到一个WM_PAINT消息,以刷新窗口的内容。

调用完BeginPaint()之后WndProc()接着调用GetClientRect():

GetClientRect(hwnd, &rect); //获取窗口客户区的尺寸

函数的功能是获取窗口客户区的尺寸。第一个参数是程序窗口的句柄,第二个参数是一个指针,指向一个RECT类型的rect结构。该结构有四个LONG域,分别为left、top、right、bottom。这四个域用来保存窗口客户区的尺寸。left和top域通常设置为0,right和bottom域设置为客户区域的宽度和高度(单位是像素点数)。

DrawText (hdc, TEXT ("Hello, Windows 98!"), -1, &rect,
          DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;  //显示文本串

该函数在指定的矩形里写入格式化文本,根据指定的方法对文本格式。

第一个参数是设备描述表句柄。第二个参数就是显示在客户区中央的文本串。第三个参数是  -1,表示在客户区显示的文本串是以'/0'结束的。

第四个参数客户区的尺寸和位置。表示在这个矩形内显示格式化文本。

第五个参数:

DT_SINGLELINE 表示文本串必须显示在同一行。即使是插入了回车和换行符都不能折行。
DT_CENTER 表示文本串在客户区水平居中。
DT_VCENTER 表示文本串在客户区垂直居中。当使用这个标志时必须同时指定DT_SINGLELINE。

一旦客户区域变得无效,WndProc()就接收一个新的WM_PAINT消息。WndProc()通过调用GetClientRect()获取变化后的窗口尺寸,并使用DrawText()在窗口中央显示文本。

WM_DESTROY消息是另一个重要消息。这一个消息指示Windows正在根据用户输入的命令清除窗口。该消息是用户单击"关闭"按钮或者在程序的系统菜单上选择"关闭"时发生的。

HelloWin通过调用PostQuitMessage()以标准方式响应WM_DESTROY消息。

PostQuitMessage(0);

该函数在程序的消息队列中插入一个WM_QUIT消息。前面提到过GetMessage()对于除了WM_QUIT之外的从消息队列中取出的所有消息都返回非0值。而当GetMessage()取到一个WM_QUIT消息时它返回0。这将导致WinMain()退出消息循环,并终止程序。然后程序执行下面的语句:

return msg.wParam;

我不知道这个时候返回msg.wParam的意义,以后会明白的。总之整个程序就这样结束了。终于可放松一下了,接下来的内容是对第三章的总结。

即使有了对HelloWin的说明,读者可能仍然对程序的结构和原理觉得神秘。在为传统环境编写简单的C程序时整个程序可能包含在main()中,而在HelloWin中WinMain()只包含了注册窗口类,创建窗口,从消息队列中取出消息和发送消息所必需的代码。

程序的所有实际动作均在窗口过程中发生。在HelloWin中这些动作不多。WndProc()只是简单地播放了一个声音文件,并在窗口中显示一个文本串。

在后面的章节中读者将发现Windows程序所做的一切都是响应发送给窗口过程的消息。这是概念上的主要难点之一,在开始编写Windows程序之前必须先搞清楚。

程序员已经习惯了使用操作系统调用的思路。例如C程序员使用fopen()打开文件。fopen()最终通过调用操作系统来打开文件,这毫无疑问。

但是Windows不同,尽管Windows有1000多个函数供程序调用,但是Windows也调用用户程序。比如前面定义的窗口过程WndProc()。窗口过程与一个窗口类相关联,窗口类是程序调用RegisterClass()注册的。基于该窗口类创建的所有窗口使用窗口类指定的窗口过程处理所有消息。

在第一次创建窗口时Windows调用WndProc(),窗口被消除时Windows调用WndProc(),窗口改变尺寸、移动或者变成图标时、从菜单选择某一项、滚动滚动条、按下鼠标或者从键盘输入字符时、以及窗口客户区域必须被刷新时Windows都要调用WndProc()。

所有这些WndProc()调用都以消息形式进行。在大多数Windows程序中程序的主要部分都用来处理消息。Windows发送给窗口过程的消息通常都以 WM 打头的名字标识,并且都在WINUSER.H头文件中定义。

Windows程序有一个消息循环。它使用GetMessage从消息队列中取出消息,并且调用DispatchMessage()将消息发送给窗口过程。

Windows程序是按照顺序将消息发送给窗口过程的?还是直接从程序外面接收消息的?实际上这两种情况都存在。消息被分为“进队的”和“不进队的”。进队的消息是Windows放入消息队列中的。在程序的消息队列中排队的消息被GetMessage()依次取出并传给窗口过程。不进队的消息在Windows调用窗口时直接发送给窗口过程。也就是说“进队”的消息被发送给消息队列,“不进队”的消息发送给窗口过程。任何情况下窗口过程都将获得窗口所有的消息--包括进队的和不进队的。窗口过程是窗口的“消息中心”。

进队消息基本上是用户输入的结果,击键(如WM_KEYDOWN和WM_KEYUP)、击键产生的字符(WM_CHAR)、鼠标移动(WM_MOUSEMOVE)和鼠标键(WM_LBUTTONDOWN)的形式给出。进队消息还包含时钟消息(WM_TIMER)、刷新消息(WM_PAINT)、退出消息(WM_QUIT)。

不进队消息通常来自特定的Windows函数。例如当WinMain()调用CreateWindow()时Windows将发送一个WM_CREATE消息。当WinMain()调用ShowWindow()时Windows将给窗口发送WM_SIZE和WM_SHOWWINDOW消息。

这一过程很复杂,非常Lucky的是其中大部分是由Windows解决的,不关程序员的事情。当我看到第四章的内容时才发现第三章简直就是小儿科。第三章的HelloWin只是准备运动。

[发现了一个错误,位置在这里:
if (!RegisterClass (&wndclass))//为程序窗口注册窗口类
{
    MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                szAppName, MB_IConERROR) ;
    return 0 ;
}
   代码本身没有错误,但是你解释得并不完全正确,这段话的意思是在假定RegisterClass()不因其他的原因注册失败的前提下,对你所使用的字符集进行了说明,并不是说这个程序在Win98下就不能运行,主要看你使用的是什么字符集ASCII就没为题,UNICODE就不行了。具体说明如下:
   实际上RegisterClass函数分为:RegisterClassA()和RegisterClassW()两个部分(其实Windows里很多函数、结构都有这样的两个部分,因字符集的不同,选用其中一个),意思就是分别指向ASCII字符集和UNICODE字符集。如果你在编译前,在预处理器定义里定义了UNICODE标识符,那么这个程序所使用的就是UNICODE字符集,这个程序在Win98下就不能正常运行了(第2章理由说明)。而MessageBox()是少数几个在使用了UNICODE字符集后,仍然能在Win98下正常运行的函数。
   对于使用Win98的朋友,不要运行书配光盘上编译好的程序,因为那张光盘上的很多程序都是使用UNICODE字符集的编译的。在Win98环境下要去掉UNICODE标识符,从新编译才能正常运行。]

=======
我能看懂 相信新手们也能看懂
2010-9-4 19:37
0
雪    币: 202
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
这帖子 感觉 好乱哦
2010-9-28 15:24
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
这是Windows SDK 编程 ,不是MFC
2010-10-18 15:08
0
雪    币: 13
活跃值: (40)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
13
[QUOTE=xouou;854930]我补充点
===============
Win32学习笔记 第一章 开始

作者: 姜学哲(netsail0@163.net)

教材: Windows程序设计(第五版)北京大学出版社
[美]Charles Petzold 著
北京博彦科技发展有限公司 译  ¥:160
参考资料...[/QUOTE]

强帖
2010-10-18 15:24
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
下来学习了!感谢楼主
2010-10-29 09:22
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
对啊,我觉得楼主还是弄清楚了比较好。
2010-11-25 13:58
0
雪    币: 400
活跃值: (61)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
虽然不是MFC    但还是应该算是个强帖
2011-2-23 17:40
0
雪    币: 54
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
望二楼的继续更新。很详细。
2011-3-5 10:33
0
游客
登录 | 注册 方可回帖
返回
//