2024年3月2日发(作者:)

目 录

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

11.

12.

13.

14.

15.

16.

17.

18.

19.

20.

C语言Windows程序设计->第一天->第一个Windows程序 ......................................... - 2 -

C语言Windows程序设计->第二天->ASCII与Unicode ................................................ - 6 -

C语言Windows程序设计->第二天->宽字符和C语言 ................................................. - 7 -

C语言Windows程序设计->第三天->Windows版printf .............................................. - 10 -

C语言Windows程序设计->第三天->属于自己的窗口 ................................................ - 12 -

C语言Windows程序设计->第四天->详解我的窗口(上) ............................................. - 16 -

C语言Windows程序设计->第四天->详解我的窗口(中) ............................................. - 20 -

C语言Windows程序设计->第四天->详解我的窗口(下) ............................................. - 24 -

C语言Windows程序设计->第五天->回顾与反思 ........................................................ - 28 -

C语言Windows程序设计->第六天->GDI与设备环境 ................................................ - 29 -

C语言Windows程序设计->第七天->TextOut与系统字体 .......................................... - 32 -

C语言Windows程序设计->第八天->滚动条 ................................................................ - 38 -

C语言Windows程序设计->第九天->GDI绘图基础 .................................................... - 57 -

C语言Windows程序设计->第十天->响应键盘事件 .................................................... - 66 -

C语言Windows程序设计->第十一天->使用鼠标 ........................................................ - 72 -

C语言Windows程序设计->第十二天->使用计时器 .................................................... - 80 -

C语言Windows程序设计->第十三天->按钮类控件 .................................................... - 88 -

C语言Windows程序设计->第十四天->窗口、编辑框样式 ........................................ - 98 -

C语言Windows程序设计->第十五天->文本输入框 .................................................... - 99 -

C语言Windows程序设计--实战:png图片的解析与显示........................................ - 110 -

- 1 -

C语言Windows程序设计->第一天->第一个Windows程序

在《Windows程序设计》(第五版)第一章的起步中, 作者介绍了学习Windows程序设计的一些基本要求:

1. 能够从用户角度熟练的使用Windows;

2. 懂得如何使用C语言;

3. 安装好了Windows的开发环境.

看起来要求并不算高(怎么样?一起来尝试下?)。 笔者在这里决定使用Visual C++ 6.0作为开发环境, 虽说在Visual Studio这个大家族中, VC++6早已被长江后浪推前浪, 把VC++6推成了一个将近淘汰的环境, 但是作者的机器实在是有点不够给力, 启动VS2010时相对比较慢。 总之, 既然Charles Petzold也假定我会用Visual C++ 6.0, 那么我就用VC++6好了。

·介绍Windows

*Windows的历史:

是的, 你不用惊讶, Windows在这里就是指的微软(Microsoft)的那个操作系统,

Windows的历史如果要详细介绍的话, 我觉得可能要单开个随笔分类才行, 所以这里就简略的介绍下, 不过我还是建议你去搜索引擎查找下关于Windows的历史(如果你认为有必要的话)。

1>. 1985年11月, Windows 1.0正式推出;

IBM与Microsoft共同开发, 基于DOS系统,通过DOS来进行文件操作, 当然, 2.0、3.0也都是基于DOS的, 直到Windows 2000的发布,Windows才彻底的摆脱了DOS,成为真正独立的操作系统。

2>. 1987年11月, Windows 2.0推向市场;相对于1.0, 2.0在界面上做了些改动, 采用了重叠窗口。

3>. 1990年 5月, Windows 3.0推向市场;支持Intel 286、386、486微处理器的16位保护模式。

4>. 1993年 7月, Windows NT投放市场;Windows家族中第一个支持Intel 386、486、奔腾微处理器32位模式的版本。

5>. 1995年 8月, Windows 95发布;一个混合的16位/32位Windows系统。

6.> 1998年 6月, Windows 98进入市场;基于Windows 95编写, 对Windows 95的改进。

书的作者就将Windows介绍到这里(没办法, 人家这本书就是1998年写好的), 后来的Windows就更猛了, 横扫桌面, Windows 2000/2003/XP/Vista/2008/, 都懂得。

*Windows的优点:

更加人性化? 操作更简单? 一定还有其他的优点。

*工作原理的中心思想:

"动态链接"概念即为Windows工作原理的中心思想, 通过调用Windows自带的函数来实现在屏幕上显示文本与图形。 函数通过动态链接库实现, .dll以及.exe的文件, 在Windows 98中, 这些文件在WindowsSystem子目录下, NT的在WinNTSystem或WinNTSystem32, NT以上放在WindowsSystem32。

·关于Windows编程

*Windows API:

API, Application Programming Interface, 应用程序编程接口,Windows API 实际上也就是Windows提供的一些函数, 通过对这些函数的调用完成应用程序的开发。

*API文档:

这份文档里介绍了Windows提供的已公开的所有API, 你可以下载份离线的, 或者去MSDN在线图书馆( MSDN Library Online )查阅这份文档.上午的随笔暂时写到这里, 感觉写的有点短, 其实书上写了很多, 仔细品味了好几遍, 越品味越有种找不到重点的感觉,

要是比着书上的句子抄, 这博文岂不是太无味了, 我想, 这些Windows的背景知识对编程影响或许也不是很大, 所以在这里就不啰嗦这些了。下午学习"你的第一个Windows程序"。

- 2 -

*我的第一个Windows程序, Hello,world!

在Charles Petzold的书中, 作者首先回顾了下C语言在控制台下通过标准输入输出函数输出"Hello,world!"的程序, 代码如下:

#include

int main()

{ printf( "Hello,world!n" ) ;

return 0 ;}

同样, Charles Petzold也给出了Windows版的"Hello,world!"(其实他给出的是Hello,windows 98!), 代码如下:

#include

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine,

int iCmdShow )

{

MessageBox( NULL, TEXT("Hello,world!"), TEXT("MessageBox"), 0 );

return 0;

}

通过Visual C++ 6.0的"文件"-->"新建"-->"工程", 选择"Win32 Application"创建一个空的项目, 再在这个项目中新建一个"文件", 文件类型为"C++ Source File", 文件以.c为扩展名, 将上面的代码敲入或者复制粘贴到这个文件内容中, 经过编译运行就可以得到一个对话框了, 赶紧截图留念吧!

在这个对话框中, 有标题栏, 标题栏的内容是"MessageBox", 对话框的内容为"Hello,world!", 还有一个"确定"按钮, 而且, 没有那个黑框框窗口, 一切看起来都是那么美好, 来一起看看这段Windows版的Hello,world!吧!

*Windows版的Hello,world!代码注释*

*第一行

#include

稍微有点C语音基础的都能明白, 这是要包含"windows.h"这个头文件, 也就说明, 在下面的代码中, 要用到这个头文件, 如果我们将#include这句去掉再进行编译看看会有什么情况:

HelloWorld.c

d:projectlwinchelloworldhelloworld.c(3) : error C2061: syntax error :

identifier 'WinMain'd:projectlwinchelloworldhelloworld.c(3) : error C2059:

syntax error : ';'d:projectlwinchelloworldhelloworld.c(3) : error C2146:

- 3 -

syntax error : missing ')' before identifier

'hInstance'd:projectlwinchelloworldhelloworld.c(3) : error C2061: syntax

error : identifier 'hInstance'd:projectlwinchelloworldhelloworld.c(3) : error

C2059: syntax error : ','d:projectlwinchelloworldhelloworld.c(3) : error C2059:

syntax error : ')'执行 时出错.

意料之内的, 报错了, 第一条就是标识符"WinMain"错误, 具体的细节暂时就不深究了,

继续向下看。

*关于windows.h头文件:

在windows.h这个头文件中, 实际上已经包含了若干的其他相关的头文件, 用书上的话说, windows.h是个非常重要的包含文件, 其中包含的其他比较重要的头文件有:

■ WINDEF.H 基本数据类型定义

■ WINNT.H 支持Unicode的类型定义

■ WINBASE.H 内核函数

■ WINUSER.H 用户界面函数

■ WINGDI.H 图像设备接口函数

不过我还是好奇windows.h到底包含了那些头文件, 找到VC6的安装目录, 打开Include文件夹, 找到WINDOWS.H并打开, 虽说看不太懂, 但找#include关键词还是无压力的.

除去上面的5个还有:

■ WINRESRC.H ■ EXCPT.H ■ STDARG.H ■ WINNLS.H ■ WINCON.H

■ WINVER.H ■ WINREG.H ■ WINNETWK.H ■ CDERR.H ■ DDE.H

■ DDEML.H ■ DLGS.H ■ LZEXPAND.H ■ MMSYSTEM.H ■ NB30.H

■ RPC.H ■ SHELLAPI.H■ WINPERF.H ■ WINSOCK2.H ■ MSWSOCK.H

■ WINSOCK.H ■ WINCRYPT.H■ COMMDLG.H ■ WINSPOOL.H ■ OLE.H

■ OLE2.H ■ WINWLM.H ■ WINSVC.H ■ MCX.H ■ IMM.H

*程序的入口

在Win32控制台程序(Win32 Console Application)中, 应用程序的入口为main()函数,

windows程序的程序入口和win32控制台程序的入口类似, 为WinMain()函数.

程序的入口函数在WINBASE.H作出了声明, 声明如下:

int WINAPI WinMain(

HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPSTR lpCmdLine,

int nShowCmd

);

其中由声明可以看出, WinMain函数的返回值被定义为int型;

WINAPI为WinMain函数的调用规则, 在WINDEF.H对"WINAPI"作出了如下宏定义:

#define WINAPI __stdcall

说明, WinMain函数的调用规则为"__stdcall"方式, 对于"__stdcall"调用规则, 现在暂时先不去深究, 知道有这么回事就行, 以后会详细了解到的, 现在如果深究"__stdcall"就偏离了这篇博文的主题。

*WinMain函数的参数:

1>. WinMain的第一个参数 HINSTANCE hInstance, 用书上的解释为"实例句柄", 由于第一次接触C语言Windows程序设计, 对这个句柄的概念也不是很了解, 去百科了下, 句柄的解释为"一个句柄是指使用的一个唯一的整数值,即一个四字节长的数值,来标志应用程序中的不同对象和同类对象中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。"——引用自百度百科->句柄。

笔者是这样对句柄进行理解的, 在一个应用程序中, 通常创建了很多的窗口、按钮、标签,

或者使用了一个文件等, 在程序的任何地方, 只要能够获得这个被称为句柄的东西, 就能够找到该控件或者窗口在内存中的位置, 从而对其进行操作。感觉有点像带参数的main函数, 只是这里的主函数参数为一个句柄。

2>. WinMain函数的第二个参数, 同样是个实例句柄, 但书上又进一步解释说在32位的- 4 -

Windows程序设计中, WinMain函数的实例句柄概念已不再采用, 因此WinMain的第二个参数通常总是NULL。

笔者的见解: 感觉马上就要晕了, 疑问一: "因此WinMain的第二个参数通常总是NULL",

那么第一个呢?WinMain的第一个参数会不会也可以是NULL呢? 疑问二: WinMain函数的参数从何而来?是操作系统么?带着疑问继续向下看。

3>. WinMain的第三个参数是用来运行程序的命令行, PSTR: 用来指向一个字符串的指针类型, szCmdLine, sz:表示以0结尾的字符串; 目的是通过命令行方式运行程序并向主函数中传入参数, 应该就像给main函数传入参数一样;

4>. WinMain的第四个参数是一个int型参数, 用来指明程序(窗口)最初如何被显示,

例如最小化?最大化?全屏?

笔者的见解: 应该很有用, 经常见一些游戏一启动就是全屏的, 但是这个参数也是操作系统传给程序的么?因为从平时运行Windows程序时都是直接双击, 并没有通过命令行给它传入参数, 在编程时应该对程序启动时的显示方式有交代才对, 这样系统再运行时再把这个交代的参数传入给程序告诉程序启动时应该如何显示.(在"笔者的见解"部分的观点均为笔者个人的见解, 如果有误肯定指正, 我会及时更正, 避免误导其他读者。)

*WinMain函数函数体的MessageBox函数:

MessageBox(), 名如其"人", 不用猜也知道这个就是显示一个对话框的函数, 打开API文档,MSDN Library通过索引找到MessageBox函数, 发现其声明如下:

int MessageBox(

HWND hWnd, // handle of owner window, 窗口的一个句柄

LPCTSTR lpText, // address of text in message box, 一个文本(字符串)的指针

LPCTSTR lpCaption, // address of title of message box, 标题字符串的指针

UINT uType // style of message box, 对话框的风格

);

在上面示例中对MessageBox函数的调用如下:

MessageBox( NULL, TEXT("Hello,world!"), TEXT("MessageBox"), 0 );

第一个参数窗口的句柄的实参为NULL, 意思为不属于任何窗口.

第二个参数为对话框的内容。

第三个参数为对话框的标题, 但是这两个参数都使用了一个TEXT()的函数, 书上讲使用TEXT()的目的是将这些字符串打包到TEXT宏代码里面, 笔者尝试了不用这个TEXT()函数而直接像这样:

MessageBox( NULL, "Hello,world!", "MessageBox", 0 );

调用并没有出现警告或者报错信息, 具体使用TEXT()函数的详细原因还不太清楚, 暂时先在这里画个圈。

第四个参数为对话框的风格, 一些以MB_开头的一些常量的组合, 可以使用OR(|)运算进行组合, 这些常量定义在WINUSER.H中, 例如常用的有:

1>.对话框按钮类型:

#define MB_OK 0x00000000L //仅有一个"确定"按钮

#define MB_OKCANCEL 0x00000001L //"确定" + "取消"

#define MB_ABORTRETRYIGNORE 0x00000002L //"终止" + "重试" + "忽略"

#define MB_YESNOCANCEL 0x00000003L //"是" + "否" + "取消"

#define MB_YESNO 0x00000004L //"是" + "否"

#define MB_RETRYCANCEL 0x00000005L //"重试" + "取消

2>.对话框中的图标类型:

#define MB_ICONHAND 0x00000010L //一个红X的错误/停止图标

#define MB_ICONQUESTION 0x00000020L //一个问号的询问图标

#define MB_ICONEXCLAMATION 0x00000030L //一个黄色感叹号的警告图标

#define MB_ICONASTERISK 0x00000040L //一个带有i的信息提示图标

同时, 在这些图标中有的还可以用其他名称代替, 这些别名在WINUSER.H的定义如下:

#define MB_ICONWARNING MB_ICONEXCLAMATION //警告

- 5 -

#define MB_ICONERROR MB_ICONHAND //错误

#define MB_ICONINFORMATION MB_ICONASTERISK //信息

#define MB_ICONSTOP MB_ICONHAND //停止

下午的学习暂时就到这里, 在学习的过程中出现了几个疑问, 在这里对疑问进行下总结:

疑问一: 在书中介绍WinMain函数的参数时讲到 "因此WinMain的第二个参数通常总是NULL", 那么第一个呢?WinMain的第一个参数也可以是NULL吗?

疑问二: WinMain函数的参数从何而来?是操作系统么?

疑问三: 使用TEXT()函数的作用是什么呢?

C语言Windows程序设计->第二天->ASCII与Unicode

一、ASCⅡ

1>. 关于ASCⅡ

ASCII(American Standard Code for Information Interchange,美国信息互换标准代码)

ASCII一共包含128个字符, 包括: 33个控制符号, 1个空格, 32个符号, 10个数字, 26个小写字母和26个大写字母。每个ASCII字符采用7位二进制编码的方式。

ASCII的优点:十分可靠, 普遍扎根在我们的键盘、显示器、系统硬件、打印机、操作系统等, 用途十分广泛。

ASCII的缺点:ASCII, 美国信息互换标准代码, 美国原生, 不能满足其他国家文字的需求, 例如, 中国的汉字?英国的英镑符号(£)?等, 这些在ASCII都是找不到的。

2>. 对ASCII的扩展

由于ASCII不能很好的满足其他国家文字的需求, 所以人们迫切希望能对ASCII进行改进。

①. 国际化标准组织的扩展方案

1967年, 国际化标准组织( ISO, International Organization for Standardization )推荐了ASCII的一个变种, 改动内容包括: 从ASCII中, 拿出 0x40('@'),、0x5B('[')、0x5C('')、0x5D(']')、0x5E('^')、0x60(' ' ')、0x7B('{')、0x7C('¦')、0x7D('}')、0x7E('~')这10个符号保留给各个国家单独使用。这显然不是解决ASCII国际化的好方法,

首先, 其他国家将这些保留字符重新定义为自己国家需要的字符后, 那么国际上的一致性将不能得到保证, 此外, 10个保留字符远远不能满足美国的东方的一些国家使用的象形文字需求, 比如我们中国的汉字。

②. IBM公司的扩展方案

IBM公司采用了使用8位二进制编码方式来表示ASCII, 使用一个字节来储存字符, 这样, 相对于7位的ASCII就可以多出128个额外字符空位来补充ASCII。IBM对ASCII的主要扩展为: 补充了一些重音字符、小写希腊字母、块图字符和线图字符。同时, 还将一些补充的字符分配到ASCII的一些不必要的控制字符上。(注: 在操作系统还是字符模式的年代,

块图字符和线图字符常用来被应用软件装饰自己的程序显示)

③. 微软公司的扩展方案

1985年11月, Windows 1.0发布, 微软采用了自己定义的一套字符集, 这套字符集被称为"ANSI字符集", 是基于ANSI和ISO标准的一个草案。

在MS-DOS 3.3时代(1987年4月), 微软为了使不同国家的计算机都能正常的显示字符,

微软采用了代码页概念, 不同国家的字符被规定在不同的代码页上, 例如代码页第437页为美国英语, 850页为拉丁语-1。用户只要将代码页设置到自己所在的国家就能正常的进行工作, 但是如果用户尝试着将自己的文档拿到与另外一个使用不同代码页的用户的计算机上- 6 -

进行修改时, 自己的文档的某些字符将会显示成其他字符, 这还算好, 有解决方案, 应用软件可以通过将代码页信息储存到文件中, 使用时再进行一些代码页的转换。

但在后来, 随着代码页数量的剧增, Windows版本的不断升级, 代码页的混淆问题开始日益凸显, DS-DOS的代码页和Windows的代码页以及其他Windows版本的系统发生了不兼容, 例如MS-DOS代码页第855页西里尔语在Windows中的1251页西里尔语或者Macintosh的第10007页西里尔语还都不一样。

微软为了解决东方一些国家使用的象形文字问题, 使用了双字节字符集, 这些字符集同样在不同的代码页, 代码页936(简体中文)、949(韩文)、950(繁体中文)以及932(日文)。微软的这个双字节字符集和你象形的可能有所不同, 在这个双字节字符集中, 前128个字符仍然是ASCII(1字节), 较高的128个扩展字符以跟随第二个字节的方式用来表示象形文字(这两个字节被称为前导字节和尾随字节)。

所以在这个代码页中, 有一个字节的字符, 还有2个字节的字符, 这就导致了两个严重的问题:

1>. 在一段字符串中, 字符串的长度不能根据字节的个数确定, 要想确定字符串的长度必须检查每个字节是不是双字节字符的前导字节。

2>. 通过任意指向字符串中的一个指针, 无法知道前一个字符的地址, 通常要回到字符串的开始, 一直解析到指针所在的位置。

二、Unicode

对ASCII扩展的过程中, 没有能够找到一个彻底解决世界上所有书面文字的表示方法,

很显然, 1个字节, 256个字符是无法表示世界上所有的书面文字的, 因此, Unicode诞生了。

Unicode使用16位(2字节)的二进制编码方式来表示字符, 我们知道, 16位最多能够表示65536个字符, 65536个字符对于世界上的所有书面文字以及一些特殊符号来说已经足够用了。在Unicode中, 不同国家使用不同的代码段, 例如, 0x0530 - 0x058F为亚美尼亚语

(Armenian) 、0x0600 - 0x06FF为阿拉伯文 (Arabic) 、0x0E00 - 0x0E7F为泰文 (Thai) 、0x2700 - 0x27BF为印刷符号 (Dingbats) 、0x4E00 - 0x9FBF为中文。

Unicode的优点:

只有一个字符集, 避免了二义性, 能够满足跨语言、跨平台进行文本转换、处理的要求。

Unicode的缺点:

Unicode字符的字符串比ASCII字符串占用的内存大两倍。(笔者认为, 随着计算机性能的不断提高, 内存和外存容量的不断增加, Unicode这一缺点可以慢慢忽略)。

C语言Windows程序设计->第二天->宽字符和C语言

一、回顾C语言中的char数据类型

1>. 在C语言中, 首先我们来声明一个字符型变量:

char c ;

我们也可以在声明时对其进行初始化:

char c = 'A' ;

这时, 字符型变量c就会被值0x41进行初始化, 0x41也就是ASCII码中的'A'字符;

2>. 我们还可以定义一个指向一个字符型数据的指针, 例如:

char *p ;

同样我们再让指针p初始化时指向一个字符串:

char *p = "Hello,world!" ;

- 7 -

3>. 我们再声明一个字符数组:

char a[10] ;

也可以在声明时对其进行初始化:

char a[10] = "Hello" ;

或者:

char a[] = "Hello" ;

通过对C语言的学习我们可以知道, char型变量为1个字节, 因此, 在char c = 'A'中, 变量c的大小即为1字节; 在32位的操作系统中, 一个指针型的变量需要4字节的存储空间,

因此在第二个示例中, char *p = "Hello,world!" ;所需的存储空间为: 4字节指针变量所需的空间 + 字符串"Hello,world!"的12个字节另外再加上一个字节用来表示字符串结束的0。对于一个字符数组char a[10] ;编译器则会自动保留10个字节的储存空间, 对于char a[]

= "Hello" ;这种声明方式, 编译器会根据"Hello"字符串的长度( 5个字符 + 一个结尾0 )来决定初始化时的数组大小。

二、宽字符

通过学习Unicode编码方式,可以知道, 一个Unicode字符占用2个字节的储存空间, 如果我们想用C语言中原有的数据类型表示Unicode的2字节编码类型, char是不行的, char的储存空间为一个字节, 在32位的环境下, int 占4个字节, 而我们仅仅需要的是2字节,

并且是无符号型数据, 因此, 我们可以使用unsigned short int型数据表示一个2字节的字符, unsigned short int为2字节, 正好符合2字节的要求, 当然, 我们也可以将unsigned

short int简写为unsigned short。

在C语言中的宽字符正是基于short型数据的, 这一数据类型在头文件WCHAR.H中的定义为:

typedef unsigned short wchar_t ;

所以C语言中的宽字符wchar_t数据类型与一个无符号短整形unsigned short一样, 都是16位宽。

如果我们用宽字符wchar_t数据类型定义一个变量并且初始化, 如下:

wchar_t c = 'A' ;

那么宽字符wchar_t变量c的值为0x0041, 学过汇编的朋友应该知道, 如果使用16位的CPU储存一个字, 将使用两个存储单元, 在这两个存储单元中, 低位字节放在低地址单元中,

高位字节则放在高地址单元中, 所以, 在这里, 处理器依然将从低位内存单元即低位字节开始处理字符, 'A'在内存中的顺序即为 0x41 -> 0x00。

当我们想使用宽字符表示一个字符串, 我们还要通知编译器这个字符串将使用宽字符存储,

我们用大写的字母'L'(表示长整形)来将这一消息告诉编译器, 例如:

wchar_t *p = L"Hello" ;

那么这个字符串"Hello"将会使用12个字节来储存, 这12个字节存储单元的内容为:"Hello"这5个字符占10个字节, 另外加上表示结束的0占两个字节。

三、有关宽字符的函数

在C语言的学习中, 字符串处理函数我们经常使用, 比如:

unsigned int strlen(char *s); //求字符串的长度

char *strcat( char *dest, char *src ) ; //将src所指字符串连接到dest结尾处

int strcmp( char *s1, char * s2 ) ; //将字符串s1与s2比较

char *strcpy( char *dest, char *src ) ; //将src所指字符串复制到dest结尾处

...

这些函数极大的方便了我们对字符串的处理, 遗憾的是, 在宽字符类型的字符串中, 这些函数将不再适用, 例如我们使用strlen求一个宽字符字符串的长度, 代码如下:

- 8 -

#include

#include

int main()

{

wchar_t *p = L"Hello" ;

printf( "%dn", strlen(p) ) ;

return 0 ;

}

运行后显示的结果为1, 很显然, 它没有求出正确的长度, 这是因为, 宽字符字符串"Hello"在一段内存中存储的值如下:

48 00 65 00 6C 00 6C 00 6F 00 21 00

这是因为当strlen找到该字符串的第一个0时就认为该字符串已经结束了, 所以得到的长度为1, strlen统计到的这一个字符即为0x48表示的'H'。

幸运的是, 虽然这些字符串处理函数不支持对宽字符的处理, 但是我们可以使用为宽字符处理准备的函数, C语言中每个字符串处理函数对应的都有其宽字符版本的字符串处理函数,

这些函数定义在STRING.H头文件和WCHAR.H中, 例如strlen函数响应的宽字符版本为wcslen, wcslen函数在STRING.H的声明如下:

size_t wcslen(const wchar_t *);

size_t为无符号整形unsigned int的别名, STRING.H在头文件的定义如下:

typedef unsigned int size_t;

我们尝试使用宽字符处理函数wcslen()求宽字符字符串的长度:

#include

#include

int main()

{

wchar_t *p = L"Hello" ;

printf( "%dn", wcslen( p ) ) ;

return 0 ;

}

编译运行后的结果显示为5, 是正确的, 同样, 在宽字符中常用的字符串处理函数如下: 四、遗留问题:关于TEXT()

昨天在学习过程中有个遗留问题:

MessageBox( NULL, TEXT("Hello,world!"), TEXT("MessageBox"), 0 );使用TEXT( )的作用是什么呢?

在今天的学习中终于找到了答案。

- 9 -

在定义wchar_t宽字符类型的字符串时, 我们需要在字符串前面加上个大写字母'L'来告诉编译器这是一个wchar_t宽字符型的字符串, 而TEXT()正是为了解决在定义如何不加前面的'L'的。

当我们使用宽字符时, 打开TCHAR.H这个头文件向下查找, 会看到:

#define __T(x) L ## x

这句宏定义, 在ANSI C标准的预处理中, 符号"##"被解释为"令牌粘贴", 作用是使得字母'L'与宏参数连接在一起, 假如参数x为"Hello", 那么经过L ## x处理后"Hello"就变成了L"Hello"。

这看起来和TEXT()没什么关系, 继续向下查找'__T(x)'会看到:

#define _T(x) __T(x)

#define _TEXT(x) __T(x)

这两行宏定义, 到这里仍然没有看到TEXT(), 有了这个思路, 笔者就尝试着找到TEXT()宏,

经过搜索引擎的帮助, 终于找到TEXT宏是定义在WINNT.H头文件中, 找到了相关的定义如下:

#define __TEXT(quote) L##quote // r_winnt

//与

#define TEXT(quote) __TEXT(quote) // r_winnt

当使用wchar_t类型的宽字符时, 使用"令牌粘贴", 这样, 使用TEXT宏时就避免了在字符串前面加上一个大写字母'L'了。

C语言Windows程序设计->第三天->Windows版printf

在Windows中使用printf

一、可变参函数

在Windows程序设计中, 由于Windows不存在标准输入输出的概念, 这就意味着以前我们学习的printf函数将不再适用, 我们知道, C语言标准输入输出函数printf一个常用的功能就是输出格式化后的文字, 例如:

int iAge = 18 ;

printf( "Hello, I am %d years old.n", iAge );

是的, printf函数的使用确实十分方便, 今天我们要做的就是完成一个Windows版的printf。

关于sprintf:

看一个C语言的函数, sprintf这个函数在stdio.h头文件中有声明, sprintf函数的主要功能是把格式化的数据写入某个字符串中, 函数的原型如下:

int sprintf( char *buffer, const char *format, [ argument] … );

sprintf的第一个参数为字符缓冲区, 后面的参数就像printf一样, 是一个格式化字符串,

函数的返回值为缓冲区buffer内的有效字符串长度, 我们尝试使用一下:

#include

int main()

{

char szBuffer[50] ; //定义缓冲区大小为50字节

sprintf( szBuffer, "today: %d-%d-%d", 2012, 10, 8 ) ; //将今天的日期格式化输出到缓冲区

puts( szBuffer ) ; //输出缓冲区szBuffer中的字符

- 10 -

return 0 ;

}

现在我们已经能看到Windows版的printf的影子了, 在Windows版的printf中, 我们可以使用MessageBox函数来替代C语言的标准输入输出函数puts, 但是在这之前我们还有一个重要的问题要解决。

sprintf的安全版

当我们使用sprintf时, 我们需要首先定义一个缓冲区, 这个缓冲区我们必须定义的足够大以至于能容纳我们格式化输出的字符串, 如果超过了这个长度, 程序将会出错, 这样是不安全的, 这里, 使用_snprintf()函数来解决这个问题, _snprintf的原型为:

int _snprintf(char *szBuffer, size_t size, const char *format, ...);

_snprintf相对于sprintf函数多出了个参数, size_t size, 这个参数的功能是:

当格式化后的字符串长度 < size,则将此字符串全部复制到szBuffer中,并给其后添加一个字符串结束符('0');当格式化后的字符串长度 > = size,则仅复制( size - 1 )个字符到缓冲区szBuffer内, 并在其后添加一个字符串结束符('0') 。

我们自己的可变参函数

sprintf有个变形函数, 叫vsprintf, 首先看下这个函数的原型:

int vsprintf(char *szBuffer, const char *format, va_list param);

vsprintf的前两个参数与sprintf相同, 第三个参数是一个va_list宏, va_list宏就是解决C语言变参问题的。

va_list宏的用法:

我们将用到va_list的以下几个成员:

va_list、va_start、va_end;

在些宏在STDARG.H头文件中有定义, 打开STDARG.H可以看到, 其中有句:

typedef char * va_list;

从这句我们可以看出, va_list其实就是char*型类型, 一个字符型指针;

再来看看va_start宏:

va_start(va_list ap, char* format);

format为函数的参数, 可以用来指明参数的类型

可以看出, ap为va_list类型, 也就是可变参数列表; 通过va_start后, 就能使得变参列表与format中的类型一一对应起来;

va_end的作用是销毁释放一个va_list型的参数列表, 例如:

va_list ap

va_end( ap );

下面我们就可以实现我们自己的变参了, 代码如下:

#include

#include

int mySprintf( char *szBuffer, const char *szFormat, ... )

{

int iLength ; //存储字符串的长度信息

va_list pArgs ; //声明一个va_list型变量

va_start( pArgs, szFormat ) ; //让pArgs指向变参

iLength = vsprintf( szBuffer, szFormat, pArgs );//输出到缓冲区szBuffer中

va_end(pArgs); //释放pArgs

- 11 -

return iLength ; //返回字符串长度

}

int main()

{

int i;

char szBuffer[128] ;

i = mySprintf( szBuffer, "today: %d-%d-%d", 2012, 10, 8 ) ;

puts( szBuffer ) ;

return 0 ;

}

运行后的结果和sprintf是相同的, 说明我们已经实现了自己的可变参函数。

二、实现Windows版的printf

在上面的介绍中, 我们所使用的字符串处理函数都是针对于char型的, 下面我们将这些转换为wchar_t型来实现Windwos的格式化消息框。

vsprintf和sprintf一样, 也有其相对应的安全版(最大长度版), 为_vsnprintf, 为了程序的安全性, 这里我们使用vsprintf的安全版:_vsnprintf。

myMessageBox的实现, 代码如下:

#include

#include

int CDECL myMessageBox( TCHAR * szCaption, size_t iStyle, TCHAR * szFormat, ... )

{ //myMessageBox函数参数: 标题, 样式, 格式化输出内容

//CDECL为调用规则, 在WINDEF.H定义为: #define CDECL _cdecl

TCHAR szBuffer [1024] ;

va_list pArgs ;

va_start( pArgs, szFormat ) ; //宏,va_start(va_list ap, char* format)

_vsnprintf( szBuffer, sizeof(szBuffer)/sizeof(TCHAR), szFormat, pArgs ) ; //sizeof(szBuffer)/sizeof(TCHAR)得到最大能容下的字符个数

va_end( pArgs ) ;

return MessageBox( NULL, szBuffer, szCaption, iStyle ) ;

}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

int cxScreen, cyScreen ;

cxScreen = GetSystemMetrics (SM_CXSCREEN) ; //获取显示器x方向像素

cyScreen = GetSystemMetrics (SM_CYSCREEN) ; //获取显示器y方向像素

myMessageBox( TEXT ("显示器分辨率"), MB_OKCANCEL, TEXT ("显示器当前分辨率为:%dx%d。"), TEXT(cxScreen), TEXT(cyScreen) ) ;

return 0 ;

}

C语言Windows程序设计->第三天->属于自己的窗口

·创建窗口前的准备

- 12 -

在创建窗口前我们先来熟悉几个名词, 这些名词现在可以暂时不去透彻的进行理解, 只需要印象中知道有这么回事就行。

1>. 窗口

"窗口"我们都已经十分熟悉了,在Windows中,一个应用程序窗口上一般会在标题栏上显示程序的名称,紧挨着标题栏的菜单栏,或许还会有状态栏、滚动条等其他"装饰品"。

2>. 控件

在一些应用程序中,我们经常可以在程序的界面(窗口)上看到一些按钮(Push Button)、文本框(Text Box)、列表框(List Box)、滚动条(Scroll Bar)等,这些对象通常被称为控件,

在《Windows程序设计》一书中,还被称为"子窗口"、"控件窗口"或"子窗口控件"。

3>. 窗口类

在建立一个窗口前, 我们必须首先注册一个"窗口类"(Windows Class),接触过面向对象的朋友应该会首先想到面向对象当中的"类",但是,这里"窗口类"中的"类"并不是指面向对象当中的那个"类"。

在这里我们可以把"窗口类"理解为一个结构体,结构体的成员就是窗口的一些属性,例如窗口的标题是什么、窗口使用什么样的小图标以及窗口的风格之类的属性,一个窗口就是一个结构体的对象,结构体成员的属性决定着窗口的属性。

4>. 消息循环

在Windows程序设计中, 消息循环是个不得不提的概念, Windows操作系统是以消息驱动的, 消息队列是指在一个应用程序运行时, Windows操作系统会为该应用程序建立一个"消息队列", 这个消息队列用来存放该程序可能创建的各种窗口的消息, 当用户对应用程序进行操作时, 例如点击一个按钮、调整下窗口的大小等, 此时Windows会立即把这一消息告诉应用程序, 使应用程序能作出相应的动作。

笔者觉得有比要强调一下,上一段中的最后一句:"此时Windows会立即把这一消息告诉应用程序, 使应用程序能作出相应的动作。",我们在创建一个应用程序时,不用想着什么时候才能从用户那得到指令, 因为操作系统会即时告诉我们用户此时是否对程序进行了操作,对于没有接触过Windows编程的朋友们这点可能有点难以理解,简单来说就是像如何获取一个按钮是否被单击,或者如何获取用户此时是否在调整窗口大小之类的代码Windows已经帮我们完成了, 我们只需要等待着Windows给我们发消息就行,如何判断消息类型以及处理这些消息,在代码上通常我们用while配合一个巨大的switch来完成。

5>. 窗口的过程函数

当一个窗口建立之后, 就可以从Windows那里不断的接收到Windows发来的消息, 接收到消息后我们就需要一个函数来处理这些消息, 可以简单的理解为, 这个用来处理接收到的消息的函数就称为窗口过程函数或者回调函数。

下面我们来尝试着看一个创建一个基本窗口的代码。

·尝试创建自己的窗口

代码如下:

#include

LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM );//声明用来处理消息的函数

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow )

{

static TCHAR szAppName[] = TEXT("MyWindow") ;

HWND hwnd ;

MSG msg ;

WNDCLASS wndclass ; //声明一个窗口类对象

//以下为窗口类对象wndclass的属性

= CS_HREDRAW | CS_VREDRAW; //窗口样式

- 13 -

assName = szAppName ; //窗口类名

nuName = NULL ; //窗口菜单:无

kground = (HBRUSH) GetStockObject(1) ; //窗口背景颜色

dProc = WndProc ; //窗口处理函数

xtra = 0 ; //窗口实例扩展:无

xtra = 0 ; //窗口类扩展:无

nce = hInstance ; //窗口实例句柄

= LoadIcon( NULL, IDI_APPLICATION ); //窗口最小化图标:使用缺省图标

r = LoadCursor( NULL, IDC_ARROW ) ; //窗口采用箭头光标

if( !RegisterClass( &wndclass ) )

{ //注册窗口类, 如果注册失败弹出错误提示

MessageBox(NULL,TEXT("窗口注册失败!"),TEXT("错误"),MB_OK|MB_ICONERROR);

return 0 ;

}

hwnd = CreateWindow( //创建窗口

szAppName, //窗口类名

TEXT("我的窗口"), //窗口标题

WS_OVERLAPPEDWINDOW, //窗口的风格

CW_USEDEFAULT, //窗口初始显示位置x:使用缺省值

CW_USEDEFAULT, //窗口初始显示位置y:使用缺省值

CW_USEDEFAULT, //窗口的宽度:使用缺省值

CW_USEDEFAULT, //窗口的高度:使用缺省值

NULL, //父窗口:无

NULL, //子菜单:无

hInstance, //该窗口应用程序的实例句柄

NULL ) ; //

ShowWindow( hwnd, iCmdShow ) ; //显示窗口

UpdateWindow( hwnd ) ; //更新窗口

while( GetMessage(&msg,NULL,0,0 ) ) //从消息队列中获取消息

{

TranslateMessage( &msg ) ; //将虚拟键消息转换为字符消息

DispatchMessage( &msg ) ; //分发到回调函数(过程函数)

}

return ;

}

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

{

HDC hdc ; //设备环境句柄

PAINTSTRUCT ps ; //绘制结构

RECT rect; //矩形结构

switch( message ) //处理得到的消息

{

case WM_CREATE: //窗口创建完成时发来的消息

MessageBox( hwnd, TEXT("窗口已创建完成!"), TEXT("我的窗口"), MB_OK |

MB_ICONINFORMATION ) ;

return 0;

case WM_PAINT: //处理窗口区域无效时发来的消息

hdc = BeginPaint( hwnd, &ps ) ;

GetClientRect( hwnd, &rect ) ;

DrawText( hdc, TEXT( "Hello, 这是我自己的窗口!" ), -1, &rect,

DT_SINGLELINE | DT_CENTER | DT_VCENTER ) ;

EndPaint( hwnd, &ps ) ;

return 0 ;

case WM_LBUTTONDOWN: //处理鼠标左键被按下的消息

MessageBox( hwnd, TEXT("鼠标左键被按下。"), TEXT("单击"), MB_OK |

MB_ICONINFORMATION ) ;

- 14 -

return 0;

case WM_DESTROY: //处理窗口关闭时的消息

MessageBox( hwnd, TEXT("关闭程序!"), TEXT("结束"), MB_OK |

MB_ICONINFORMATION ) ;

PostQuitMessage( 0 ) ;

return 0;

}

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

//DefWindowProc处理我们自定义的消息处理函数没有处理到的消息

}

先简单介绍这段代码, 在主函数WinMain中的窗口类对象wndclass我们定义了窗口的相关属性, 尝试注册窗口类并调用CreateWindow创建窗口, 创建完成后用ShowWindow让窗口显示出来, 我们还使用了个while用来从消息队列里获取并分发消息给程序, 我们还定义了一个过程函数WndProc用来处理系统发来的消息。

编译运行后, 如果没有错误将首先看到一个窗口建立成功与否的对话框, 当创建窗口失败时弹出一个错误对话框并关闭程序。

当窗口被创建时首先Windows会发给我们一条创建完成的消息"WM_CREATE", 在我们的消息处理函数WinProc中有switch-case语句对该消息进行了处理, 就是弹出一个窗口创建完成的对话框。

同样, 我们还处理了当窗口的客户区(内容部分)被改变时发来的的"WM_PAINT"消息进行了处理, 让"Hello, 这是我创建的窗口!"始终显示在窗口中心。

"WM_LBUTTONDOWN"消息是当用户在客户区按下鼠标左键时发来的消息, 我们作出相应的动作为弹出一个鼠标左键被按下的对话框。

- 15 -

当用户点击窗口的关闭按钮时, "WM_DESTROY"消息就会发来, 我们作出的动作是弹出"关闭程序!"的对话框并且退出程序。

今天的学习先到这里, 明天我们详细学习下整个创建窗口的代码。

C语言Windows程序设计->第四天->详解我的窗口(上)

对窗口创建的详细解释

在昨天的学习中, 我们已经初步完成了一个窗口的创建, 虽然我们对于代码中的许多语句可能还不理解, 但这暂时并不会影响到我们的学习。再次来回顾昨天那个窗口的代码部分(如上)。

·调用了哪些Windows API

通过这段代码, 我们完成了在客户区(窗口的白色区域)中心显示一段字符串, 此外,

我们还处理了系统发来的部分消息, 在这个过程中我们使用了Windows的诸多函数, 这些函数分别为:

函数名

GetStockObject

LoadIcon

LoadCursor

RegisterClass

MessageBox

函数描述

获取一个图形对象

为程序加载图标

为程序加载光标

为程序窗口注册一个窗口类

显示消息对话框

- 16 -

CreateWindow

ShowWindow

UpdateWindow

GetMessage

创建一个窗口

在屏幕上将窗口显示出来

重绘窗口客户区

从消息队列获取消息

TranslateMessage 将虚拟键消息转换为字符消息

DispatchMessage

BeginPaint

GetClientRect

DrawText

EndPaint

PostQuitMessage

DefWindowProc

将消息发送给消息处理函数

准备对窗口进行绘图

获取窗口客户区尺寸

绘制一个文本字符串

结束对窗口的绘图

向消息队列插入"退出"消息

执行系统默认的消息处理

对于这些函数的详细说明, 可以到百科查询, 或者到MSDN Library文档中查询(推荐)。

·大写标识符说明

在上面的程序中出现了许多类似于XX_XXXX的大写标识符, 如:

CS_HREDRAW

MB_ICONERROR

DT_VCENTER

CS_VREDRAW

WS_OVERLAPPEDWINDOW

WM_CREATE

IDI_APPLICATION IDC_ARROW

CW_USEDEFAULT

WM_PAINT

DT_SINGLELINE

WM_LBUTTONDOWN

MB_OK

DT_CENTER

WM_DESTROY

这些标识符均为数值常量, 在WINUSER.H头文件中有定义, 该程序中用到的一些标识符前缀含义为:

前缀

CS_

CW_

含义

类风格选项

创建窗口选项

前缀

IDC_

MB_

含义

光标的ID号

消息框选项

- 17 -

DT_

IDI_

文本绘制选项

图标的ID号

WM_

WS_

窗口消息

窗口风格

对于这些大写标识符暂时没必要强记下来, 只需要对其有个大致的印象即可, 不会影响到我们的学习。

·"新"数据类型

在调用的函数中, 一些函数的参数类型是我们以往在C控制台编程中所没有见到过的,

例如在代码的第三行对回调函数的声明中:

LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ; //声明用来处理消息的函数

其中的"LRESULT"、"UINT"、"WPARAM"、"LPWARAM", 以及主函数WinMain中的"PSTR"之类的类型都是我们所没有见过的, 这些"新"数据类型都是什么呢?

其实这些"新"数据类型是为了程序书写时的简便而使用typedef或#define对C已有的数据类型重新定义的, 在WINDEF.H/WINNT.H头文件中有定义, 如:

typedef unsigned int UINT;

typedef char CHAR;

typedef CHAR *LPSTR, *PSTR;

typedef UINT WPARAM;

typedef long LONG;

typedef LONG LPARAM;

typedef LONG LRESULT;

可以看出, UINT实际上就是unsigned int型; PSTR就是一个char *型, WPARAM被定义为UINT型, 也就是unsigned int型; LPARAM和LRESULT被定义为long型;由此观之, "新"数据类型, 并不新。

在声明消息处理函数WndProc时, 在函数名前面我们还是使用了一个"CALLBACK"标识符,

这个标识符其实就是函数的调用规则, 在WINDEF.H中有定义:

#define CALLBACK __stdcall

在前些天的学习中, 我们还知道在WinMain主函数的前面有个WINAPI调用规则, 在WINDEF.H中也被定义为 __stdcall。

·结构类型

结构体, 在C语言的学习中我们已经对这种数据类型十分熟悉了, 在上述创建窗口的过程中我们使用了四种结构类型来创建结构体的对象, 这些结构体在WINUSER.H/WINDEF.H中有定义, 现在我们只需对其进行一下印象中的认识, 对于结构体的成员暂时不去讨论, 这四种结构体分别为:

1>. MSG消息结构

MSG在WINUSER.H中的定义:

typedef struct tagMSG {

HWND hwnd;

UINT message;

WPARAM wParam;

LPARAM lParam;

DWORD time;

POINT pt;

#ifdef _MAC

DWORD lPrivate;

- 18 -

#endif} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

2>. WNDCLASS窗口类结构

WNDCLASS在WINUSER.H中的定义:

typedef struct tagWNDCLASSW {

UINT style;

WNDPROC lpfnWndProc;

int cbClsExtra;

int cbWndExtra;

HINSTANCE hInstance;

HICON hIcon;

HCURSOR hCursor;

HBRUSH hbrBackground;

LPCWSTR lpszMenuName;

LPCWSTR lpszClassName;

} WNDCLASSW, *PWNDCLASSW, NEAR *NPWNDCLASSW, FAR *LPWNDCLASSW;

#ifdef UNICODE

typedef WNDCLASSW WNDCLASS;

3>. PAINTSTRUCT绘制结构

PAINTSTRUCT在WINUSER.H中的定义:

typedef struct tagPAINTSTRUCT {

HDC hdc;

BOOL fErase;

RECT rcPaint;

BOOL fRestore;

BOOL fIncUpdate;

BYTE rgbReserved[32];

} PAINTSTRUCT, *PPAINTSTRUCT, *NPPAINTSTRUCT, *LPPAINTSTRUCT;

4>. RECT矩形结构

RECT在WINDEF.H中的定义:

typedef struct tagRECT

{

LONG left;

LONG top;

LONG right;

LONG bottom;

} RECT, *PRECT, NEAR *NPRECT, FAR *LPRECT;

·再谈句柄

不同类型的句柄有不同的标识符, 在我们尝试创建窗口的代码中用到的句柄有:

标识符

HINSTANCE

HWND

含义

实例句柄, 指程序本身 HDC

窗口句柄 HBRUSH

标识符 含义

设备环境句柄

图形画刷句柄

句柄是一个标识符,用来来标识对象, 一个句柄使用四个字节长的整数来存储一个整数值, 这个具体的整数值实际上我们并不需要知道是多少, 我们要做的就是传递句柄,

Windows会知道如果用过这个句柄找到并以引用相应的对象。

- 19 -

·匈牙利命名法

匈牙利命名法是一种编程时的命名规范, 在一开始我们接触的MessageBox对话框时我们就已经见到了匈牙利命名的变量:

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine,

int iCmdShow )

这里的 hInstance、hPrevInstance、szCmdLine、iCmdShow的命名都遵循匈牙利命名法, 匈牙利命名法的目的和作用是使变量名非常清晰易懂,增强了代码的可读性,方便各程序员之间相互交流代码, 减少代码中因数据类型不匹配的错误。

例如:

szCmdLine, 通过变量名我们就可以知道这是一个"以0结束的字符串";

hInstance, 以h开头, 代表一个句柄;

iCmdShow, 以i开头, 代表一个int整形。

这样, 就可以不用再去找到变量声明的位置去看它的变量类型, 避免了在使用变量时由于数据类型不匹配引起的错误。

经常使用的匈牙利命名的前缀如下:

前缀

p

fn

v

h

l

b

f

dw

sz

n

d

c

ch

i

by

含义描述

指针

函数

无效

句柄

长整形

布尔型

浮点型

双字

字符串

短整型

双精度浮点型

计数, 通常写为cnt

整型

字节

前缀

g_

c_

m_

s_

Max

Min

Init

T 或 Temp

Src

Dest

w

含义描述

全局变量

常量

成员变量

静态变量

最大

最小

初始化

临时变量

源对象

目标对象

字型

实型

无符号型

字符, 通常写为c r

u

C语言Windows程序设计->第四天->详解我的窗口(中)

对窗口创建的详细解释

窗口的注册

一个窗口的风格, 总是要根据窗口类来决定的, 同时, 窗口类决定了处理窗口消息所使用的函数(窗口过程)。 当定义好窗口类对象并完成对象属性后, 如果想要建立这个窗口必须

- 20 -

调用函数RegisterClass来注册窗口类, RegisterClass的函数原型如下:

ATOM RegisterClass(CONST WNDCLASS *lpWndClass);

该函数只有一个参数, 一个指向一个WNDCLASS结构的指针, 现在, 我们再来看一下WNDCLASS窗口类的结构, 下面是等效定义, 和WINUSER.H中的WNDCLASS效果相同, 但WINUSER.H中是通过定义tagWNDCLASSA(ASCII版本)和tagWNDCLASSW(Unicode版本), 然后进行 :

#ifdef UNICODE

typedef WNDCLASSW WNDCLASS;

typedef PWNDCLASSW PWNDCLASS;

typedef NPWNDCLASSW NPWNDCLASS;

typedef LPWNDCLASSW LPWNDCLASS;

#elsetypedef WNDCLASSA WNDCLASS;

typedef PWNDCLASSA PWNDCLASS;

typedef NPWNDCLASSA NPWNDCLASS;

typedef LPWNDCLASSA LPWNDCLASS;

#endif // UNICODE

来实现 WNDCLASS的定义, 在效果上和以下定义是相同的:

typedef struct tagWNDCLASS {

UINT style; //窗口样式

WNDPROC lpfnWndProc; //回调函数

int cbClsExtra; //窗口类扩展

int cbWndExtra; //窗口实例扩展

HINSTANCE hInstance; //窗口实例句柄

HICON hIcon; //窗口最小化图标

HCURSOR hCursor; //窗口采用鼠标指针样式

HBRUSH hbrBackground; //窗口背景颜色

LPCWSTR lpszMenuName; //窗口菜单

LPCWSTR lpszClassName; //窗口类名

} WNDCLASS, *PWNDCLASS;

在WinMain主函数中, 我们使用WNDCLASS创建了一个窗口类对象:wndclass, 并且对wndclass的属性做了如下设置:

= CS_HREDRAW | CS_VREDRAW ; //窗口样式

assName = szAppName ; //窗口类名

nuName = NULL ; //窗口菜单:无

kground = (HBRUSH) GetStockObject(1) ; //窗口背景颜色

dProc = WndProc ; //窗口消息处理函数

xtra = 0 ; //窗口实例扩展:无

xtra = 0 ; //窗口类扩展:无

nce = hInstance ; //窗口实例句柄

= LoadIcon(NULL,IDI_APPLICATION); //窗口最小化图标:使用缺省图标

r = LoadCursor( NULL, IDC_ARROW ) ; //窗口采用箭头光标

下面我们来详细看下WNDCLASS的这些成员:

1>. = CS_HREDRAW | CS_VREDRAW ;

style属性决定着窗口的风格, 可以通过C语言的或运算组合出不同风格类型的窗口, 在WINUSER.H头文件中定义着这些标识符以及其值, 所有的前缀为CS_的标识符如下:

/* Class styles */

#define CS_VREDRAW 0x0001

#define CS_HREDRAW 0x0002

#define CS_DBLCLKS 0x0008

#define CS_OWNDC 0x0020

#define CS_CLASSDC 0x0040

- 21 -

#define CS_PARENTDC 0x0080

#define CS_NOCLOSE 0x0200

#define CS_SAVEBITS 0x0800

#define CS_BYTEALIGNCLIENT 0x1000

#define CS_BYTEALIGNWINDOW 0x2000

#define CS_GLOBALCLASS 0x4000

#define CS_IME 0x00010000

2>. assName = szAppName ;

赋予窗口一个名称, 可以使用ASCII版本的字符串也可以使用Unicode版本的字符串, 需要注意的是, 这里的窗口名称并不是指窗口标题。

3>. nuName = NULL ;

指定窗口类的菜单, 由于我们这个窗口没有使用菜单, 所以为NULL, 具体的用法以后肯定会学习到;

4>. kground = (HBRUSH) GetStockObject(WHITE_BRUSH) ;

窗口的背景颜色, 这里使用的是以白色作为填充, 填充白色这是常见的做法, 如果你愿意,

也可以填充为其他的一些样式, 这里是一些样式的标识符:

#define WHITE_BRUSH 0

#define LTGRAY_BRUSH 1

#define GRAY_BRUSH 2

#define DKGRAY_BRUSH 3

#define BLACK_BRUSH 4

#define NULL_BRUSH 5

#define HOLLOW_BRUSH NULL_BRUSH

#define WHITE_PEN 6

#define BLACK_PEN 7

#define NULL_PEN 8

#define OEM_FIXED_FONT 10

#define ANSI_FIXED_FONT 11

#define ANSI_VAR_FONT 12

#define SYSTEM_FONT 13

#define DEVICE_DEFAULT_FONT 14

#define DEFAULT_PALETTE 15

#define SYSTEM_FIXED_FONT 16

5>. dProc = WndProc ;

将窗口的消息处理函数设置为我们自定义的WndProc函数。

6>. xtra = 0 ;

7>. xtra = 0 ;

这两个属性用来维护结构中预留的一些额外空间, 程序可以根据需要要使用这些额外空间,

通过匈牙利命名的cbClsExtra、cbWndExtra中的cb可以知道, 这些成员表示一个"字节数", 由于我们这个窗口没有使用到额外空间, 所以将这个两个属性赋值为0;

8>. nce = hInstance ;

表示应用程序的实例句柄, hInstance的来源是WinMain的一个参数;

9>. = LoadIcon( NULL, IDI_APPLICATION ) ;

加载一个位图作为程序标题栏最左侧的小图标, 我们这里加载了一个系统的默认图标, 后面的学习这将会学习到如何从磁盘加载我们自定义的图标.

10>. r = LoadCursor( NULL, IDC_ARROW ) ;

与LoadIcon类似, 加载鼠标的指针位图

完成这个窗口类成员属性的初始化后, 我们就可以通过RegisterClass来注册窗口了, 如果注册成功, 将返回一个具有唯一标识已注册的类的一个原子, 如果函数失败,返回0。

·窗口的创建

成功注册窗口类后, 我们就可以呼叫CreateWindow函数对窗口进行创建了, CreateWindow函数的原型如下:

HWND CreateWindow(

- 22 -

);

LPCTSTR lpClassName, //窗口类名称

LPCTSTR lpWindowName, //窗口标题

DWORD dwStyle, //窗口样式

int x, //窗口初始x坐标

int y, //窗口初始y坐标

int nWidth, //窗口初始x方向尺寸

int nHeight, //窗口初始y方向尺寸

HWND hWndParent, //父窗口句柄

HMENU hMenu, //窗口菜单句柄

HANDLE hlnstance, //程序实例句柄

LPVOID lpParam //创建参数

函数返回的是创建成功后的窗口的句柄。 需要说明的几点:

1>.参数一: LPCTSTR lpClassName

参数LPCTSTR lpClassName,为窗口的名称, 我们需要传入的参数就是刚才我们在窗口类注册的窗口类名称, 这样使用CreateWindow建立的窗口就能与注册的窗口进行关联。

2>. 参数三: DWORD dwStyle

参数三为窗口的样式, 示例中创建的是一个普通的层叠窗口样式,

WS_OVERLAPPEDWINDOW, 打开WINUSER.H头文件, 对于WS_OVERLAPPEDWINDOW是这样定义的:

#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED |

WS_CAPTION |

WS_SYSMENU |

WS_THICKFRAME |

WS_MINIMIZEBOX |

WS_MAXIMIZEBOX)

可以看出, WS_OVERLAPPEDWINDOW样式实际上是通过一些标识符通过或的组合, 此外我们还可以通过组合设计我们自己的样式, 这些标识符在WINUSER.H的定义如下:

#define WS_OVERLAPPED 0x00000000L //产生一个层叠的窗口。一个层叠的窗口有一个标题条和一个边框。与WS_TILED风格相同。

#define WS_POPUP 0x80000000L //创建一个弹出式窗口。该风格不能与WS_CHLD风格同时使用。

#define WS_CHILD 0x40000000L //创建一个子窗口。这个风格不能与WS_POPUP风格合用。

#define WS_MINIMIZE 0x20000000L //创建初始状态为最小化的窗口。仅与WS_OVERLAPPED风格一起使用。

#define WS_VISIBLE 0x10000000L //创建一个最初可见的窗口。

#define WS_DISABLED 0x08000000L //创建一个初始状态为禁止的窗口。

#define WS_CLIPSIBLINGS 0x04000000L //排除子窗口之间的相对区域。

#define WS_CLIPCHILDREN 0x02000000L //创建一个初始状态为禁止的子窗口。一个禁止状态的窗口不能接受来自用户的输入信息。

#define WS_MAXIMIZE 0x01000000L //创建一个初始状态为最大化状态的窗口。

#define WS_CAPTION 0x00C00000L //创建有标题框的窗口(包括WS_BODER风格)。

#define WS_BORDER 0x00800000L //创建一个单边框的窗口。

#define WS_DLGFRAME 0x00400000L //创建带对话框边框风格的窗口,这种风格的窗口不能带标题条。

#define WS_VSCROLL 0x00200000L //创建一个有垂直滚动条的窗口。

#define WS_HSCROLL 0x00100000L //创建一个有水平滚动条的窗口。

#define WS_SYSMENU 0x00080000L //创建一个在标题条上带有窗口菜单的窗口,必须同时设定WS_CAPTION风格。

#define WS_THICKFRAME 0x00040000L //创建一个具有可调边框的窗口,与WS_SIZEBOX风格相同。

#define WS_GROUP 0x00020000L //指定一组控制的第一个控制。

- 23 -

#define WS_TABSTOP 0x00010000L //创建一个控制,这个控制在用户按下Tab键时可以获得键盘焦点。按下Tab键后使键盘焦点转移到下一具有WS_TABSTOP风格的控制。

#define WS_MINIMIZEBOX 0x00020000L //创建一个具有最小化按钮的窗口。

#define WS_MAXIMIZEBOX 0x00010000L //创建一个具有最大化按钮的窗口。

#define WS_TILED WS_OVERLAPPED //产生一个层叠的窗口。一个层叠的窗口有一个标题和一个边框。与WS_OVERLAPPED风格相同。

#define WS_ICONIC WS_MINIMIZE //创建一个初始状态为最小化的窗口。仅与WS_OVERLAPPED风格一起使用。

#define WS_SIZEBOX WS_THICKFRAME //创建具有厚边框的窗口,可以通过厚边框来改变窗口大小。

#define WS_TILEDWINDOW WS_OVERLAPPEDWINDOW //创建一个具有WS_OVERLAPPED,WS_CAPTION,WS_SYSMENU,WS_THICKFRAME,WS_MINIMIZEBOX和WS_MAXIMIZEBOX风格的重叠式窗口。

关于这些样式的详细说明可以查阅MSDN Library(推荐)或到百科查询。

·窗口的显示

使用CreateWindow函数成功创建好窗口后, 这个窗口就被在保存在一段内存中了, 但是此时窗口还不能显示在屏幕上, 我们需要使用ShowWindow函数来将建立好的窗口显示出来, ShowWindow函数的原型如下:

BOOL ShowWindow( HWND hWnd, int iCmdShow );

参数一为刚才通过CreateWindow函数窗口出的窗口句柄, 参数二为窗口的显示方式, 由WinMain函数的int iCmdShow接收该值, 常用的显示方式有:

SW_HIDE //隐藏窗口并激活其他窗口;

SW_MAXIMIZE //最大化;

SW_MINIMIZE //最小化指定的窗口并且激活在Z序中的下一个顶层窗口;

SW_RESTORE //激活并显示窗口。如果窗口最小化或最大化,则系统将窗口恢复到原来的尺寸和位置;

SW_SHOW //在窗口原来的位置以原来的尺寸激活和显示窗口;

SW_SHOWMAXIMIZED //激活窗口并将其最大化;

SW_SHOWMINIMIZED //激活窗口并将其最小化;

SW_SHOWMINNOACTIVE //窗口最小化,激活窗口仍然维持激活状态;

SW_SHOWNA //以窗口原来的状态显示窗口。激活窗口仍然维持激活状态;

SW_SHOWNOACTIVATE//以窗口最近一次的大小和状态显示窗口。激活窗口仍然维持激活状态;

SW_SHOWNORMAL //激活并显示一个窗口。如果窗口被最小化或最大化,系统将其恢复到原来的尺寸和大小;

窗口的更新

当窗口客户区需要进行更新时, UpdateWindow函数就会被调用, 并绕过消息队列直接发送一个WM_PAINT的消息给窗口过程函数WndProc进行相应处理, UpdateWindow函数的原型如下:

BOOL UpdateWindow( HWND hWnd );

该函数只有一个参数, 参数为需要更新的窗口句柄; 函数调用成功返回值为非0, 调用失败时返回值为0。明天学习窗口过程WndProc是如何处理这些消息的。

C语言Windows程序设计->第四天->详解我的窗口(下)

对窗口创建的详细解释

·消息的循环

"消息", 在Windows程序设计中是个十分重要的概念, 是消息驱动着Windows系统的运行。

- 24 -

通过昨天的学习, 我们已经知道一个窗口是如何被创建并且显示在屏幕上, 当一个窗口被创建之后, Windows就开始向窗口源源不断的发送一些消息, 消息来自Windows的为该程序创建的一个消息队列中, 然后窗口过程根据不同的消息作出相应的处理。

Windows中的"消息"(MSG)是一个结构类型, 该结构定义在WINUSER.H头文件中, MSG结构的定义如下:

/* Message structure*/

typedef struct tagMSG{

HWND hwnd; //窗口句柄, 代表消息所属的窗口

UINT message; //消息的编号

WPARAM wParam; //附加信息

LPARAM lParam; //附加信息

DWORD time; //消息的时间

POINT pt; //鼠标的位置

} MSG, *PMSG;

成员HWND为消息所指向的窗口句柄, 代表消息所属的窗口; UINT为消息的编号, 为一个unsigned int型数据; WPARAM和LPARAM用于指定消息的附加信息, 具体含义取决于具体的消息; DWORD为unsigned long型, 为消息进入消息队列时的时间; POINT也是一种结构类型, 在WINDEF.H中的定义如下:

typedef struct tagPOINT

{

LONG x;

LONG y;

} POINT, *PPOINT;

在这里用来记录消息进入消息队列时的鼠标位置。

当消息产生后会存在于Windows为该程序维护的一个消息队列里, 要得到这些消息我们需要使用GetMessage函数, 此函数的作用是消息队列里取得一个消息并将该消息存放在指定的一个MSG结构的一个对象中, 函数的原型如下:

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

参数一LPMSG lpMsg为指向一个MSG结构型的指针, 用于从消息队列中接收消息; 参数二为待检索消息的窗体句柄, 通常设为NULL, 第三、四个参数UINT wMsgFilterMin, UINT

wMsgFilterMax分别代表允许被检索的最小消息值的整数值和最大消息值的整数值, 当成功取得消息时, 该消息就会被从消息队列中删除(消息是个例外)。

函数的返回值, 成功取得除WM_QUIT之外的消息返回值为非零, 取得WM_QUIT消息时, 返回值是零, 如果出现了错误, 返回值为-1。

在GetMessage后我们还看到使用了个TranslateMessage函数, 该函数用来将虚拟键消息转换为字符消息(暂时先了解下), 完成对消息的翻译转换后调用DispatchMessage函数将得到的消息再传递给Windows, 然后由Windows去调用我们的窗口过程(回调函数)。窗口过程完成对消息的相关处理后再将控制权转回给Windows, 继续下一轮的消息处理。

·对消息的处理

在以上的叙述中我们已经大致了解了一个窗口创建的工作流程, 其中我们多次提到了窗口过程, 也就是我们自定义的消息处理函数, 窗口过程决定对得到的消息以何种方式进行处理, 在我们创建窗口的示例中, 自定义的窗口过程名为WndProc, 窗口过程总是以以下形式声明:

LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ;

窗口过程有四个参数, 分别对应这个MSG结构中的前四个成员。

关于窗口过程的使用, 是通过窗口类

dProc = WndProc ;

- 25 -

的这种方式引用的, 一个窗口过程总是与一个特定的窗口类进行关联。

例如在我们创建窗口的示例中分别对得到的四种消息进行处理, WM_CREATE、WM_PAINT、WM_LBUTTONDOWN和WM_DESTROY。在Windows中, 消息总是以WM为前缀作为消息类型的标示,

这些标识符在WINUSER.H头文件中有相关的定义, 关于这些的消息详细情况可以查阅MSDN

Library。

接下来我们使用了个switch语句对得到的这些消息进行处理, 通常是以这样的形式出现:

switch( message )

{

case 消息1:

[对消息1进行处理] ;

return 0 ;

case 消息2:

[对消息1进行处理] ;

return 0 ;

case 消息3:

[对消息3进行处理] ;

return 0 ;

......

case 消息n:

[对消息n进行处理];

return 0;

}

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

这里需要注意2点,一是在case语句对消息处理完毕后一般使用return 0;结束窗口过程,

对于如果使用break;语句函数将再次调用默认的消息处理函数DefWindowProc进行处理;

二是DefWindowProc( hwnd, message, wParam, lParam );有这条语句是十分必要的, 对于我们没有处理到的消息, 必须进行调用默认的消息处理函数DefWindowProc来确保得到的每个消息进行处理, DefWindowProc函数的原型如下:

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

参数一HWND hWnd为接收消息的窗口句柄;

参数二UINT Msg,为需要处理的消息类型;

参数三wParam与参数四LPARAM IParam为额外的消息信息。该参数的内容与msg参数值有关。

返回值为消息处理的结果, 根据不同的消息返回值可能不同。

如果没有对其他消息使用默认的消息处理, 那么窗口的其他正常行为将无法进行。

现在, 我们来看下创建示例窗口中是如何对这4种消息进行处理的。

1>. 处理WM_CREATE 消息

WM_CREATE消息是窗口在成功创建时进入消息队列, 但值得一提的是: WM_CREATE在CreateWindow函数返回之前就被送到了消息队列当中。

我们对于WM_CREATE消息作出的响应是弹出一个对话框告诉用户窗口已被成功创建:

MessageBox( hwnd, TEXT("窗口已创建完成!"), TEXT("我的窗口"), MB_OK |

MB_ICONINFORMATION ) ;

2>. 处理WM_PAINT消息

WM_PAINT消息是当窗口客户区无效并且需要重新更新时接收到的消息, 何时窗口客户区会变得无效且需要更新?例如以下情况:

1. 首次被创建时, 这时整个客户区都是无效的;

- 26 -

2. 调整窗口的尺寸时, 这时客户区也会变得无效;

3. 最小化窗口后再将其恢复显示时;

4. 拖拽调整窗口位置时;

当客户区变得无效时我们就需要对其进行重绘, 一般总是调用BeginPaint函数作为重绘的开始, BeginPaint函数的原型如下:

HDC BeginPaint( HWND hwnd, LPPAINTSTRUCT lpPaint );

参数一为程序被重绘的窗口句柄, 参数二为用来指向接收绘画信息的一个PAINTSTRUCT结构;当函数调用成功, 返回值是指定窗口的“显示设备描述表”句柄; 如果函数失败,返回值是NULL。

通过设备环境句柄我们就可以在窗口的客户区进行绘制一些信息, 例如绘制图形或一些文本, 取得设备环境句柄:

hdc = BeginPaint( hwnd, &ps ) ;

成功获取到设备环境句柄后就可以开始对客户区进行重绘工作了, 首先使用GetClientRect函数获取该窗口客户区左上角(0, 0)和右下角(x, y)的坐标, 函数原型。

BOOL GetClientRect(

HWND hWnd, // 窗口句柄

LPRECT lpRect //指向一个矩形结构用来接收客户区坐标

);

函数调用成功时,返回一个非零值; 调用失败, 返回零。

随后使用了DrawText向矩形结构里输入一些文字, DrawText函数的原型如下:

int DrawText(

HDC hDC, // 设备描述表句柄

LPCTSTR lpString, // 将要绘制的字符串

int nCount, // 字符串的长度

LPRECT lpRect, // 指向矩形结构RECT的指针

UINT uFormat // 绘制选项

);

第一个参数为BeginPaint函数所返回的设备环境句柄;

第二个参数为要绘制的字符串;

第三个参数为字符串的长度, 如果参数为-1, 则表示一个以0结尾的字符串, 让DrawText自己计算字符个数;

第四个参数为一组通过组合的标识符, 这些标识符用来标明文本的绘制方式, 例如较常用的一些有:

DT_BOTTOM //将正文调整到矩形底部;

DT_CENTER //使正文在矩形中水平居中;

DT_LEFT //正文左对齐;

DT_RIGHT //正文右对齐;

DT_TOP //正文顶端对齐;

DT_VCENTER //使正文在矩形中垂直居中;

DT_WORD_ELLIPSIS //截短不符合矩形的正文,并增加省略号;

此外还有更多的绘制方式, 这些标识符定义在WINUSER.H头文件中, 可以到MSDN Library查询更多详细的介绍。

绘制完成后使用EndPaint( hwnd, &ps ) ;释放设备环境句柄, 这样窗口的一遍重绘就完成了, 窗口客户区再次变得有效。

3>. 处理WM_LBUTTONDOWN 消息

- 27 -

WM_LBUTTONDOWN 消息是当鼠标左键在客户区被按下时产生的消息, 处理的过程为简单的弹出一个对话框提示鼠标左键被按下了。

4>.处理 WM_DESTROY 消息

当用户点击窗口上的关闭按钮时, WM_DESTROY 消息产生, 我们弹出一个"关闭程序!"的对话框后使用PostQuitMessage 函数向消息队列插入一个WM_QUIT, 当GetMessage获取到WM_QUIT消息时便返回一个0, 使while退出循环执行

return ;

//为PostQuitMessage( 0 ); 函数中的参数, 通常为0

从而结束程序运行。

C语言Windows程序设计->第五天->回顾与反思

回顾与反思

在前四天的学习中, 我们主要学习了有关字符集和创建窗口的一些知识, 下午对这些知识做一下简要的回顾与在编程思想上的反思。

一、回顾

在第一天的学习中主要了解了Windows的历史、相关的开发环境并且完成了我们的第一个Windows程序, MessageBox对话框, 这里, 再来看看我们的第一个Windows程序, 他标志着我们已经跨入了Windows程序设计的大门, 就像printf("Hello,world!");一样经典!

#include

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine,

int iCmdShow )

{

MessageBox( NULL, TEXT("Hello,world!"), TEXT("MessageBox"), 0 );

return 0;

}

在第二天主要学习了有关ASCII与Unicode字符集的知识, ASCII字符用7位或8位(扩展版)二进制表示, 每个字符占一个字节, 最多能容下256个字符, 虽然ASCII码是一种非常可靠的标准, 但遗憾的是但它不能满足其他一些国家的需求, 因此Unicode便担任了这个任务, Unicode使用16位(2字节)的二进制编码方式来表示字符, 最多能够容下65536个字符, 能够容下世界上的所有书面文字以及一些特殊符号, Unicode只有一个字符集, 避免了二义性, 能够满足跨语言、跨平台进行文本转换、处理的要求。

此外, 我们还了解了宽字符与C语言的一些关系, 宽字符在C语言中被定义为wchar_t型, 实际上它就是一个2字节的unsigned short型数据, 随后我们了解到了关于宽字符处理函数的一些用法, 比如wcscat(连接字符串)、wcschr(找字符在字符串中出现的位置)、wcscmp(比较字符串)、wcscpy(复制字符串)、wcslen(求字符串长度)、wcsstr(判断一个字符串在另一个中首次出现的位置), 我们还学习了一个TEXT宏的用法, 省去在声明一个宽字符字符串时在前面加'L'字母的问题。

第三天就我们使用va_list宏完成了myMessageBox函数变参的功能, 可以用来实现像printf函数那样格式化输出, 顺便看了段创建一个普通窗口的代码。

第四天到今天上午我们来详细分析了创建窗口的详细过程, 首先要声明一个窗口过程函数, 用来处理Windows发来的消息, 然后通过WNDCLASS窗口类定义一个窗口类的对象,

一个窗口总是根据窗口类来创建的, 将这个窗口类对象的属性全部定义好后就调用RegisterClass函数对窗口类进行注册, 再呼叫CreateWindow函数完成对窗口的创建, 当窗口创建完成后并不会立即显示在屏幕上, 我们还需要利用ShowWindow配合UpdateWindow这两个函数让窗口正常显示出来。

当一个窗口创建完成后, Windows系统就会源源不断的发来一些消息等待我们处理,

- 28 -

通过GetMessage我们从消息队列中获取这些消息, 并用TranslateMessage把虚拟键消息转换为字符消息, 再通过DispatchMessage函数将得到的消息分发给窗口过程, 完成对消息的处理。

对于消息的处理部分, 我们使用switch语句, case到不同的消息后作出不同的响应,

别忘了, 调用DefWindowProc默认的消息处理函数处理我们没有处理到消息, 确保每个消息都能被正常的处理。

二、反思

在从C语言控制台程序设计过渡到Windows平台程序设计时, 首先最大的感触就是常量标识符好多, 函数好多, 好再这些标识符不需要强记下来, 对于API也有MSDN Library这个大手册方便查阅。

其次, 感觉到了C语言控制台编程对函数的调用与Windows程序设计中对函数的调用对象不同, 在C语言控制台编程中, 我们通过调用库函数以及自定义函数完成程序功能的实现, 而在Windows程序设计中, 虽说我们也调用了系统提供的函数, 但是最终, 例如对消息的处理是系统调用我们定义好的消息处理函数, 以便完成程序的功能。

笔者认为, 对于Windows程序设计学习的入门阶段, 首先应该从大体上了解整个程序大致的框架, 以及能从宏观角度了解程序的工作流程, 不要急着能够在不看书不查资料的情况下就能顺利的把一个程序敲出来, 对于程序运行的具体细节, 等入门之后再做研究,

遇到不认识的标识符或API, 可以直接查阅MSDN Library或者在搜索引擎上搜索它, 就可以得到它的详细解释, 要善于利用MSDN Library和搜索引擎。

C语言Windows程序设计->第六天->GDI与设备环境

·GDI简介

GDI(Graphics Device Interface), 图形设备接口, 负责系统与绘图程序之间的信息交换,处理所有Windows程序的图形输出。利用GDI所提供的函数就可以方便的在屏幕、打印机及其它输出设备上输出图形, 文本等操作。GDI的目的在于将应用程序上的输出转化为在硬件上的输出, 使应用程序无需关心硬件设备以及设备驱动, 降低了Windows程序设计的难度, 提高开发效率。

GDI的相关特点:

1>. 开发者无需关心物理硬件的设备类型;

2>. 不允许应用程序直接访问物理显示硬件, 通过获取显示设备的"设备环境"句柄实现与显示硬件的间接通信;

3>. 程序与物理显示设备进行通信时, 必须首先获得与特定窗口相关联的设备环境;

4. Windows根据设备环境的数据结构完成信息的输出。

·再谈WM_PAINT消息

"当窗口显示区域的一部分显示内容或者全部变为"无效", 以致于必须"更新画面"时,应用程序将得到此消息。"这是我们在前面的学习中以及了解到的概念, 并且还学习了一些令窗口"客户区"变成无效的一些情况:

1>. 首次被创建时, 这时整个客户区都是无效的;

2>. 调整窗口的尺寸时, 这时客户区也会变得无效;

3>. 最小化窗口后再将其恢复显示时;

4>. 拖拽调整窗口位置时;

除此之外以下这些情况也能导致窗口客户区变成无效, 这些情况为:

- 29 -

1>. 窗口滚动条被拖动;

2>. 显式调用了InvalidateRect或者InvalidateRgn函数以产生WM_PAINT消息;

还有一些情况则可能导致客户区变为无效:

1>. 菜单被拉下然后又重新释放;

2>. 关闭一个覆盖在客户区上的对话框;

3>. 窗口控件显示提示信息。

一般来说, 当程序进入消息循环前都会调用UpdateWindows来产生一个 最初的WM_PAINT

消息来通知窗口重绘客户区。

实际上, 当窗口客户区的某一部分需求重绘时, 并不需要对整个窗口客户区进行重绘, 通常只需要更新这一部分区域即可, 这部分无效的区域也被称为"无效矩形"。

·使用TextOut函数在客户区显示字符

在创建窗口的示例程序中, 我们尝试使用了DrawText对字符文本进行了格式化输出,

但是DrawText相对来说不够灵活, 使用最频繁的字符输出函数当数TextOut, 该函数可以使用系统当前选择的字体、背景颜色和正文颜色将一个字符串输出到指定位置。函数的原型如下:

BOOL TextOut(

HDC hdc, // 设备环境句柄

int nXStart, // 字符串的开始位置 x坐标

int nYStart, // 字符串的开始位置 y坐标

LPCTSTR lpString, // 待输出的字符串

int cchString ); // 字符串字符个数

如果我们要使用TextOut进行字符的输出, 根据TextOut函数的参数所示, 首先我们应该获取设备环境的句柄。

设备环境(DC, Device Context)是一个抽象的概念, 通过设备环境就可以使用GDI中的函数。

如何获取设备环境句柄

方法一:

使用BeginPaint函数。这种方法比较适合在处理WM_PAINT消息时使用, BeainPaint函数的原型如下:

HDC BeginPaint(

HWND hwnd, // 窗口的句柄

LPPAINTSTRUCT lpPaint // 绘制信息结构);

参数一为窗口句柄, 参数二为绘制信息结构PAINTSTRUCT对象的地址。

关于绘制信息结构

PAINTSTRUCT结构包含了应用程序用来绘制它所拥有的窗口客户区所需要的信息。PAINTSTRUCT的结构定义如下:

typedef struct tagPAINTSTRUCT {

HDC hdc;

BOOL fErase;

RECT rcPaint;

BOOL fRestore;

BOOL fIncUpdate;

BYTE rgbReserved[32];

} PAINTSTRUCT, *PPAINTSTRUCT;

当调用BeginPaint函数时, Windows将自动填充这个结构体中的成员相关属性, 程序仅能

- 30 -

使用前三个成员, 其他为Windows内部使用。

参数一HDC hdc即为设备环境句柄, BeginPaint函数的返回值也就是这里的设备环境句柄,

简单来说就是先填充再返回;

参数二fErase决定是否擦出客户区背景, 如果为非零值则擦除背景,否则不擦除背景;

参数三rcPaint 通过指定客户区左上角和右下角的坐标确定一个要绘制的矩形范围, 即使需要更新的无效区域不是一个矩形, Windows也会把需要重绘的部分裁剪为一个矩形。

如果你仍想重绘整个客户区, 可以在BeginPaint函数之前调用InvalidateRect( hwnd,

NULL, TRUE );使整个客户区无效化。

方法二:

使用设备环境句柄有时不仅仅是在处理WM_PAINT消息时才会用到, 我们还可能使用设备环境句柄用作其他用途。

1>. 通过GetDC函数来获得

GetDC函数的原型如下:

HDC GetDC(HWND hWnd);

该函数的参数为一个窗口句柄, 返回值为窗口客户区的设备环境句柄, 在GetDC中得到的裁剪矩形为整个客户区,。 在使用完成后需要调用ReleaseDC( hwnd, hdc )函数将设备环境释放。需要注意的是, GetDC不会将无效区域有效化, 如果需要对其进行有效化需要调用ValidateRect函数。

2>. 通过GetWindowDC函数来获得

GetWindowDC函数与GetDC不同的是: GetDC返回的是整个窗口客户区的设备环境句柄, 而GetWindow返回得是整个窗口的设备环境的句柄, 通过这个句柄你还可以在窗口的标题栏进行输出, 在使用完成后同样要对其进行释放。

下面尝试使用TextOut对文本进行输出, 代码如下:

#include

LRESULT CALLBACK WinProc( HWND, UINT, WPARAM, LPARAM );

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine,

int iCmdShow )

{

static TCHAR szAppName[] = TEXT( "CreateMyWinow" ) ;

HWND hwnd;

MSG msg;

WNDCLASS wndclass;

dProc = WinProc ;

nce = hInstance ;

assName = szAppName ;

= CS_HREDRAW | CS_VREDRAW ;

xtra = 0 ;

xtra = 0 ;

kground = (HBRUSH) GetStockObject(WHITE_BRUSH) ;

nuName = NULL ;

r = LoadCursor(NULL, IDC_ARROW) ;

= LoadIcon(NULL, IDI_APPLICATION) ;

if( !RegisterClass(&wndclass) )

{

MessageBox( NULL, TEXT("窗口注册失败!"), TEXT("错误"), MB_OK |

MB_ICONERROR ) ;

return 0 ;

}

hwnd = CreateWindow(

szAppName,

TEXT("创建窗口"),

- 31 -

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

600,

400,

NULL,

NULL,

hInstance,

NULL

) ;

ShowWindow( hwnd, iCmdShow ) ;

UpdateWindow( hwnd ) ;

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

{

TranslateMessage( &msg ) ;

DispatchMessage( &msg ) ;

}

return ;

}

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

{

HDC hdc ;

PAINTSTRUCT ps ;

RECT rect ;

TCHAR szTextBuffer[1024]; //存储字符的缓冲区大小

size_t iLength; //字符串长度

switch( message )

{

case WM_CREATE:

MessageBox( hwnd, TEXT("窗口已建立!"), TEXT("成功"), MB_OK ) ;

return 0 ;

case WM_PAINT:

hdc = BeginPaint( hwnd, &ps );

GetClientRect( hwnd, &rect ) ;

iLength = wsprintf(szTextBuffer, TEXT("使用TextOut进行文字输出!") ) ;

//向缓冲区写入待输出的文字

TextOut( hdc, 200, 100, szTextBuffer, iLength ) ;

EndPaint( hwnd, &ps ) ;

return 0 ;

case WM_DESTROY:

PostQuitMessage( 0 ) ;

return 0 ;

}

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

}

对于TextOut函数的详细解释以及更加完善的输出方式将在下一篇随笔中进行。

C语言Windows程序设计->第七天->TextOut与系统字体

·TextOut函数

TextOut函数的作用是使用系统当前选择的字体、背景颜色以及正文颜色将一个字符串输出到指定位置, 其函数的原型如下:

BOOL TextOut(

- 32 -

HDC hdc, //设备环境句柄

int nXStart, //字符串开始输出的x坐标

int nYStart, // 字符串开始输出的y坐标

LPCTSTR lpString, //需要输出的字符串

int cbString // 字符串的长度

);

当函数调用成功时返回一个非零的值, 调用失败时, 返回值为0。

尝试下用TextOut函数在屏幕上输出些字符串, 输出一个4列3行的学生信息。 定义一个结构体, 成员为姓名, 年龄, 住址和联系电话, 相关的代码:

#include

LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ; //声明窗口过程函数

//待输出的信息

struct{

TCHAR *szName ;

TCHAR *szAge ;

TCHAR *szAddr ;

TCHAR *szTel ;

}STU[] = {

TEXT("李??"), TEXT("23"), TEXT("上海"), TEXT("123456"),

TEXT("王??"), TEXT("18"), TEXT("安徽"), TEXT("654321"),

TEXT("孙??"), TEXT("21"), TEXT("浙江"), TEXT("987654")

};

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine,

int iCmdShow )

{

static TCHAR szAppName[] = TEXT("PrintText") ;

HWND hwnd ;

MSG msg ;

WNDCLASS wndclass;

//窗口类成员属性

dProc = WndProc ;

assName = szAppName ;

nce = hInstance ;

= CS_HREDRAW | CS_VREDRAW ;

kground = (HBRUSH) GetStockObject(WHITE_BRUSH) ;

r = LoadCursor( NULL, IDC_ARROW ) ;

= LoadIcon( NULL, IDI_APPLICATION ) ;

xtra = 0 ;

xtra = 0 ;

nuName = NULL ;

//注册窗口类

if( !RegisterClass( &wndclass ) )

{

MessageBox( NULL, TEXT("错误, 窗口注册失败!"), TEXT("错误"), MB_OK );

return 0 ;

}

//创建窗口

hwnd = CreateWindow(szAppName, TEXT("TextOut用法示例"),

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT,

NULL, NULL, hInstance, NULL );

//显示窗口

ShowWindow( hwnd, iCmdShow ) ;

UpdateWindow( hwnd ) ;

//获取、翻译、分发消息

- 33 -

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

{

TranslateMessage( &msg ) ;

DispatchMessage( &msg ) ;

}

return ;

}

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

{

HDC hdc ;

PAINTSTRUCT ps ;

int y = 0 ; //记录x、y方向坐标

int i ;

switch(message)

{

case WM_PAINT: //处理WM_PAINT消息

hdc = BeginPaint( hwnd, &ps ) ;

//输出信息标题文字

TextOut( hdc, 0, y, TEXT("姓名:"), lstrlen(TEXT("姓名:")) ) ;

TextOut( hdc, 200, y, TEXT("年龄:"), lstrlen(TEXT("年龄:")) ) ;

TextOut( hdc, 400, y, TEXT("住址:"), lstrlen(TEXT("住址:")) ) ;

TextOut( hdc, 600, y, TEXT("联系电话:"), lstrlen(TEXT("联系电话:")) );

y += 25 ; //y方向坐标+25

//输出信息

for( i = 0; i < 3; i++ )

{

TextOut( hdc, 0, y, STU[i].szName, lstrlen(STU[i].szName) ) ;

//(0, 0)处输出姓名

TextOut( hdc, 200, y, STU[i].szAge, lstrlen(STU[i].szAge) ) ;

//(200, y)处输出年龄

TextOut( hdc, 400, y, STU[i].szAddr, lstrlen(STU[i].szAddr) ) ;

//(400, y)处输出住址

TextOut( hdc, 600, y, STU[i].szTel, lstrlen(STU[i].szTel) ) ;

//(600, y)处输出联系电话

y += 25 ; //y方向坐标+25, 相当于换一行

}

EndPaint( hwnd, &ps ) ;

return 0 ;

case WM_DESTROY: //处理WM_DESTROY消息

PostQuitMessage(0) ;

return 0 ;

}

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

}

运行的效果如下:

- 34 -

理清代码思路:

包含头文件 -> 声明窗口过程函数 -> 定义WinMain函数 -> 声明相关变量 -> 为wndclass窗口类成员赋值 -> 注册窗口类 -> 建立窗口获取窗口句柄 -> 显示窗口 -> 获取、翻译、分发消息 -> 定义窗口过程函数。

•关于TextOut中的坐标

在没有接触过任何GUI程序设计的情况下, 通常对窗口中的坐标不太了解, 与平时常见的坐标系感觉不太一样, 在一个窗口的客户区中, ( 0, 0 )坐标即为窗口客户区的左上角,

沿着竖直向下方向为y坐标, 水平向右方向为x坐标, 用图示来表示如下:

在上面的例子中, 看起来一起都很好, 4列3行显示的整整齐齐, 这是因为我们对坐标的不断调整才使其显示成这样的效果, 在这篇文章的开头部分已经说明了, TextOut函数是使用系统当前选择的字体、背景颜色以及正文颜色将一个字符串输出到指定位置。

要知道, 系统字体是可以根据用户的需求进行调节, 用户可以选择将用户字体调大或者调小, 当字体调大时我们就应该考虑到两行文字之间的间距问题, 如果调的很大, 而我们在程序中使用的y方向坐标每行之间的间隔留的过小, 那么两行文字就会挤在一起造成无法辨认, 当用户把系统字体调小时如果y方向间距留的过大则影响显示效果, 那么, 如何才能让显示效果达到最好呢?

·获取系统字体信息

要使显示的文字能够根据用户的屏幕大小以及分辨率自动调整显示位置以及字体间的间距, 我们就要知道系统字体的字符的高度以及宽度, 还要知道一句话的总宽度, 防止这句话被输出到窗口外部, 当一行文字因过长输出的窗口外部, 多出的文字就会被截掉, 这可不是我们想要的结果。

获取系统当前字体信息的函数为GetTextMetrics, 该函数的原型如下:

BOOL GetTextMetrics(

HDC hdc, // 设备环境句柄

LPTEXTMETRIC lptm // 指向一个TEXTMETRIC结构的指针, 该结构用于存放字体信息。

- 35 -

);

如果函数调用成功, 返回值为非零, 如果函数调用失败, 返回值是0。

关于TEXTMETRIC结构:

TEXTMETRIC结构的成员为当前字体的各种信息, 该结构定义在WINGDI.H头文件中, 定义如下:

typedef struct tagTEXTMETRIC {

LONG tmHeight; //字符高度

LONG tmAscent; //字符上部高度(基线以上)

LONG tmDescent; //字符下部高度(基线以下)

LONG tmInternalLeading, //由tmHeight定义的字符高度的顶部空间数目

LONG tmExternalLeading, //夹在两行之间的空间数目

LONG tmAveCharWidth, //平均字符宽度

LONG tmMaxCharWidth, //最宽字符的宽度

LONG tmWeight; //字体的粗细轻重程度

LONG tmOverhang, //加入某些拼接字体上的附加高度

LONG tmDigitizedAspectX, //字体设计所针对的设备水平方向

LONG tmDigitizedAspectY, //字体设计所针对的设备垂直方向

BCHAR tmFirstChar; //为字体定义的第一个字符

BCHAR tmLastChar; //为字体定义的最后一个字符

BCHAR tmDefaultChar; //字体中所没有字符的替代字符

BCHAR tmBreakChar; //用于拆字的字符

BYTE tmItalic, //字体为斜体时非零

BYTE tmUnderlined, //字体为下划线时非零

BYTE tmStruckOut, //字体被删去时非零

BYTE tmPitchAndFamily, //字体间距(低4位)和族(高4位)

BYTE tmCharSet; //字体的字符集

} TEXTMETRIC;

可以看出, 在TEXTMETRIC结构中共有20位成员, 不过暂时我们只关心前7个成员, 这7个成员为:

LONG tmHeight; //字符高度

LONG tmAscent; //字符上部高度(基线以上)

LONG tmDescent; //字符下部高度(基线以下)

LONG tmInternalLeading, //由tmHeight定义的字符高度的顶部空间数目

LONG tmExternalLeading, //夹在两行之间的空间数目

LONG tmAveCharWidth, //平均字符宽度

LONG tmMaxCharWidth, //最宽字符的宽度

在《Windows程序设计》第五版一书中, 用图示来表示出了字符纵向尺寸的4个值:

- 36 -

其中tmAscent是指字符上部高度(基线Baseline以上), tmDescent为字符下部高度(基线以下), tmHeight的值为tmAscent与tmDescent的和, 表示整个字符的高度,

tmInternalLeading为内部间距, 通常用来显示字母上部的重音符号。除此之外,TEXTMETRICT结构中的tmExternalLeading用来表示字体设计者建议在两行文本之间留出的空间大小; tmAveCharWidth表示小写字符的加权平均宽度, 大写字符的加权平均宽度通常按小写字符的加权平均宽度的1.5倍来计算。tmMaxCharWidth为字体中最宽字符的宽度。

·使用系统字体信息

在使用系统字体信息之前我们首先要获取系统字体的信息, 那么, 在何时获取呢? 通常的做法就是在收到WM_CREATE消息时, WM_CREATE是窗口过程函数接收到的第一条消息,

在这时获取系统字体消息再好不过了。

首先定义一个TEXTMETRIC结构的对象:

TEXTMETRIC tm ;

然后再获取设备环境句柄, 并调用GetTextMetrics函数:

hdc = GetDC(hwnd) ;

GetTextMetrics( hdc, &tm ) ;

ReleaseDC( hwnd, hdc ) ;

这样, tm中就有了我们需要的信息了。

下面我们尝试将文章开始部分的代码简单的更改为根据系统字体信息的方式进行输出, 仅改动窗口过程函数部分:

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

lParam )

{

HDC hdc ;

PAINTSTRUCT ps ;

TEXTMETRIC tm ; //记录系统字体信息

static int cxChar, cyChar ; //系统字体的平均宽度、高度

int y = 0 ; //记录x、y方向坐标

int i ;

switch(message)

{

case WM_CREATE: //处理WM_CREATE消息

hdc = GetDC( hwnd ) ; //获取设备环境句柄

GetTextMetrics( hdc, &tm ) ; //获取系统字体信息

ReleaseDC( hwnd, hdc ) ; //设备环境句柄使用完毕, 释放

cxChar = harWidth ; //得到字体平均宽度

cyChar = ht + rnalLeading ; //得到字体高度

return 0 ;

case WM_PAINT: //处理WM_PAINT消息

hdc = BeginPaint( hwnd, &ps ) ;

//输出信息标题文字

TextOut( hdc, 0, y, TEXT("姓名:"), lstrlen(TEXT("姓名:")) ) ;

TextOut( hdc, 200, y, TEXT("年龄:"), lstrlen(TEXT("年龄:")) ) ;

TextOut( hdc, 400, y, TEXT("住址:"), lstrlen(TEXT("住址:")) ) ;

TextOut( hdc, 600, y,TEXT("联系电话:"),lstrlen(TEXT("联系电话:")));

y += cyChar ; //y方向增加一个字体高度的单位

//输出信息

for( i = 0; i < 3; i++ )

{

TextOut( hdc, 0, y, STU[i].szName, lstrlen(STU[i].szName) ) ;

//(0, 0)处输出姓名

TextOut( hdc, 200, y, STU[i].szAge, lstrlen(STU[i].szAge) ) ;

- 37 -

//(200, y)处输出年龄

TextOut( hdc, 400, y, STU[i].szAddr, lstrlen(STU[i].szAddr) ) ;

//(400, y)处输出住址

TextOut( hdc, 600, y, STU[i].szTel, lstrlen(STU[i].szTel) ) ;

//(600, y)处输出联系电话

y += cyChar ; //y方向坐标cyChar, 相当于换一行

}

EndPaint( hwnd, &ps ) ;

return 0 ;

case WM_DESTROY: //处理WM_DESTROY消息

PostQuitMessage(0) ;

return 0 ;

}

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

}

修改后的代码运行效果如下:

一些问题:

1>. 上面的代码中, 我们虽说获取了字符的平均宽度, 但是我们并没有去使用它;

2>. 修改后的代码仅仅是解决了多行文本的显示问题, 并没有解决当文本过长时的输出越界问题;

3>. 当行数过多时, 多出的行数也会被截断不予显示。

解决思路:

解决水平方向越界问题: 利用平均字体的宽度计算何时将会越界并进行相应的换行处理;

解决水平方向越界问题: 使用滚动条, 滚动条的使用将在下次的学习中介绍。

C语言Windows程序设计->第八天->滚动条

对上一天学习的回顾:

1>. TextOut函数的使用

TextOut函数的作用是使用系统当前选择的字体、背景颜色以及正文颜色将一个字符串输出到指定位置, 函数原型:

BOOL TextOut(

HDC hdc, //设备环境句柄

int nXStart, //字符串开始输出的x坐标

int nYStart, // 字符串开始输出的y坐标

LPCTSTR lpString, //需要输出的字符串

- 38 -

int cbString // 字符串的长度

);

当函数调用成功时返回一个非零的值, 调用失败时, 返回值为0。

2>. 取得当前系统字体信息:

使用GetTextMetrics函数可以取得当前字体信息, 函数原型如下:

BOOL GetTextMetrics(

HDC hdc, // 设备环境句柄

LPTEXTMETRIC lptm // 指向一个TEXTMETRIC结构的指针, 该结构用于存放字体信息。

);

参数二LPTEXTMETRIC指向TEXTMETRIC结构, 在函数调用成功时, 函数将系统当前字体的各种信息复制到TEXTMETRIC结构中。

Windows滚动条介绍

滚动条由滚动滑块以及两端的滚动箭头组成, 滚动条的作用是当需要显示的内容超过窗口客户区大小时提供上下/左右的翻页使用户能够完整的阅读显示信息, 滚动条的图示:

滚动条理论基础

1>. 上下滚动

以垂直方向的滚动条为例, 当用户向下滚动滚动条时目的是想看到下方更多的的信息, 因此我们需要将下方的信息显示出来, 如何显示更多的信息?

解决方案: 将不需要被显示的信息显示到客户区外, 令信息自动被Windows截掉, 图示说明:

- 39 -

由图示看出, 当用户向下翻动滚动条实际上我们是将起始输出部分的y坐标设为负数, 使已经显示过的信息输出到客户区的上部, 我们知道, 输出到客户区外部的信息会被Windows自动截掉, 所以用户不会再次看到已经显示过的信息, 取而代之的就是下方等待显示的信息, 上翻以及左右翻动的显示思路与下翻相同, 不再介绍。

2>. 如何创建一个带有滚动条的窗口?

创建带有水平/垂直的滚动条的窗口十分简单, 在CreateWindow函数中说明下即可,

CreateWindow函数的原型回顾:

HWND CreateWindow(

LPCTSTR lpClassName, //窗口类名称

LPCTSTR lpWindowName, //窗口标题

DWORD dwStyle, //窗口样式

int x, //窗口初始x坐标

int y, //窗口初始y坐标

int nWidth, //窗口初始x方向尺寸

int nHeight, //窗口初始y方向尺寸

HWND hWndParent, //父窗口句柄

HMENU hMenu, //窗口菜单句柄

HANDLE hlnstance, //程序实例句柄

LPVOID lpParam //创建参数

);

要窗口带有滚动条的窗口, 只需要在第三个参数

DWORD dwStyle, //窗口样式

也就是在窗口样式的属性中使用位或( | )运算对相关的标识符进行组合即可得到一个带有垂直/水平滚动条的窗口,

WS_HSCROLL //水平滚动条的标识符

WS_VSCROLL //垂直滚动条的标识符

- 40 -

例如要创建一个既含有垂直滚动条又含有水平滚动条的组合:

WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL

3>. 对于滚动条, Windows需要做哪些事?

当带有滚动条的窗口创建好后, Windows就会做一些必要的处理来协助我们更好的使用滚动条, Windows需要做的事情如下:

1>. 处理滚动条中的所有鼠标消息;

2>. 当用户单击滚动条时提供被单击部分的轻微闪烁;

3>. 当用户拖动滑块时在滚动条内移动滑块;

4>. 当窗口大小被调整时, 自动调整滚动条的长度;

5>. 向滚动条所在的窗口发送滚动条的相关消息。

4>. 我们要做的事情:

相对于系统, 我们需要做的事情已经较为轻松了, 主要有4项任务:

1>. 初始化滚动条的位置和滚动条的范围;

2>. 处理系统发来的消息;

3>. 根据发来的消息重置滑块的位置;

4>. 根据滚动条消息重绘客户区显示的内容。

5>. 将会收到哪些滚动条消息?

滚动条消息来源同其他消息一样, 伴随着wParam与lParam消息机制, 当窗口为父窗口时消息的来源为wParam, 此时可忽略lParam的值, lParam用于子窗口消息。

wParam参数分为两部分, 高位字与低位字, 其中高位字代表用户松开鼠标键时滑块的最终位置, 低位字上代表鼠标在滚动条上的动作, 以一个值的形式表现出来, 同样, 为了方便记忆, 有不同的标识符对这些值进行区分, 这些标识符定义在WINUSER.H头文件中, 以SB_开头, 有关滚动条的消息标识符如下:

#define SB_LINEUP 0 //上翻一个单位

#define SB_LINELEFT 0 //左翻一个单位

#define SB_LINEDOWN 1 //下翻一个单位

#define SB_LINERIGHT 1 //右翻一个单位

#define SB_PAGEUP 2 //上翻一页

#define SB_PAGELEFT 2 //左翻一页

#define SB_PAGEDOWN 3 //下翻一页

#define SB_PAGERIGHT 3 //右翻一页

#define SB_THUMBPOSITION 4 //当鼠标放下滑块时

#define SB_THUMBTRACK 5 //移动滑块时

#define SB_TOP 6 //滑块到了顶端

#define SB_LEFT 6 //滑块到了左端

#define SB_BOTTOM 7 //滑块到了底端

#define SB_RIGHT 7 //滑块到了右端

#define SB_ENDSCROLL 8 //释放鼠标

6>. 需要使用到的新函数:

①. SetScrollRange

SetScrollRange函数的作用是设置所指定滚动条范围的最小值和最大值, 其函数的原型如下:

BOOL SetScrollRange(

HWND hWnd, //窗口句柄

- 41 -

);

int nBar, //被设置的滚动条类型

int nMinPos, //滚动条的最小位置

int nMaxPos, //滚动条的最大位置

BOOL bRedraw //重绘标志

参数二int nBar为被设置的滚动条类型, SB_HORZ表示该窗口的水平滚动条, SB_VERT表示垂直滚动条;

参数四BOOL bRedraw指定滚动条是否被重绘以反映变化, 当参数为TRUE, 滚动条被重绘,

FALSE则不被重绘。

②. SetScrollPos

SetScrollPos函数的作用是设置所指定滚动条中的滚动按钮的位置, 函数原型:

int SetScrollPos(

HWND hWnd, //窗体句柄

int nBar, //被设置的滚动条类型

int nPos, //滚动条的新位置

BOOL bRedraw //重绘标志 );

实战滚动条

下面我们尝试着输出一些文字, 使其上下、左右均超过客户区的尺寸, 这样我们就可以实际练习下水平滚动条以及垂直滚动条了, 我们准备了很多行文字, 笔者也不知道到底有多少行, 而且最长的那行文字有多少个也不知道, 我们把他放在一个text.h头文件中, 并计算他到底有多少行以及最长的那行有多少字, 由于文字行数较多, 这里将它在代码框里折叠显示, 定义的头文件如下:

- text.h

#include

#define NUMLINES ( (int)(sizeof(statement) / sizeof(statement[0]) ) ) //计算总行数

TCHAR *statement[] = {

TEXT("没有一种不通过蔑视、忍受和奋斗就可以征服的命运。"),

TEXT("伟人之所以伟大,是因为他与别人共处逆境时,别人失去了信心,他却下决心实现自己的目标。"),

TEXT("世上没有绝望的处境,只有对处境绝望的人。"),

TEXT("当你感到悲哀痛苦时,最好是去学些什么东西。学习会使你永远立于不败之地。"),

TEXT("给自己定目标,一年,两年,五年,也许你出生不如别人好,通过努力,往往可以改变70%的命运。破罐子破摔只能和懦弱做朋友。"),

TEXT("知道自己要干什么,夜深人静,问问自己,将来的打算,并朝着那个方向去实现。而不是无所事事和做一些无谓的事。"),

TEXT("梦想无论怎么模糊,它总潜伏在我们心底,使我们的心境永远得不到宁静,直到梦想成为事实。"),

TEXT("梦是一种欲望,想是一种行动。梦想是梦与想的结晶。"),

TEXT("生气,就是拿别人的过错来惩罚自己。原谅别人,就是善待自己。"),

TEXT("一个实现梦想的人,就是一个成功的人。"),

TEXT("大事坚持原则,小事学会变通。"),

TEXT("一个人有钱没钱不一定,但如果这个人没有了梦想,这个人穷定了。"),

TEXT("不要急于表态不急于表态或发表意见,可以使人对你揣摸猜测。谨慎的沉默是精明人的帮手。一旦表态,你的决定就容易受到批评和非议。如果这些决定是以失败告终,你就会更加倒霉。"),

- 42 -

TEXT("平凡朴实的梦想,我们用那唯一的坚持信念去支撑那梦想。"),

TEXT("不要总谈论自己你若总是谈论自己,那么不是吹嘘自己,就是贬低自己;前者是虚荣的表现,后者是卑微的表现。"),

TEXT("千万不要抱怨,抱怨会使你丢丑。抱怨会使人对你傲慢无礼,并促使别人如你所抱怨的那么做。要赢得别人的帮助,最好的办法是表扬他人。"),

TEXT("凡事终了时务必小心谨慎,顺利抽身退出要比顺利地进入时更难。"),

TEXT("很难说什么是办不到的事情,因为昨天的梦想,可以是今天的希望,并且还可以成为明天的现实。"),

TEXT("当你再也没有什么可以失去的时候,就是你开始得到的时候。"),

TEXT("生命犹如一片绿叶,随着时间的流逝,慢慢变的枯黄,但他的叶脉还是那么清晰可见。"),

TEXT("我们可以躲开大家,却躲不开一只苍蝇。生活中使我们不快乐的常是一些芝麻小事。"),

TEXT("人生的意义不在于拿一手好牌,而在于打好一手坏牌。"),

TEXT("一个人想平庸,阻拦者很少;一个人想出众,阻拦者很多。不少平庸者与周围人关系融洽,不少出众者与周围人关系紧张。"),

TEXT("“危机”两个字,一个意味着危险,另外一个意味着机会,不要放弃任何一次努力。"),

TEXT("世上没有绝望的处境,只有对处境绝望的人。"),

TEXT("再长的路,一步步也能走完,再短的路,不迈开双脚也无法到达。"),

TEXT("有志者自有千计万计,无志者只感千难万难。"),

TEXT("成功与不成功之间有时距离很短只要后者再向前几步。"),

TEXT("世界会向那些有目标和远见的人让路。"),

TEXT("世界青睐有雄心壮志的人。成功所依靠的惟一条件就是思考。当你的思维以最高速度运转时,乐观欢快的情绪就会充斥全身。一个人最完美的作品都是在充满愉快、乐观、深情的状态下完成的。学会让自己安静,把思维沉浸下来,慢慢降低对事物的欲望。把自我经常归零,每天都是新的起点,没有年龄的限制,只要你对事物的欲望适当的降低,会赢得更多的求胜机会。"),

TEXT("学会让自己安静,把思维沉浸下来,慢慢降低对事物的欲望。把自我经常归零,每天都是新的起点,没有年龄的限制,只要你对事物的欲望适当的降低,会赢得更多的求胜机会。"),

TEXT("杨澜:年轻时候应该能够作一些冒险。年轻时候最大的财富,不是你的青春,不是你的美貌,也不是你充沛的精力,而是你有犯错误的机会。"),

TEXT("没有十全十美的东西,没有十全十美的人,关键是清楚到底想要什么。得到想要的,肯定会失去另外一部分。如果什么都想要,只会什么都得不到。"),

TEXT("做自己的决定。然后准备好承担后果。从一开始就提醒自己,世上没有后悔药吃。"),

TEXT("在你内心深处,还有无穷的潜力,有一天当你回首看时,你就会知道这绝对是真的。"),

TEXT("不为模糊不清的未来担忧,只为清清楚楚的现在努力。"),

TEXT("没有一种不通过蔑视、忍受和奋斗就可以征服的命运。"),

TEXT("伟人之所以伟大,是因为他与别人共处逆境时,别人失去了信心,他却下决心实现自己的目标。"),

TEXT("世上没有绝望的处境,只有对处境绝望的人。"),

TEXT("当你感到悲哀痛苦时,最好是去学些什么东西。学习会使你永远立于不败之地。"),

TEXT("给自己定目标,一年,两年,五年,也许你出生不如别人好,通过努力,往往可以改变70%的命运。破罐子破摔只能和懦弱做朋友。"),

TEXT("知道自己要干什么,夜深人静,问问自己,将来的打算,并朝着那个方向去实现。

- 43 -

而不是无所事事和做一些无谓的事。"),

TEXT("梦想无论怎么模糊,它总潜伏在我们心底,使我们的心境永远得不到宁静,直到梦想成为事实。"),

TEXT("梦是一种欲望,想是一种行动。梦想是梦与想的结晶。"),

TEXT("生气,就是拿别人的过错来惩罚自己。原谅别人,就是善待自己。生气,就是拿别人的过错来惩罚自己。原谅别人,就是善待自己。生气,就是拿别人的过错来惩罚自己。"),

TEXT("一个实现梦想的人,就是一个成功的人。"),

TEXT("大事坚持原则,小事学会变通。"),

TEXT("一个人有钱没钱不一定,但如果这个人没有了梦想,这个人穷定了。"),

TEXT("不要急于表态不急于表态或发表意见,可以使人对你揣摸猜测。谨慎的沉默是精明人的帮手。一旦表态,你的决定就容易受到批评和非议。"),

TEXT("平凡朴实的梦想,我们用那唯一的坚持信念去支撑那梦想。"),

TEXT("不要总谈论自己你若总是谈论自己,那么不是吹嘘自己,就是贬低自己;前者是虚荣的表现,后者是卑微的表现。"),

TEXT("千万不要抱怨,抱怨会使你丢丑。抱怨会使人对你傲慢无礼,并促使别人如你所抱怨的那么做。要赢得别人的帮助,最好的办法是表扬他人。"),

TEXT("凡事终了时务必小心谨慎,顺利抽身退出要比顺利地进入时更难。"),

TEXT("很难说什么是办不到的事情,因为昨天的梦想,可以是今天的希望,并且还可以成为明天的现实。"),

TEXT("当你再也没有什么可以失去的时候,就是你开始得到的时候。"),

TEXT("生命犹如一片绿叶,随着时间的流逝,慢慢变的枯黄,但他的叶脉还是那么清晰可见。"),

TEXT("我们可以躲开大家,却躲不开一只苍蝇。生活中使我们不快乐的常是一些芝麻小事。"),

TEXT("人生的意义不在于拿一手好牌,而在于打好一手坏牌。"),

TEXT("一个人想平庸,阻拦者很少;一个人想出众,阻拦者很多。不少平庸者与周围人关系融洽,不少出众者与周围人关系紧张。"),

TEXT("“危机”两个字,一个意味着危险,另外一个意味着机会,不要放弃任何一次努力。"),

TEXT("世上没有绝望的处境,只有对处境绝望的人。"),

TEXT("再长的路,一步步也能走完,再短的路,不迈开双脚也无法到达。"),

TEXT("有志者自有千计万计,无志者只感千难万难。"),

TEXT("成功与不成功之间有时距离很短只要后者再向前几步。"),

TEXT("世界会向那些有目标和远见的人让路。"),

TEXT("世界青睐有雄心壮志的人。成功所依靠的惟一条件就是思考。当你的思维以最高速度运转时,乐观欢快的情绪就会充斥全身。一个人最完美的作品都是在充满愉快、乐观、深情的状态下完成的。"),

TEXT("学会让自己安静,把思维沉浸下来,慢慢降低对事物的欲望。把自我经常归零,每天都是新的起点,没有年龄的限制,只要你对事物的欲望适当的降低,会赢得更多的求胜机会。"),

TEXT("杨澜:年轻时候应该能够作一些冒险。年轻时候最大的财富,不是你的青春,不是你的美貌,也不是你充沛的精力,而是你有犯错误的机会。"),

TEXT("没有十全十美的东西,没有十全十美的人,关键是清楚到底想要什么。得到想要的,肯定会失去另外一部分。如果什么都想要,只会什么都得不到。"),

TEXT("做自己的决定。然后准备好承担后果。从一开始就提醒自己,世上没有后悔药吃。"),

TEXT("在你内心深处,还有无穷的潜力,有一天当你回首看时,你就会知道这绝对是真的。"),

- 44 -

TEXT("不为模糊不清的未来担忧,只为清清楚楚的现在努力。"),

TEXT("没有一种不通过蔑视、忍受和奋斗就可以征服的命运。"),

TEXT("伟人之所以伟大,是因为他与别人共处逆境时,别人失去了信心,他却下决心实现自己的目标。"),

TEXT("世上没有绝望的处境,只有对处境绝望的人。"),

TEXT("当你感到悲哀痛苦时,最好是去学些什么东西。学习会使你永远立于不败之地。"),

TEXT("给自己定目标,一年,两年,五年,也许你出生不如别人好,通过努力,往往可以改变70%的命运。破罐子破摔只能和懦弱做朋友。"),

TEXT("知道自己要干什么,夜深人静,问问自己,将来的打算,并朝着那个方向去实现。而不是无所事事和做一些无谓的事。"),

TEXT("梦想无论怎么模糊,它总潜伏在我们心底,使我们的心境永远得不到宁静,直到梦想成为事实。"),

TEXT("梦是一种欲望,想是一种行动。梦想是梦与想的结晶。"),

TEXT("生气,就是拿别人的过错来惩罚自己。原谅别人,就是善待自己。"),

TEXT("一个实现梦想的人,就是一个成功的人。"),

TEXT("大事坚持原则,小事学会变通。"),

TEXT("一个人有钱没钱不一定,但如果这个人没有了梦想,这个人穷定了。"),

TEXT("不要急于表态不急于表态或发表意见,可以使人对你揣摸猜测。谨慎的沉默是精明人的帮手。一旦表态,你的决定就容易受到批评和非议。如果这些决定是以失败告终,你就会更加倒霉。"),

TEXT("平凡朴实的梦想,我们用那唯一的坚持信念去支撑那梦想。"),

TEXT("不要总谈论自己你若总是谈论自己,那么不是吹嘘自己,就是贬低自己;前者是虚荣的表现,后者是卑微的表现。"),

TEXT("千万不要抱怨,抱怨会使你丢丑。抱怨会使人对你傲慢无礼,并促使别人如你所抱怨的那么做。要赢得别人的帮助,最好的办法是表扬他人。"),

TEXT("凡事终了时务必小心谨慎,顺利抽身退出要比顺利地进入时更难。"),

TEXT("很难说什么是办不到的事情,因为昨天的梦想,可以是今天的希望,并且还可以成为明天的现实。"),

TEXT("当你再也没有什么可以失去的时候,就是你开始得到的时候。"),

TEXT("生命犹如一片绿叶,随着时间的流逝,慢慢变的枯黄,但他的叶脉还是那么清晰可见。"),

TEXT("我们可以躲开大家,却躲不开一只苍蝇。生活中使我们不快乐的常是一些芝麻小事。"),

TEXT("人生的意义不在于拿一手好牌,而在于打好一手坏牌。"),

TEXT("一个人想平庸,阻拦者很少;一个人想出众,阻拦者很多。不少平庸者与周围人关系融洽,不少出众者与周围人关系紧张。"),

TEXT("“危机”两个字,一个意味着危险,另外一个意味着机会,不要放弃任何一次努力。"),

TEXT("世上没有绝望的处境,只有对处境绝望的人。"),

TEXT("再长的路,一步步也能走完,再短的路,不迈开双脚也无法到达。"),

TEXT("有志者自有千计万计,无志者只感千难万难。"),

TEXT("成功与不成功之间有时距离很短只要后者再向前几步。"),

TEXT("世界会向那些有目标和远见的人让路。"),

TEXT("世界青睐有雄心壮志的人。成功所依靠的惟一条件就是思考。当你的思维以最高速度运转时,乐观欢快的情绪就会充斥全身。一个人最完美的作品都是在充满愉快、乐观、深情的状态下完成的。"),

TEXT("学会让自己安静,把思维沉浸下来,慢慢降低对事物的欲望。把自我经常归零,

- 45 -

每天都是新的起点,没有年龄的限制,只要你对事物的欲望适当的降低,会赢得更多的求胜机会。"),

TEXT("杨澜:年轻时候应该能够作一些冒险。年轻时候最大的财富,不是你的青春,不是你的美貌,也不是你充沛的精力,而是你有犯错误的机会。"),

TEXT("没有十全十美的东西,没有十全十美的人,关键是清楚到底想要什么。得到想要的,肯定会失去另外一部分。如果什么都想要,只会什么都得不到。"),

TEXT("做自己的决定。然后准备好承担后果。从一开始就提醒自己,世上没有后悔药吃。"),

TEXT("在你内心深处,还有无穷的潜力,有一天当你回首看时,你就会知道这绝对是真的。"),

TEXT("不为模糊不清的未来担忧,只为清清楚楚的现在努力。")

};

//计算statement所有句子中最长语句的长度

int GetMaxLength()

{

/*

*计算statement所有句子中最长语句的长度

*返回值: int GetMaxLength(void) -> int

*/

int maxLength = 0 ;

int i ;

for( i = 0; i < NUMLINES; i++ )

{

if( wcslen(statement[i]) > maxLength )

maxLength = wcslen(statement[i]) ;

}

return maxLength ;

}

在这个头文件中, 其中有两句是十分重要的, 一是计算总行数:

#define NUMLINES ( (int)(sizeof(statement) / sizeof(statement[0]) ) ) //计算总行数

另一个是计算最长串字符个数的函数GetMaxLength, 该函数的定义如下:

int GetMaxLength()

{

/* *计算statement所有句子中最长语句的长度

*返回值: int GetMaxLength(void) -> int

*/

int maxLength = 0 ;

int i ;

for( i = 0; i < NUMLINES; i++ )

{

if( wcslen(statement[i]) > maxLength )

maxLength = wcslen(statement[i]) ;

}

return maxLength ;

}

- 46 -

下面编写我们的源文件, LearnScroll.c, 先看一下完整的代码, 稍后我们详细解释,

代码如下:

#include

#include"text.h"

LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ; //声明窗口过程函数

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine,

int iCmdShow )

{

static TCHAR szAppName[] = TEXT("LearnScroll") ;

HWND hwnd ;

MSG msg ;

WNDCLASS wndclass ;

//窗口类成员属性

dProc = WndProc ;

= CS_HREDRAW | CS_VREDRAW ;

nce = hInstance ;

assName = szAppName ;

nuName = NULL ;

kground = (HBRUSH)GetStockObject(WHITE_BRUSH) ;

r = LoadCursor(NULL, IDI_APPLICATION) ;

= LoadIcon(NULL, IDC_ARROW) ;

xtra = 0 ;

xtra = 0 ;

//注册窗口类

if( !RegisterClass(&wndclass) )

{

MessageBox( NULL, TEXT("无法注册窗口类!"), TEXT("错误"), MB_OK |

MB_ICONERROR ) ;

return 0 ;

}

//创建窗口

hwnd = CreateWindow(

szAppName, TEXT("滚动条示例"),

WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,

CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT,

NULL, NULL, hInstance, NULL

) ;

//显示窗口

ShowWindow( hwnd, iCmdShow ) ;

UpdateWindow( hwnd ) ;

//获取、翻译、分发消息

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

{

TranslateMessage( &msg ) ;

DispatchMessage( &msg ) ;

}

return ;

}

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

{

static int cxChar, cxCaps, cyChar, cyClient, cxClient, iVscrollPos,

iHscrollPos ;

//cxChar:平均字符宽度; cxCaps: 大写字母平均宽度; cyChar: 字符高; cyClient、cxClient: 客户区y、x方向尺寸;

//iVscrollPos: 竖直方向滚动条滑块位置; iHscrollPos: 水平方向滚动条滑块位置

- 47 -

HDC hdc ;

RECT rect ; //记录客户区RECT结构

int i, x, y; //i循环控制, x记录水平方向坐标, y竖直方向坐标

PAINTSTRUCT ps ;

TEXTMETRIC tm ;

switch(message)

{

case WM_CREATE: //处理WM_CREATE消息

hdc = GetDC(hwnd) ;

GetTextMetrics( hdc, &tm ) ; //获取系统字体信息

cxChar = harWidth ; //获取平均宽度

cxCaps = (hAndFamily & 1 ? 3 : 2) * cxChar / 2 ;//大写字母平均宽度

cyChar = ht + rnalLeading ; //字符高度

ReleaseDC( hwnd, hdc );

SetScrollRange( hwnd, SB_VERT, 0, NUMLINES - 1, FALSE ) ; //设置竖直滚动条范围的最小值和最大值

SetScrollRange( hwnd, SB_HORZ, 0, GetMaxLength() - 1, FALSE ) ; //设置水平滚动条范围的最小值和最大值

SetScrollPos( hwnd, SB_VERT, iVscrollPos, TRUE ) ; //设置竖直滚动条中的滚动按钮的位置

SetScrollPos( hwnd, SB_HORZ, iHscrollPos, TRUE ) ; //设置水平定滚动条中的滚动按钮的位置

return 0 ;

case WM_SIZE: //处理WM_SIZE

GetClientRect( hwnd, &rect ) ;

cyClient = ; //得到客户区y方向尺寸

cxClient = ; //得到客户区x方向尺寸

return 0 ;

case WM_VSCROLL: //处理垂直滚动条消息

switch( LOWORD(wParam) )

{

case SB_LINEUP: //上翻一行

iVscrollPos -= 1 ;

break ;

case SB_LINEDOWN: //下翻一行

iVscrollPos += 1 ;

break ;

case SB_PAGEUP: //向上翻一整页

iVscrollPos -= cyClient / cyChar ;

break ;

case SB_PAGEDOWN: //向下翻一整页

iVscrollPos += cyClient / cyChar ;

break ;

case SB_THUMBPOSITION: //拖动滑块滑块被放下时

iVscrollPos = HIWORD(wParam) ;

break ;

default:

break;

}

iVscrollPos = max( 0, min(iVscrollPos, NUMLINES -1) ) ;

if( iVscrollPos != GetScrollPos(hwnd, SB_VERT) ) //当滑块位置改变时重置滑块位置

- 48 -

{

SetScrollPos( hwnd, SB_VERT, iVscrollPos, TRUE ) ;

InvalidateRect( hwnd, NULL, TRUE ) ; //使客户区无效等待重绘

}

return 0 ;

case WM_HSCROLL: //处理水平滚动条消息

switch( LOWORD(wParam) )

{

case SB_LINELEFT: //左翻一行

iHscrollPos -= 1 ;

break ;

case SB_LINERIGHT: //右翻一行

iHscrollPos += 1 ;

break ;

case SB_PAGELEFT: //左翻一页

iHscrollPos -= cxClient / cxCaps ;

break ;

case SB_PAGERIGHT: //右翻一页

iHscrollPos += cxClient / cxCaps ;

break ;

case SB_THUMBPOSITION: //拖动滑块滑块被放下时

iHscrollPos = HIWORD(wParam) ;

break ;

default:

break ;

}

iHscrollPos = max( 0, min( iHscrollPos, GetMaxLength() -1 ) ) ;

if( iHscrollPos != GetScrollPos( hwnd, SB_HORZ ) )

{

SetScrollPos( hwnd, SB_HORZ, iHscrollPos, TRUE ) ;

InvalidateRect( hwnd, NULL, TRUE ) ;

}

return 0 ;

case WM_PAINT: //处理WM_PAINT消息

hdc = BeginPaint( hwnd, &ps ) ;

for( i= 0; i < NUMLINES; i++ )

{

y = cyChar * ( i -iVscrollPos ) ;

x = cxCaps * ( 0 - iHscrollPos ) ;

TextOut( hdc, x, y, statement[i], lstrlen(statement[i]) ) ; //输出文字

}

EndPaint( hwnd, &ps ) ;

return 0 ;

case WM_DESTROY: //处理WM_DESTROY消息

PostQuitMessage( 0 ) ;

return 0 ;

}

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

}

编译运行, 看下成果:

- 49 -