电脑程序确定键被黑色战斗方块剧场双人键盘掩盖住怎么解决

我的电脑桌面上的图标有黑色方块是怎么回事?_百度知道
我的电脑桌面上的图标有黑色方块是怎么回事?
我的电脑桌面上的图标有黑色方块是怎么回事?
我有更好的答案
  1、我的电脑”处右击,在跳出来的菜单栏点击最下面的“属性”。  2、在“高级”后点击“性能”下方的“设置”按钮。  3、跳出的“系统属性”窗口上方点击“高级”。  4、接下来在跳出的“性能选项”窗口中选中“让Windows选择计算机的最佳设置(L)”,或者选中“调整为最佳外观(B)”,点击确定或应用即可消除阴影。  5、可以选择“自定义(C)”,只要在下面的“在桌面上为图标标签使用阴影”前打上勾即可,然后点击确定或应用。
采纳率:74%
来自团队:
解决方案1、双击打开“计算机”--“组织”--“文件夹和搜索选项”--“查看”--选中“显示隐藏的文件和文件夹”后点击确定,以便显示隐藏文件。2、双击打开“计算机”--“本地磁盘c”--“用户”--“用户名”(既以登陆系统的用户名命名的文件夹)--“appdate”--“local”--找到“iconcache”后将其删除并重启后正常。
本回答被提问者和网友采纳
为您推荐:
其他类似问题
电脑桌面的相关知识
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。电脑所有的图标都变成黑方块怎么解决_百度知道
电脑所有的图标都变成黑方块怎么解决
我有更好的答案
原因一:电脑中了病毒.可能是中威金病毒,装个诺顿杀毒软件更新到最新病毒库杀毒.杀毒后,重新启动系统后图标并未恢复正常.解决方法如下:按“开始\设置\控制面板\显示\效果”顺序打开“效果”标签,在“视觉效果”栏里随便选一个效果在前面打上钩,然后按“确定”,重新启动系统后可以看到图标已经恢复正常.原因二:可能是由于Windows文件ShellIconCache损坏了。Win 9x/NT通常会把要显示的图标存放在ShellIconCache文件中,这样每次显示快捷键的图标时都不用再去读快捷键而直接读取这个文件,可以加快显示图标的速度.如果该文件损坏,则所有快捷键的图标都显示不正常,新建立的快捷图标也受影响,但是“回收站”等图标不受影响,因为它们不是来自这个文件.解决这个问题的办法就是利用资源管理器在Windows目录找到这个文件,然后删除掉该文件,再重新启动计算机.需要说明的是,微软的有关资料在介绍如何删除该文件时,总是要求用户从安全模式启动,尽管在我们的实践中发现这通常是不需要的,但是可能对于某些特别的情况下,你需要从安全模式启动Windows.原因三.硬盘有问题.有坏道.
重启一下电脑
应用程序的图标关联出错了,你在图标上点右键-》属性—》自定义-》还原默认图标或更改图标,选择你要的图标以后确定就行了! 我的电脑 我的文档 网上邻居 宽带连接 这些这样改: 桌面右键-》属性-》桌面-》自定义桌面-》常规-》还原默认图标,这样就行了 OFFICE的图标建议你把它们删了,在安装目录里找到相应的程序,右键发送到桌面快捷方式就行了 !
显卡驱动安装未正确,试重装一下驱动。在设备管理器那。
其他1条回答
为您推荐:
其他类似问题
图标的相关知识
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。如何开发一个苹果手机App:俄罗斯方块游戏如何开发一个苹果手机App:俄罗斯方块游戏零基础学编程百家号俄罗斯方块游戏,是风靡世界几十年的经典游戏,相信绝大多数人都玩过这个游戏,那么它是怎样编写出来的,我们如何才能自己编写一个这样的游戏呢?游戏开发步骤要编写一个游戏,基本按照以下一些步骤进行:1.游戏规则定义游戏规则决定了这个游戏怎么玩,好不好玩,也就决定了编写程序实现的目标。不同的需求导致不同的规则,不同的规则就需要不同的方法去实现。例如,俄罗斯方块是由哪些类型方块组成,棋盘布局是9*18还是10*20,是否允许旋转,消掉一行获得的分数和获得多行获得的分数如何进行累加,等等。2.选择游戏运行平台和界面设计在很多种平台上都可以玩游戏,常见的比如电脑pc或者笔记本上面直接运行游戏exe程序,或者在电脑浏览器中运行flash游戏,或者手机上运行游戏App,或者在特殊游戏机比如xbox等上直接运行游戏程序,或者在VR设备上运行游戏程序。不同的平台,需要不同的实现方式,也就需要用不同的程序或开发语言开发工具来实现。例如,在网页上开发游戏,可以用html+css+js实现,也可以用flash实现;在手机上开发游戏,可以用java语言在android系统中实现,也可以用objective-c在ios系统上实现;在电脑pc机上可以用c语言在windows系统中实现。界面设计就是根据游戏规则决定人机交互界面,决定了输入和输出的方式。例如,俄罗斯方块可以设计成左边是棋盘,右侧上面是分数,下一个方块,还可以放一个重新来一句按钮,或者暂停按钮等等。方块设计成纯一种颜色块,还是多种颜色块,或者是方的还是圆的,或者是卡通图案,这些就是界面设计。3.考虑编程思路和算法设计如果决定了在哪种平台以及用何种开发语言来编程实现游戏,则要考虑编程的思路,数据的存储,以及具体的算法。例如,俄罗斯方块根据棋盘大小是用二维数组来实现,还是用一维数组来实现,还是用字典表来实现一局游戏是定时计算,还是用一个循环来处理如何保存下一个类型,如何计算分数,如何判断一个方块是否落地不能再移动,如何判断一行是否已经被填满,如何判断游戏已经结束4.按照前面设计的思路编写代码实现考虑好编程思路和算法设计之后,可以按照设想进行实际的编程开发不停的验证思路,通过开发中的实际情况,可能对编程思路进行修改调整,直到完成整个游戏功能代码就是一行一行的编写出来,直到成为一个可以运行的游戏程序5.游戏测试测试就是把自己当成实际用户来运行游戏程序,找到游戏可能存在的问题,避免程序没有按照预期执行,防止程序崩溃死机等情况,验证游戏确实按照游戏规则能完整可靠的运行,这个开发人员可以找其他人进行测试,防止自己存在思维盲区。6.游戏发布游戏测试完成之后,就可以将游戏发布到相应的渠道或者平台,让更多的人可以玩到这个游戏。例如,苹果手机App可以发布到苹果的AppStore,安卓手机App可以发布到各大应用市场。最简单的发布就是直接发送给自己的朋友,让他们大吃一惊。游戏规则定义俄罗斯方块游戏有如下规则:棋盘由宽度为10格高度为20格的方块组成方块共有7种形态:长条形1个,正方形1个,T型1个,L型2个,S型2个方块出现在最上方正中间,同时知道下一个方块是什么方块往下掉落,每隔1秒下落一行方块下落中如果碰到有方块阻挡不能下落则停止下落方块停止下落后,判断是否有整行都被方块填满,如果有,则整行消失,上面的全部行整体下落一行统计消掉的总行数方块在下落过程中,可以按向下方向键让方块直接掉落到直到停止位置可以按向左或者向右方向键让方块进行旋转,每按键一次旋转90度如果没有足够的空间让方块落下则游戏结束游戏运行平台和界面设计如果以最流行的苹果手机作为游戏运行环境,则需要开发一个苹果手机App苹果手机使用的是iOS操作系统,开发环境需要Mac电脑和开发工具软件Xcode开发语言可以使用objective-C,开发游戏可以使开发用工具包cocos2d如果需要发布到苹果AppStore,则需要一个开发者账号,以及一年99美金界面设计:程序启动之后进入主界面,只能竖屏显示游戏主界面分为左右部分左边为棋盘:显示整个棋盘,正方形的格子组成,宽10格高20格按照苹果手机尺寸320点*480点来设计,20格高度为480点,则宽度10格占用240点因此左边棋盘占用240点,右侧剩余80点右边分为上部和下部上部为状态栏靠上显示:最上方显示显示当前消掉的行数,下面显示方块类型小图标下部为按钮栏靠下显示:从上往下显示3个按钮,排行榜/关于/重新开始大概的界面设计如下示意图:界面运行逻辑:点击排行榜按钮进入一个新页面,上面显示消掉的行数最多的8个行数数字,下面是一个返回按钮点击关于按钮进入一个新页面,上面显示一张说明图片,下面是一个返回按钮点击重新开始按钮,将当前棋盘清空并重新开始一局游戏,游戏启动游戏启动之后,第一个方块从最上面中间开始往下掉落,每秒下落一行同时,上方的状态栏,显示下一个即将出现的方块类型小图标方块下落过程中,在屏幕上单指左滑可以向左逆时针旋转当前方块,或者在屏幕上单指右滑可以向右顺时针旋转当前方块还可以在屏幕上单指下滑可以让当前方块直接掉落到底部方块如果不能再往下移动一行的时候,则方块停止,同时开始判断是否能够消掉某些行如果能够消掉某些行,则统计消掉的行数,增加到上方状态栏显示消掉的数字上,刷新显示消掉的行从屏幕上消除,同时上方的所有行往下整体移动加入棋盘最上方中间的供方块出现的地方已经被方块占住了,则认为这一局游戏结束游戏结束则弹出一个窗口显示游戏结束信息,显示总共消掉了多少行,然后将该记录保存到数据库中弹出窗口上有返回按钮,点击返回按钮,弹出窗口关闭,回到游戏主界面,主界面停止游戏,点击重新开始按钮开始新一局编程思路和算法设计数据存储:考虑用二维列表来存储棋盘上的每一个格子[[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],......[0,0,0,0,0,0,0,0,0,0]]二维列表里面,第一层是放总计20行,第二层里面是放每一行的10个格子如果格子里面有方块,则存放数字1,如果没有方块则存放数字0判断一行如果全部是1,则表示该行填满了方格,需要消掉使用下列数字来对7种方块进行区分,存储2个变量,当前方块和下一个方块1:长条形 2:正方形 3:T型 4:L型向左 5:L型向右 6:S型左上右下 7:S型右上左下生成下一个方块则使用随机函数生成1到7当中的随机数然后不同方块如果进行了旋转会出现不同的状态,定义旋转状态如下:11:竖立长条 12:横排长条21:正方形31:T型尖头朝下 32:T型尖头朝左 33:T型尖头朝上 34:T型尖头朝右41:L型短头向左长头向上 42:L型短头向上长头向右 43:L型短头向右长头向下 44:L型短头向下长头向左51:L型短头向右长头向上 52:L型短头向下长头向右 53:L型短头向左长头向下 54:L型短头向上长头向左61:S型左上右下竖立 62:S型左上右下横排71:S型右上左下竖立 72:S型右上左下横排使用2个变量x,y存储当前方块的左上角的方块的行号和列号使用变量存储当前已经消除的行数算法设计:点击重新开始按钮后,主程序启动清空整个棋盘,对二维列表全部数字清零然后生成第1个随机数,赋值给当前方块变量然后生成第2个随机数,赋值给下一个方块变量刷新显示页面进入定时处理函数,定时间隔时间为1秒:定时处理函数:判断当前方块如果可以往下移动,则向下移动,修改二维列表变量如果不可以下移了,则判断是否可以消除某行,如果消除了某行则上方的所有行的值整体下移一行如果不可以下移也不可以消除了,则将下一个方块的数值赋值给当前方块,随机数生成下一个方块然后判断当前方块是否可以放入棋盘最上方的中间位置,如果可以放入则修改二维列表变量,如果不能放入,则游戏结束判断当前方块是否可以下移函数:根据当前方块类型变量,以及方块当前旋转状态,结合二维列表变量判断,举例:如果是11竖立长条形,则根据当前方块左上角的行号和列号,可以知道最下方的坐标位置比如x=0行,y=5列,则最下方的坐标为(3,5),那么只要看二维列表的(4,5)如果为1则不可下移,否则可以下移比如如果是31:T型尖头朝下x=0,y=4,那么下方可能有3个方块可能碰到阻碍,分别是(0,4)/(1,5)/(0,6)那么只要看二维列表的(1,4)/(2,5)/(1,6)只要有一个为1则不可下移,否则可以下移判断是否可以消除某行函数:对二维列表进行循环,如果一行当中的所有值都为1,则可以消除直接将所有上面的行的值往下复制,最上面一行的值全部赋值为0,同时总的消除行数变量加1判断当前方块是否可以放入棋盘最上方的中间位置函数:根据当前方块类型变量,以及方块当前旋转状态,结合二维列表变量判断,举例:如果是1长条形,默认为11树立长条形则需要判断(0,5)/(1,5)/(2,5)/(3,5)这4个坐标在二维列表中是否为1,只要有1个位置为1则不可放入比如如果是3T型尖头朝下则需要判断(0,4)/(0,5)/(0,6)/(1,5)这4个坐标旋转处理:总共允许有3种滑动手指操作,向下/向左/向右向下滑动手指,表示将当前方块直接掉落到最下方直到碰到阻碍停住向左滑动手指,表示将当前方块进行逆时针旋转向右滑动手指,表示将当前方块进行顺时针旋转向下掉落处理:重复调用前面定义的判断当前方块是否可以下移函数从第一行循环到最后一行坐标,即可得出最多可以掉落到哪一行逆时针旋转处理:根据当前方块类型变量,以及方块当前旋转状态,结合二维列表变量判断,举例:如果是11竖立长条形,旋转中心点取从上往下第2个方块那么根据旋转后需要占用的位置,需要判断(1,4)/(1,6)/(1,7)这3个位置在二维列表中的值必须是0才可以旋转旋转完成后应该左上角坐标应该从(0,5)变成(1,4),方块当前旋转状态从11变成12请看下面的示意图:那么,顺时针旋转和这个类似,只是选择后的左上角左边变量变化,以及方块当前旋转状态值变化具体代码实现由于篇幅所限,这里只能大概描述核心的一些代码实现方式开发准备:首先取注册一个AppleID然后在Mac电脑上安装好Xcode开发软件引入相关工具开发包:导入cocos2d开发工具包以及需要的一些系统Frameworks数据操作类:AllData.h#define SIZE 24#define WIDTH 320#define HEIGHT 480@interface AllData : NSObject@@@proper@@@p@property (nonatomic,retain) NSMutableArray *//取得当前类的实例+(AllData *) sharedAllD//初始化棋盘-(void)initAllD//取得下一个方块-(int) getNextV//逆时针旋转当前方块-(void)changeL//顺时针旋转当前方块-(void)changeR//判断是否可以下移当前方块-(boolean)canMoveD//处理消除慢行操作-(void)removeL@end存储控制类:DBUtil.h@interface DBUtil : NSObject+ (NSString *)dataFileP+ (void)initDataB//存储一局游戏消除行数数字+(void)insertOneData:(int)//取得最大的8个记录数:消除行数+ (NSMutableArray *)getListDataFromDb;@end方块绘画类:#import &CommonUtil.h&#import &AllData.h&@implementation CommonUtil//绘画棋盘当中的一个格子+ (void) drawOneNumber:(int)number pos:(CGPoint)pos layer:(CCLayer*)layer {CCSpriteBatchNode *numbatch = [CCSpriteBatchNode batchNodeWithFile:[NSString stringWithFormat:@&num_%d.png&,number] capacity:15];numbatch.anchorPoint = CGPointZ[numbatch setPosition:pos];[layer addChild:numbatch];CCSprite *sprite1 = [CCSprite spriteWithTexture:numbatch.texture rect:CGRectMake(0, 0, SIZE-2, SIZE-2)];sprite1.position = ccp(0,0);sprite1.anchorPoint = CGPointZ[numbatch addChild:sprite1];}// 绘画整个棋盘......启动进入主界面:@implementation AppDelegate- (void) applicationDidFinishLaunching:(UIApplication*)application{......[[AllData sharedAllData] initAllData];[DBUtil initDataBase];[[CCDirector sharedDirector] runWithScene: [MainLayer scene]];.......状态栏和工具栏:@interface ToolLayer : CCLayer {}@implementation ToolLayer- (void) drawTool {[self removeAllChildrenWithCleanup:YES];//画底部背景CCSprite *bg = [CCSprite spriteWithFile:@&tool_bootom_back.png&];bg.anchorPoint = CGPointZ[self addChild:bg z:0 tag:0];//排行按钮CCMenuItem *gold = [CCMenuItemImage itemFromNormalImage:@&gold.png& selectedImage:@&gold_pressed.png& target:self selector:@selector(gold:)];//关于按钮CCMenuItem *about = [CCMenuItemImage itemFromNormalImage:@&about.png& selectedImage:@&about_pressed.png& target:self selector:@selector(about:)];//重新开始按钮CCMenuItem *restart = [CCMenuItemImage itemFromNormalImage:@&restart.png& selectedImage:@&restart_pressed.png& target:self selector:@selector(restart:)];......// 绘画消除行数的图标// 绘画消除行数的数字// 绘画下一个方块的图标- (void) restart:(id) sender{[[AllData sharedAllData] initAllData];MapLayer *mapLayer = (MapLayer *)[[CCDirector sharedDirector].runningScene getChildByTag:0];[mapLayer startGame];[self drawTool];}棋盘滑动手势控制方法:- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {NSSet *allTouches = [event allTouches];switch ([allTouches count]){case 1:{UITouch *touch1 = [[allTouches allObjects] objectAtIndex:0];single = [touch1 locationInView:[touch1 view]];}default:}}- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {NSSet *allTouches = [event allTouches];switch ([allTouches count]){case 1:{UITouch *touch1 = [[allTouches allObjects] objectAtIndex:0];CGPoint singleend = [touch1 locationInView:[touch1 view]];float x = single.x - singleend.x;float y = single.y - singleend.y;if (x*x & y*y && x*x & 2500 && x & 0) {//向左横向滑动超过50NSLog(@&左&);[self changeLeft];}else if (x*x & y*y && x*x & 2500 && x & 0) {//向右横向滑动超过50NSLog(@&右&);[self changeRight];}else if (x*x & y*y && y*y & 2500 && y & 0) {//向下纵向滑动超过50NSLog(@&下&);[self moveDown];}}default:}}主界面程序:@interface MapLayer : CCLayer {}@implementation MapLayer-(void) startGame{[self removeAllChildrenWithCleanup:YES];int toppos = 0;for (NSMutableArray *onerow in [AllData sharedAllData].numberdatas) {int leftpos = 0;toppos = toppos + SIZE + 1;for (NSNumber *onepos in onerow) {leftpos = leftpos + SIZE + 1;[CommonUtil drawOneNumber:[onepos intValue] pos:CGPointMake(leftpos, toppos) layer:self];}}// 设置定时器处理函数,定时间隔时间1秒......}//定时器处理函数//判断当前方块如果可以往下移动,则向下移动,修改二维列表变量//如果不可以下移了,则判断是否可以消除某行,如果消除了某行则上方的所有行的值整体下移一行//如果不可以下移也不可以消除了,则将下一个方块的数值赋值给当前方块,随机数生成下一个方块//然后判断当前方块是否可以放入棋盘最上方的中间位置,如果可以放入则修改二维列表变量,如果不能放入,则游戏结束......具体代码这里不贴了,有了详细的算法程序设计和数据存储类型,要实现出来并不是太难,只是需要耐心细心而已。游戏测试游戏的主体程序完成后,就可以一边测试一边修改测试的时候要注意测试各种边界情况,例如将当前方块移动到最左边,看看碰到边界的时候会不会出错将当前方块卡入一个正好插入的空槽形状中,看程序是不是会出错等待当前方块掉入一个空槽,然后立刻旋转,理论上应该不能旋转了,测试看看程序会不会出错测试一次性消掉4行,看看程序是不是会出错如果自己测试的差不多了,就可以将程序打包发送给朋友测试有时候,开发的人很难测试自己编写的程序,但是反而让别人可以测试出来很多问题游戏发布下面介绍一下大概的发布流程,让大家有个初步印象,详细的发布流程网上可以搜索到很多游戏测试到基本没有错误之后,就可以将游戏发布到苹果的AppStore了发布App需要购买苹果的开发者账号,一年是99美金,目前用信用卡支付也很方便然后登陆https://developer.apple.com,进行一些证书的设置生成好证书之后,将证书下载到Mac电脑中导入到Xcode之中然后使用Xcode进行编译和打包好一个ipa文件然后登陆https://itunesconnect.apple.com创建一个App,填写一些介绍信息,需要一些App的截图然后回到Xcode使用Application Loader这个工具软件将ipa文件上传到itunesconnect当中然后回到itunesconnect网站上提交App然后就是等待苹果AppStore的审核,一般7-10个工作日可以完成审核完成审核之后,你的App就会出现在苹果手机的AppStore里面了,就可以搜索到了然后就可以告诉你的朋友们,让他们大吃一惊吧。本文仅代表作者观点,不代表百度立场。系作者授权百家号发表,未经许可不得转载。零基础学编程百家号最近更新:简介:每天半小时零基础学编程。作者最新文章相关文章当前位置:&&
本页文章导读:
&&&&?Windows程式开发设计指南-输出文字 &&&&&& Windows程式开发设计指南--输出文字4. 输出文字
在前一章,您看到了一个简单的Windows 98程式,它在视窗中央,或者更准确地说,在显示区域中央显示一行文字。正如我们学到的,显示区域是整.........&&&&? Windows程式开发设计指南-开始 &&&&&& Windows程式开发设计指南--开始1. 开始
本书介绍了在Microsoft Windows 98、Microsoft Windows NT 4.0和Windows NT 5.0下程式写作的方法。这些程式用C语言编写并使用原始的Windows Application Programming
Interface(AP.........&&&&? Windows程式开发设计指南-Unicode简介&&&&&& Windows程式开发设计指南--Unicode简介2. Unicode简介
在第一章中,我已经预告,C语言中在Microsoft Windows程式设计中扮演著重要角色的任何部分都会讲述到,您也许在传统文字模式程式设计中还尚.........
[1]Windows程式开发设计指南-输出文字
&&&&来源:&互联网& 发布时间:&
Windows程式开发设计指南--输出文字4. 输出文字
在前一章,您看到了一个简单的Windows 98程式,它在视窗中央,或者更准确地说,在显示区域中央显示一行文字。正如我们学到的,显示区域是整个应用程式视窗中未被标题列、视窗边框,以及可选的功能表列、工具列、状态列和卷动列占据的部分。简而言之,显示区域是视窗中可以由程式任意书写和传递视觉资讯的部分。
对於程式的显示区域,您几乎可以为所欲为,只不过您不能假定视窗大小是某一特定尺寸,或者在程式执行时其大小会保持不变。如果您不熟悉图形视窗环境的程式设计,这些限制可能会使您感到惊讶:不能再假设萤幕上的一行文字一定有80个字元了。您的程式必须与其他Windows程式共用视讯显示器。Windows使用者控制程式视窗在萤幕上显示的方式。尽管可以建立固定大小的视窗(这对於计算器之类的应用是合理的),但在大多数情况下,使用者应该能够改变应用程式视窗的大小。您的程式必须能够接受指定给它的大小,并且合理地利用这一空间。
这有两种可能的情况。一种可能是,程式只有仅能显示「hello」的显示区域;还有另一种可能,即程式在一个大萤幕、高解析度的系统上执行,其显示区域大得足以显示两整页文字。灵活地处理这两种极端是Windows程式设计的要点之一。
这一章,我们将讲述程式在显示区域显示资讯的方式,但比上一章说明的显示方式更加复杂。当程式在显示区域显示文字或图形时,它经常要「绘制」它的显示区域。本章著重讲述绘制的方法。
尽管Windows为显示图形提供了强大的图形装置介面(GDI)函式,但在这一章中,我只介绍简单文字行的显示。我也将忽略Windows能够使用的不同字体外形及字体大小,仅使用Windows的内定系统字体。这看起来似乎是一种限制,其实不然,本章涉及和解决的问题适用於所有Windows程式设计。在混合显示文字和图形时,Windows内定字体的字元大小通常决定了图形的尺寸。
本章表面上是讨论绘图的方法,实际上是讨论与装置无关的程式设计基础。Windows程式只能对显示区域大小甚至字元的大小做很少的假定,相反地,必须使用Windows提供的功能来取得关於程式执行环境的资讯。
绘制和更新
在文字模式环境下,程式可以在显示器的任意部分输出,程式输出到萤幕上的内容会停留在原处,不会神秘地消失。因此,程式可以丢掉重新生成萤幕显示时所需的资讯。
在Windows中,只能在视窗的显示区域绘制文字和图形,而且不能确保在显示区域内显示的内容会一直保留到程式下一次有意地改写它时还保留在那里。例如,使用者可能会在萤幕上移动另一个程式的视窗,这样就可能覆盖您的应用程式视窗的一部分。Windows不会保存您的视窗中被其他程式覆盖的区域,当程式移开後,Windows会要求您的程式更新显示区域的这个部分。
Windows是一个讯息驱动系统。它通过把讯息投入应用程式讯息伫列中或者把讯息发送给合适的视窗讯息处理程式,将发生的各种事件通知给应用程式。Windows通过发送WM_PAINT讯息通知视窗讯息处理程式,视窗的部分显示区域需要绘制。
WM_PAINT讯息
大多数Windows程式在WinMain中进入讯息回圈之前的初始化期间都要呼叫函式UpdateWindow。Windows利用这个机会给视窗讯息处理程式发送第一个WM_PAINT讯息。这个讯息通知视窗讯息处理程式:必须绘制显示区域。此後,视窗讯息处理程式应在任何时刻都准备好处理其他WM_PAINT讯息,必要的话,甚至重新绘制视窗的整个显示区域。在发生下面几种事件之一时,视窗讯息处理程式会接收到一个WM_PAINT讯息:
在使用者移动视窗或显示视窗时,视窗中先前被隐藏的区域重新可见。
  使用者改变视窗的大小(如果视窗类别样式有著CS_HREDRAW和CS_VREDRAW位元旗标的设定)。
  程式使用ScrollWindow或ScrollDC函式滚动显示区域的一部分。
  程式使用InvalidateRect或InvalidateRgn函式刻意产生WM_PAINT讯息。
在某些情况下,显示区域的一部分被临时覆盖,Windows试图保存一个显示区域,并在以後恢复它,但这不一定能成功。在以下情况下,Windows可能发送WM_PAINT讯息:
Windows擦除覆盖了部分视窗的对话方块或讯息方块。
  功能表下拉出来,然後被释放。
  显示工具提示讯息。
在某些情况下,Windows总是保存它所覆盖的显示区域,然後恢复它。这些情况是:
滑鼠游标穿越显示区域。
  图示拖过显示区域。
处理WM_PAINT讯息要求程式写作者改变自己向显示器输出的思维方式。程式应该组织成可以保留绘制显示区域需要的所有资讯,并且仅当「回应要求」-即Windows给视窗讯息处理程式发送WM_PAINT讯息时才进行绘制。如果程式在其他时间需要更新其显示区域,它可以强制Windows产生一个WM_PAINT讯息。这看来似乎是在萤幕上显示内容的一种舍近求远的方法。但您的程式结构可以从中受益。
有效矩形和无效矩形
尽管视窗讯息处理程式一旦接收到WM_PAINT讯息之後,就准备更新整个显示区域,但它经常只需要更新一个较小的区域(最常见的是显示区域中的矩形区域)。显然,当对话方块覆盖了部分显示区域时,情况即是如此。在擦除对话方块之後,需要重画的只是先前被对话方块遮住的矩形区域。
这个区域称为「无效区域」或「更新区域」。正是显示区域内无效区域的存在,才会让Windows将一个WM_PAINT讯息放在应用程式的讯息伫列中。只有在显示区域的某一部分失效时,视窗才会接受WM_PAINT讯息。
Windows内部为每个视窗保存一个「绘图资讯结构」,这个结构包含了包围无效区域的最小矩形的座标以及其他资讯,这个矩形就叫做「无效矩形」,有时也称为「无效区域」。如果在视窗讯息处理程式处理WM_PAINT讯息之前显示区域中的另一个区域变为无效,则Windows计算出一个包围两个区域的新的无效区域(以及一个新的无效矩形),并将这种变化後的资讯放在绘制资讯结构中。Windows不会将多个WM_PAINT讯息都放在讯息伫列中。
视窗讯息处理程式可以通过呼叫InvalidateRect使显示区域内的矩形无效。如果讯息伫列中已经包含一个WM_PAINT讯息,Windows将计算出新的无效矩形。否则,它将一个新的WM_PAINT讯息放入讯息伫列中。在接收到WM_PAINT讯息时,视窗讯息处理程式可以取得无效矩形的座标(我们马上就会看到这一点)。通过呼叫GetUpdateRect,可以在任何时候取得这些座标。
在处理WM_PAINT讯息处理期间,视窗讯息处理程式在呼叫了BeginPaint之後,整个显示区域即变为有效。程式也可以通过呼叫ValidateRect函式使显示区域内的任意矩形区域变为有效。如果这呼叫具有令整个无效区域变为有效的效果,则目前伫列中的任何WM_PAINT讯息都将被删除。
要在视窗的显示区域绘图,可以使用Windows的图形装置介面(GDI)函式。Windows提供了几个GDI函式,用於将字串输出到视窗的显示区域内。我们已经在上一章看过DrawText函式,但是目前使用最为普遍的文字输出函式是TextOut。该函式的格式如下:
TextOut (hdc, x, y, psText, iLength) ;
TextOut向视窗的显示区域写入字串。psText参数是指向字串的指标,iLength是字串的长度。x和y参数定义了字串在显示区域的开始位置(不久会讲述关於它们的详细情况)。hdc参数是「装置内容代号」,它是GDI的重要部分。实际上,每个GDI函式都需要将这个代号作为函式的第一个参数。
读者可能还记得,代号只不过是一个数值,Windows以它在内部使用物件。程式写作者从Windows取得代号,然後在其他函式中使用该代号。装置内容代号是GDI函式的视窗「通行证」,有了这种装置内容代号,程式写作者就能自如地在显示区域上绘图,使图形如自己所愿地变得好看或者难看。
装置内容(简称为「DC」)实际上是GDI内部保存的资料结构。装置内容与特定的显示设备(如视讯显示器或印表机)相关。对於视讯显示器,装置内容总是与显示器上的特定视窗相关。
装置内容中的有些值是图形「属性」,这些属性定义了GDI绘图函式工作的细节。例如,对於TextOut,装置内容的属性确定了文字的颜色、文字的背景色、x座标和y座标映射到视窗的显示区域的方式,以及显示文字时Windows使用的字体。
当程式需要绘图时,它必须先取得装置内容代号。在取得了该代号後,Windows用内定的属性值填入内部装置内容结构。在後面的章节中您会看到,可以通过呼叫不同的GDI函式改变这些预设值。利用其他的GDI函式可以取得这些属性的目前值。当然,还有其他的GDI函式能够在视窗的显示区域真正地绘图。
当程式在显示区域绘图完毕後,它必须释放装置内容代号。代号被程式释放後就不再有效,且不能再被使用。程式必须在处理单个讯息处理期间取得和释放代号。除了呼叫CreateDC(函式,在本章暂不讲述)建立的装置内容之外,程式不能在两个讯息之间保存其他装置内容代号。
Windows应用程式一般使用两种方法来取得装置内容代号,以备在萤幕上绘图。
取得装置内容代号:方法一
在处理WM_PAINT讯息时,使用这种方法。它涉及BeginPaint和EndPaint两个函式,这两个函式需要视窗代号(作为参数传给视窗讯息处理程式)和PAINTSTRUCT结构的变数(在WINUSER.H表头档案中定义)的地址为参数。Windows程式写作者通常把这一结构变数命名为ps并且在视窗讯息处理程式中定义它:
PAINTSTRUCT
在处理WM_PAINT讯息时,视窗讯息处理程式首先呼叫BeginPaint。BeginPaint函式一般在准备绘制时导致无效区域的背景被擦除。该函式也填入ps结构的栏位。BeginPaint传回的值是装置内容代号,这一传回值通常被保存在叫做hdc的变数中。它在视窗讯息处理程式中的定义如下:
HDC资料型态定义为32位元的无正负号整数。然後,程式就可以使用需要装置内容代号的TextOut等GDI函式。呼叫EndPaint即可释放装置内容代号。
一般地,处理WM_PAINT讯息的形式如下:
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
使用GDI函式
EndPaint (hwnd, &ps) ;
return 0 ;
在处理WM_PAINT讯息时,必须成对地呼叫BeginPaint和EndPaint。如果视窗讯息处理程式不处理WM_PAINT讯息,则它必须将WM_PAINT讯息传递给Windows中DefWindowProc(内定视窗讯息处理程式)。DefWindowProc以下列代码处理WM_PAINT讯息:
case WM_PAINT:
BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;
return 0 ;
这两个BeginPaint和EndPaint呼叫之间中没有任何叙述,仅仅使先前无效区域变为有效。但以下方法是错误的:
case WM_PAINT:
return 0 ;
// WRONG !!!
Windows将一个WM_PAINT讯息放到讯息伫列中,是因为显示区域的一部分无效。如果不呼叫BeginPaint和EndPaint(或者ValidateRect),则Windows不会使该区域变为有效。相反,Windows将发送另一个WM_PAINT讯息,且一直发送下去。
绘图资讯结构
前面提到过,Windows为每个视窗保存一个「绘图资讯结构」,这就是PAINTSTRUCT,定义如下:
typedef struct tagPAINTSTRUCT
rgbReserved[32] ;
} PAINTSTRUCT ;
在程式呼叫BeginPaint时,Windows会适当填入该结构的各个栏位值。使用者程式只使用前三个栏位,其他栏位由Windows内部使用。hdc栏位是装置内容代号。在旧版本的Windows中,BeginPaint的传回值也曾是这个装置内容代号。在大多数情况下, fErase被标志为FALSE(0),这意味著Windows已经擦除了无效矩形的背景。这最早在BeginPaint函式中发生(如果要在视窗讯息处理程式中自己定义一些背景擦除行为,可以自行处理WM_ERASEBKGND讯息)。Windows使用WNDCLASS结构的hbrBackground栏位指定的画刷来擦除背景,这个WNDCLASS结构是程式在WinMain初始化期间登录视窗类别时使用的。许多Windows程式使用白色画刷。以下叙述设定视窗类别结构栏位值:
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
不过,如果程式通过呼叫Windows函式InvalidateRect使显示区域中的矩形失效,则该函式的最後一个参数会指定是否擦除背景。如果这个参数为FALSE(即0),则Windows将不会擦除背景,并且在呼叫完BeginPaint後PAINTSTRUCT结构的fErase栏位将为TRUE(非零)。
PAINTSTRUCT结构的rcPaint栏位是RECT型态的结构。您已经在第三章中看到,RECT结构定义了一个矩形,其四个栏位为left、top、right和bottom。PAINTSTRUCT结构的rcPaint栏位定义了无效矩形的边界,如图4-1所示。这些值均以图素为单位,并相对於显示区域的左上角。无效矩形是应该重画的区域。
图4-1 无效矩形的边界
PAINTSTRUCT中的rcPaint矩形不仅是无效矩形,它还是一个「剪取」矩形。这意味著Windows将绘图操作限制在剪取矩形内(更确切地说,如果无效矩形区域不为矩形,则Windows将绘图操作限制在这个区域内)。
在处理WM_PAINT讯息时,为了在更新的矩形外绘图,可以使用如下呼叫:
InvalidateRect (hwnd, NULL, TRUE) ;
该呼叫在BeginPaint呼叫之前进行,它使整个显示区域变为无效,并擦除背景。但是,如果最後一个参数等於FALSE,则不擦除背景,原有的东西将保留在原处。
通常这是Windows程式在无论何时收到WM_PAINT讯息而不考虑rcPaint结构的情况下简单地重画整个显示区域最方便的方法。例如,如果在显示区域的显示输出中包括了一个圆,但是只有圆的一部分落到了无效矩形中,它就使仅绘制圆的无效部分变得没有意义。这需要画整个圆。在您使用从BeginPaint传回的装置内容代号时,Windows不会绘制rcPaint矩形外的任何部分。
在第三章的HELLOWIN程式中,我们并不关心处理WM_PAINT讯息时的无效矩形。如果文字显示区域恰巧在无效矩形内,则由DrawText恢复之。否则,在处理DrawText呼叫的某个时刻,Windows会确定它无须向显示器上输出。不过,这一决定需要时间。关心程式性能和速度的程式写作者希望在处理WM_PAINT期间使用无效矩形范围,以避免不必要的GDI呼叫。如果绘制时需要存取例如点阵图这样的磁片档案,则这就显得尤其重要。
取得装置内容代号:方法二
虽然最好是在处理WM_PAINT讯息处理期间更新整个显示区域,但是您也会发现在处理非WM_PAINT讯息处理期间绘制显示区域的某个部分也是非常有用的。或者您需要将装置内容代号用於其他目的,如取得装置内容的资讯。
要得到视窗显示区域的装置内容代号,可以呼叫GetDC来取得代号,在使用完後呼叫ReleaseDC:
hdc = GetDC (hwnd) ;
使用GDI函式
ReleaseDC (hwnd, hdc) ;
与BeginPaint和EndPaint一样,GetDC和ReleaseDC函式必须成对地使用。如果在处理某讯息时呼叫GetDC,则必须在退出视窗讯息处理程式之前呼叫ReleaseDC。不要在一个讯息中呼叫GetDC却在另一个讯息呼叫ReleaseDC。
与从BeginPaint传回装置内容代号不同,GetDC传回的装置内容代号具有一个剪取矩形,它等於整个显示区域。可以在显示区域的某一部分绘图,而不只是在无效矩形上绘图(如果确实存在无效矩形)。与BeginPaint不同,GetDC不会使任何无效区域变为有效。如果需要使整个显示区域有效,可以呼叫
ValidateRect (hwnd, NULL) ;
一般可以呼叫GetDC和ReleaseDC来对键盘讯息(如在字处理程式中)和滑鼠讯息(如在画图程式中)作出反应。此时,程式可以立刻根据使用者的键盘或滑鼠输入来更新显示区域,而不需要考虑为了视窗的无效区域而使用WM_PAINT讯息。不过,一旦确实收到了WM_PAINT讯息,程式就必须要收集足够的资讯後才能更新显示。
与GetDC相似的函式是GetWindowDC。GetDC传回用於写入视窗显示区域的装置内容代号,而GetWindowDC传回写入整个视窗的装置内容代号。例如,您的程式可以使用从GetWindowDC传回的装置内容代号在视窗的标题列上写入文字。然而,程式同样也应该处理WM_NCPAINT (「非显示区域绘制」)讯息。
TextOut:细节
TextOut是用於显示文字的最常用的GDI函式。语法是:
TextOut (hdc, x, y, psText, iLength) ;
以下将详细地讨论这个函式。
第一个参数是装置内容代号,它既可以是GetDC的传回值,也可以是在处理WM_PAINT讯息时BeginPaint的传回值。
装置内容的属性控制了被显示的字串的特徵。例如,装置内容中有一个属性指定文字颜色,内定颜色为黑色;内定装置内容还定义了白色的背景。在程式向显示器输出文字时,Windows使用这个背景色来填入字元周围的矩形空间(称为「字元框」)。
该文字背景色与定义视窗类别时设置的背景并不相同。视窗类别中的背景是一个画刷,它是一种纯色或者非纯色组成的画刷,Windows用它来擦除显示区域,它不是装置内容结构的一部分。在定义视窗类别结构时,大多数Windows应用程式使用WHITE_BRUSH,以便内定装置内容中的内定文字背景颜色与Windows用以擦除显示区域背景的画刷颜色相同。
psText参数是指向字串的指标,iLength是字串中字元的个数。如果psText指向Unicode字串,则字串中的位元组数就是iLength值的两倍。字串中不能包含任何ASCII控制字元(如回车、换行、制表或退格),Windows会将这些控制字元显示为实心块。Text0ut不识别作为字串结束标志的内容为零的位元组(对於Unicode,是一个短整数型态的0),而需要由nLength参数指明长度。
TextOut中的x和y定义显示区域内字串的开始位置,x是水平位置,y是垂直位置。字串中第一个字元的左上角位於座标点(x,y)。在内定的装置内容中,原点(x和y均为0的点)是显示区域的左上角。如果在TextOut中将x和y设为0,则将从显示区域左上角开始输出字串。
当您阅读GDI绘图函式(例如TextOut)的文件时,就会发现传递给函式的座标常常被称为「逻辑座标」。在第五章会详细地解释这种情况。现在请注意,Windows有许多「座标映射方式」,它们用来控制GDI函式指定的逻辑座标转换为显示器的实际图素座标的方式。映射方式在装置内容中定义,内定映射方式是MM_TEXT(使用WINGDI.H中定义的识别字)。在MM_TEXT映射方式下,逻辑单位与实际单位相同,都是图素;x的值从左向右递增,y的值从上向下递增(参看图4-2)。MM_TEXT座标系与Windows在PAINTSTRUCT结构中定义无效矩形时使用的座标系相同,这为我们带来了很多方便(但是,其他映射方式并非如此)。
图4-2 MM_TEXT映射方式下的x座标和y座标
装置内容也定义了一个剪裁区域。您已经看到,对於从GetDC取得的装置内容代号,内定剪裁区域是整个显示区域;而对於从BeginPaint取得的装置内容代号,则为无效区域。Windows不会在剪裁区域之外的任何位置显示字串。如果一个字元有一部分在剪裁区域外,则Windows将只显示此区域内的那部分。要想将输出写到视窗的显示区域之外不是那么容易的,所以不用担心会无意间出现这种事情。
装置内容还定义了在您呼叫TextOut显示文字时Windows使用的字体。内定字体为「系统字体」,或用Windows表头档案中的识别字,即SYSTEM_FONT。系统字体是Windows用来在标题列、功能表和对话方块中显示字串的内定字体。
在Windows的早期版本中,系统字体是等宽(fixed-pitch)字体,这意味著所有字元均具有同样的宽度,非常类似於打字机。然而,从Windows 3.0开始,系统字体成为一种变宽(variable-pitch)字体,这意味著不同的字元具有不同的大小,比如,「W」要比「i」宽。变宽字体比等宽字体好读,这已经是公认的事实。不过,可以想见,这一转变使很多原来的Windows程式码不再适用,从而要求程式写作者学习一些使用字体的新技术。
系统字体是一种「点阵字体」,这意味著字元被定义为图素块(在第十七章,将讨论TrueType字体,它是由轮廓定义的)。至於确切的大小,系统字体的字元大小取决於视讯显示器的大小。系统字体设计为至少能在显示器上显示25行80列文字。
要用TextOut显示多行文字,就必须确定字体的字元大小,可以根据字元的高度来定位字元的後续行,以及根据字元的宽度来定位字元的後续列。
系统字体的字元高度和平均宽度是多少?这个问题取决於视讯显示器的图素大小。Windows需要的最小显示大小是640×480,但是许多使用者更喜欢800×600或的显示大小。另外,对於这些较大的显示尺寸,Windows允许使用者选择不同大小的系统字体。
程式可以呼叫GetSystemMetrics函式以取使用者介面上各类视觉元件大小的资讯,呼叫GetTextMetrics取得字体大小。GetTextMetrics传回装置内容中目前选取的字体资讯,因此它需要装置内容代号。Windows将文字大小的不同值复制到在WINGDI.H中定义的TEXTMETRIC型态的结构中。TEXTMETRIC结构有20个栏位,我们只使用前七个:
typedef struct tagTEXTMETRIC
LONG tmInternalL
LONG tmExternalL
LONG tmAveCharW
LONG tmMaxCharW
其他结构栏位
TEXTMETRIC, * PTEXTMETRIC ;
这些栏位值的单位取决於选定的装置内容映射方式。在内定装置内容下,映射方式是MM_TEXT,因此值的大小是以图素为单位。
要使用GetTextMetrics函式,需要先定义一个结构变数(通常称为tm):
TEXTMETRIC
在需要确定文字大小时,先取得装置内容代号,再呼叫GetTextMetrics:
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
ReleaseDC (hwnd, hdc) ;
此後,您就可以查看文字尺寸结构中的值,并有可能保存其中的一些以备将来使用。
文字大小:细节
TEXTMETRIC结构提供了关於目前装置内容中选用的字体的丰富资讯。但是,字体的纵向大小只由5个值确定,其中4个值如图4-3所示。
图4-3 定义字体中纵向字元大小的4个值
最重要的值是tmHeight,它是tmAscent和tmDescent的和。这两个值表示了基准线上下字元的最大纵向高度。「间距」(leading)指印表机在两行文字间插入的空间。在TEXTMETRIC结构中,内部的间距包括在tmAscent中(因此也在tmHeight中),并且它经常是重音符号出现的地方。tmInternalLeading栏位可被设成0,在这种情况下,加重音的字母会稍稍缩短以便容纳重音符号。
TEXTMETRIC结构还包括一个不包含在tmHeight值中的栏位tmExternalLeading。它是字体设计者建议加在横向字元之间的空间大小。在安排文字行之间的空隙时,您可以接受设计者建议的值,也可以拒绝它。在系统字体中tmExternalLeading可以为0,因此我没有在图4-3中显示它。(尽管我不想告诉你们,图4-3确实就是Windows在640×480的显示解析度中使用的系统字体。)
TEXTMETRICS结构包含有描述字元宽度的两个栏位,即tmAveCharWidth(小写字母加权平均宽度)和tmMaxCharWidth(字体中最宽字元的宽度)。对於定宽字体,这两个值是相等的(图4-3中这些值分别为7和14)。
本章的范例程式还需要另一种字元宽度,即大写字母的平均宽度,这可以用tmAveCharWidth乘以150%大致计算出来。
必须认识到,系统字体的大小取决於Windows所执行的视讯显示器的解析度,在某些情况下,取决於使用者选取的系统字体的大小。Windows提供了一个与装置无关的图形介面,但程式写作者还是有事情要处理的。不要想当然耳地猜测字体大小来写作Windows程式,也不要把值定死,您可以使用GetTextMetrics函式取得这一资讯。
格式化文字
Windows启动後,系统字体的大小就不会发生改变,所以在程式执行过程中,程式写作者只需要呼叫一次GetTexMetrics。最好是在视窗讯息处理程式中处理WM_CREATE讯息时进行此呼叫,WM_CREATE讯息是视窗讯息处理程式接收的第一个讯息。在WinMain中呼叫CreateWindow时,Windows会以一个WM_CREATE讯息呼叫视窗讯息处理程式。
假设要编写一个Windows程式,在显示区域显示几行文字,这需要先取得字元宽度和高度。您可以在视窗讯息处理程式内定义两个变数来保存平均字元宽度(cxChar)和总的字元高度(cyChar):
static int cxChar, cyC
变数名的字首c代表「count」,在这里指图素数,与x和y结合,分别指宽和高。这些变数定义为static静态变数,因为它们在视窗讯息处理程式中处理其他讯息(如WM_PAINT)时也应该是有效的。如果变数在函式外面定义,则不需要定义为static。
下面是取得系统字体的字元宽度和高度的WM_CREATE程式码:
case WM_CREATE:
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharW
cyChar = tm.tmHeight + tm.tmExternalL
ReleaseDC (hwnd, hdc) ;
return 0 ;
注意我在计算cyChar时包括了tmExternalLeading栏位,虽然该栏位在系统字体中为0,但是因为它使得文字的可读性更好,所以还是应该把它包括进去。沿著视窗向下每隔cyChar图素就会显示一行文字。
您会发现常常需要显示格式化的数字跟简单的字串。我在第二章讲到过,您不能使惯用的工具(可爱的printf函式)来完成这项工作,但是可以使用sprintf和Windows版的sprintf-wsprintf。这些函式与printf相似,只是把格式化字串放到字串中。然後,可以用TextOut将字串输出到显示器上。非常方便的是,从sprintf和wsprintf传回的值就是字串的长度。您可以将这个值传递给TextOut作为iLength参数。下面的程式码显示了wsprintf与TextOut的典型组合:
TCHAR szBuffer [40] ;
其他行程式
iLength = wsprintf (szBuffer, TEXT ("The sum of %i and %i is %i"),
iA, iB, iA + iB) ;
TextOut (hdc, x, y, szBuffer, iLength) ;
对於这样简单的情况,可以将nLength的定义值与TextOut放在同一条叙述中,从而无需定义iLength:
TextOut (hdc, x, y, szBuffer,
wsprintf (szBuffer, TEXT ("The sum of %i and %i is %i"),
iA, iB, iA + iB)) ;
虽然这样子写起来不好看,但是功能与前者是一样的。
现在,我们似乎已经具备了在萤幕上显示多行文字所需要的所有知识。我们知道如何在WM_PAINT讯息处理期间取得一个装置内容代号,如何使用TextOut函式以及如何根据字元大小来安排字距,剩下的就是显示一点有意义的东西了。
在前一章里,我们大概知道从Windows的GetSystemMetrics函式中取得的资讯是很有意义的,该函式传回Windows中不同视觉元件的大小资讯,如图示、游标、标题列和卷动列等。它们的大小因显示卡和驱动程式的不同而有所不同。GetSystemMetrics是在程式中完成与装置无关图形输出的重要函式。
该函式需要一个参数,叫做「索引」,在Windows表头档案定义了75个整数索引识别字(识别字的数量随著每个版本的Windows的发布而不断地增加,在Windows 1.0的程式写作者文件中仅列出了26个)。GetSystemMetrics传回一个整数,这个整数通常就是参数中指定的图形元件大小。
让我们来编写一个程式,显示一些可以从GetSystemMetrics呼叫中取得的资讯,显示格式为每种视觉元件一行。如果我们建立一个表头档案,在表头档案中定义一个结构阵列,此结构包含GetSystemMetrics索引对应的Windows表头档案识别字和呼叫所传回的每个值对应的字串,这样处理起来要容易一些。表头档案名为SYSMETS.H,如程式4-1所示。
/*---------------------
SYSMETS.H -- System metrics display structure
-----------------------*/
#define NUMLINES ((int) (sizeof sysmetrics / sizeof sysmetrics [0]))
sysmetrics [] =
SM_CXSCREEN,
TEXT ("SM_CXSCREEN"),
TEXT ("Screen width in pixels"),
SM_CYSCREEN,
TEXT ("SM_CYSCREEN"),
TEXT ("Screen height in pixels"),
SM_CXVSCROLL,
TEXT ("SM_CXVSCROLL"),
TEXT ("Vertical scroll width"),
SM_CYHSCROLL,
TEXT ("SM_CYHSCROLL"),
TEXT ("Horizontal scroll height"),
SM_CYCAPTION,
TEXT ("SM_CYCAPTION"),
TEXT ("Caption bar height"),
SM_CXBORDER,
TEXT ("SM_CXBORDER"),
TEXT ("Window border width"),
SM_CYBORDER, TEXT ("SM_CYBORDER"),
TEXT ("Window border height"),
SM_CXFIXEDFRAME,TEXT ("SM_CXFIXEDFRAME"),
TEXT ("Dialog window frame width"),
SM_CYFIXEDFRAME,TEXT ("SM_CYFIXEDFRAME"),
TEXT ("Dialog window frame height"),
SM_CYVTHUMB, TEXT ("SM_CYVTHUMB"),
TEXT ("Vertical scroll thumb height"),
SM_CXHTHUMB,
TEXT ("SM_CXHTHUMB"),
TEXT ("Horizontal scroll thumb width"),
SM_CXICON,
TEXT ("SM_CXICON"),
TEXT ("Icon width"),
SM_CYICON,
TEXT ("SM_CYICON"),
TEXT ("Icon height"),
SM_CXCURSOR, TEXT ("SM_CXCURSOR"),
TEXT ("Cursor width"),
SM_CYCURSOR,
TEXT ("SM_CYCURSOR"),
TEXT ("Cursor height"),
SM_CYMENU,
TEXT ("SM_CYMENU"),
TEXT ("Menu bar height"),
SM_CXFULLSCREEN,TEXT ("SM_CXFULLSCREEN"),
TEXT ("Full screen client area width"),
SM_CYFULLSCREEN,TEXT ("SM_CYFULLSCREEN"),
TEXT ("Full screen client area height"),
SM_CYKANJIWINDOW,TEXT ("SM_CYKANJIWINDOW"),
TEXT ("Kanji window height"),
SM_MOUSEPRESENT, TEXT ("SM_MOUSEPRESENT"),
TEXT ("Mouse present flag"),
SM_CYVSCROLL,
TEXT ("SM_CYVSCROLL"),
TEXT ("Vertical scroll arrow height"),
SM_CXHSCROLL,
TEXT ("SM_CXHSCROLL"),
TEXT ("Horizontal scroll arrow width"),
TEXT ("SM_DEBUG"),
TEXT ("Debug version flag"),
SM_SWAPBUTTON,
TEXT ("SM_SWAPBUTTON"),
TEXT ("Mouse buttons swapped flag"),
TEXT ("SM_CXMIN"),
TEXT ("Minimum window width"),
TEXT ("SM_CYMIN"),
TEXT ("Minimum window height"),
SM_CXSIZE,
TEXT ("SM_CXSIZE"),
TEXT ("Min/Max/Close button width"),
SM_CYSIZE,
TEXT ("SM_CYSIZE"),
TEXT ("Min/Max/Close button height"),
SM_CXSIZEFRAME,
TEXT ("SM_CXSIZEFRAME"),
TEXT ("Window sizing frame width"),
SM_CYSIZEFRAME,
TEXT ("SM_CYSIZEFRAME"),
TEXT ("Window sizing frame height"),
SM_CXMINTRACK,
TEXT ("SM_CXMINTRACK"),
TEXT ("Minimum window tracking width"),
SM_CYMINTRACK,
TEXT ("SM_CYMINTRACK"),
TEXT ("Minimum window tracking height"),
SM_CXDOUBLECLK,
TEXT ("SM_CXDOUBLECLK"),
TEXT ("Double click x tolerance"),
SM_CYDOUBLECLK,
TEXT ("SM_CYDOUBLECLK"),
TEXT ("Double click y tolerance"),
SM_CXICONSPACING,TEXT ("SM_CXICONSPACING"),
TEXT ("Horizontal icon spacing"),
SM_CYICONSPACING,TEXT ("SM_CYICONSPACING"),
TEXT ("Vertical icon spacing"),
SM_MENUDROPALIGNMENT, TEXT ("SM_MENUDROPALIGNMENT"),
TEXT ("Left or right menu drop"),
SM_PENWINDOWS,
TEXT ("SM_PENWINDOWS"),
TEXT ("Pen extensions installed"),
SM_DBCSENABLED,
TEXT ("SM_DBCSENABLED"),
TEXT ("Double-Byte Char Set enabled"),
SM_CMOUSEBUTTONS,
TEXT ("SM_CMOUSEBUTTONS"),
TEXT ("Number of mouse buttons"),
SM_SECURE,
TEXT ("SM_SECURE"),
TEXT ("Security present flag"),
SM_CXEDGE,
TEXT ("SM_CXEDGE"),
TEXT ("3-D border width"),
SM_CYEDGE,
TEXT ("SM_CYEDGE"),
TEXT ("3-D border height"),
SM_CXMINSPACING,
TEXT ("SM_CXMINSPACING"),
TEXT ("Minimized window spacing width"),
SM_CYMINSPACING,
TEXT ("SM_CYMINSPACING"),
TEXT ("Minimized window spacing height"),
SM_CXSMICON,
TEXT ("SM_CXSMICON"),
TEXT ("Small icon width"),
SM_CYSMICON,
TEXT ("SM_CYSMICON"),
TEXT ("Small icon height"),
SM_CYSMCAPTION,
TEXT ("SM_CYSMCAPTION"),
TEXT ("Small caption height"),
SM_CXSMSIZE,
TEXT ("SM_CXSMSIZE"),
TEXT ("Small caption button width"),
SM_CYSMSIZE,
TEXT ("SM_CYSMSIZE"),
TEXT ("Small caption button height"),
SM_CXMENUSIZE,
TEXT ("SM_CXMENUSIZE"),
TEXT ("Menu bar button width"),
SM_CYMENUSIZE,
TEXT ("SM_CYMENUSIZE"),
TEXT ("Menu bar button height"),
SM_ARRANGE,
TEXT ("SM_ARRANGE"),
TEXT ("How minimized windows arranged"),
SM_CXMINIMIZED,
TEXT ("SM_CXMINIMIZED"),
TEXT ("Minimized window width"),
SM_CYMINIMIZED,
TEXT ("SM_CYMINIMIZED"),
TEXT ("Minimized window height"),
SM_CXMAXTRACK,
TEXT ("SM_CXMAXTRACK"),
TEXT ("Maximum draggable width"),
SM_CYMAXTRACK,
TEXT ("SM_CYMAXTRACK"),
TEXT ("Maximum draggable height"),
SM_CXMAXIMIZED,
TEXT ("SM_CXMAXIMIZED"),
TEXT ("Width of maximized window"),
SM_CYMAXIMIZED,
TEXT ("SM_CYMAXIMIZED"),
TEXT ("Height of maximized window"),
SM_NETWORK,
TEXT ("SM_NETWORK"),
TEXT ("Network present flag"),
SM_CLEANBOOT,
TEXT ("SM_CLEANBOOT"),
TEXT ("How system was booted"),
SM_CXDRAG,
TEXT ("SM_CXDRAG"),
TEXT ("Avoid drag x tolerance"),
SM_CYDRAG,
TEXT ("SM_CYDRAG"),
TEXT ("Avoid drag y tolerance"),
SM_SHOWSOUNDS,
TEXT ("SM_SHOWSOUNDS"),
TEXT ("Present sounds visually"),
SM_CXMENUCHECK,
TEXT ("SM_CXMENUCHECK"),
TEXT ("Menu check-mark width"),
SM_CYMENUCHECK,
TEXT ("SM_CYMENUCHECK"),
TEXT ("Menu check-mark height"),
SM_SLOWMACHINE,
TEXT ("SM_SLOWMACHINE"),
TEXT ("Slow processor flag"),
SM_MIDEASTENABLED,
TEXT ("SM_MIDEASTENABLED"),
TEXT ("Hebrew and Arabic enabled flag"),
SM_MOUSEWHEELPRESENT, TEXT ("SM_MOUSEWHEELPRESENT"),
TEXT ("Mouse wheel present flag"),
SM_XVIRTUALSCREEN,
TEXT ("SM_XVIRTUALSCREEN"),
TEXT ("Virtual screen x origin"),
SM_YVIRTUALSCREEN,
TEXT ("SM_YVIRTUALSCREEN"),
TEXT ("Virtual screen y origin"),
SM_CXVIRTUALSCREEN,
TEXT ("SM_CXVIRTUALSCREEN"),
TEXT ("Virtual screen width"),
SM_CYVIRTUALSCREEN,
TEXT ("SM_CYVIRTUALSCREEN"),
TEXT ("Virtual screen height"),
SM_CMONITORS,
TEXT ("SM_CMONITORS"),
TEXT ("Number of monitors"),
SM_SAMEDISPLAYFORMAT, TEXT ("SM_SAMEDISPLAYFORMAT"),
TEXT ("Same color format flag")
显示资讯的程式命名为SYSMETS1。SYSMETS1.C的原始码如程式4-2所示。现在大多数程式码看起来都很熟悉。WinMain中的程式码实际上与HELLOWIN中的程式码相同,并且WndProc中的大部分程式码都已经讨论过了。
SYSMETS1.C
/*------------------------------
SYSMETS1.C -- System Metrics Display Program No. 1
(c) Charles Petzold, 1998
----------------------------*/
#include &windows.h&
#include "sysmets.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
static TCHAR szAppName[] = TEXT ("SysMets1") ;
wndclass.style
= CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndP
wndclass.cbClsExtra
wndclass.cbWndExtra
wndclass.hInstance = hI
wndclass.hIcon
= LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor
= LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground
= (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName
wndclass.lpszClassName
if (!RegisterClass (&wndclass))
MessageBox ( NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
hwnd = CreateWindow (szAppName, TEXT ("Get System Metrics No. 1"),
WS_OVERLAPPEDWINDOW,
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 msg.wP
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
static int cxChar, cxCaps, cyC
PAINTSTRUCT
szBuffer [10] ;
TEXTMETRIC
switch (message)
case WM_CREATE:
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharW
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
cyChar = tm.tmHeight + tm.tmExternalL
ReleaseDC (hwnd, hdc) ;
return 0 ;
WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
for (i = 0 ; i & NUMLINES ; i++)
(hdc, 0, cyChar * i,
sysmetrics[i].szLabel,
lstrlen (sysmetrics[i].szLabel)) ;
(hdc, 22 * cxCaps, cyChar * i,
sysmetrics[i].szDesc,
lstrlen (sysmetrics[i].szDesc)) ;
SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;
TextOut (hdc, 22 * cxCaps + 40 * cxChar, cyChar * i,
wsprintf (szBuffer, TEXT ("%5d"),
GetSystemMetrics (sysmetrics[i].iIndex))) ;
SetTextAlign (hdc, TA_LEFT | TA_TOP) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
return DefWindowProc (hwnd, message, wParam, lParam) ;
图4-4显示了在标准VGA上执行的SYSMETS1。在程式显示区域的前两行可以看到,萤幕宽度是640个图素,萤幕高度是480个图素,这两个值以及程式所显示的其他值可能会因视讯显示器型态的不同而不同。
图4-4 SYSMETS1的显示
SYSMETS1.C视窗讯息处理程式
SYSMETS1.C程式中的WndProc视窗讯息处理程式处理三个讯息:WM_CREATE、WM_PAINT和WM_DESTROY。WM_DESTROY讯息的处理方法与第三章的HELLOWIN程式相同。
WM_CREATE讯息是视窗讯息处理程式接收到的第一个讯息。在CreateWindow函式建立视窗时,Windows产生这个讯息。在处理WM_CREATE讯息时,SYSMETS1呼叫GetDC取得视窗的装置内容,并呼叫GetTextMetrics取得内定系统字体的文字大小。SYSMETS1将平均字元宽度保存在cxChar中,将字元的总高度(包括外部间距)保存在cyChar中。
SYSMETS1还将大写字母的平均宽度保存在静态变数cxCaps中。对於固定宽度的字体, cxCaps等於cxChar。对於可变宽度字体,cxCaps设定为cxChar乘以150%。对於可变宽度字体,TEXTMETRIC结构中的tmPitchAndFamily栏位的低位元为1,对於固定宽度字体,该值为0。 SYSMETS1使用这个位元从cxChar计算cxCaps:
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
SYSMETS1在处理WM_PAINT讯息处理期间完成所有视窗建立工作。通常,视窗讯息处理程式先呼叫BeginPaint取得装置内容代号,然後用一道for叙述对SYSMETS.H中定义的sysmetrics结构的每一行进行回圈。三列文字用三个TextOut函式显示,对於每一列,TextOut的第三个参数都设定为:
cyChar * i
这个参数指示了字串顶端相对於显示区域顶部的图素位置。
第一条TextOut叙述在第一列显示了大写识别字。TextOut的第二个参数是0,这是说文字从显示区域的左边缘开始。文字的内容来自sysmetrics结构的szLabel栏位。我使用Windows函式lstrlen来计算字串的长度,它是TextOut需要的最後一个参数。
第二条TextOut叙述显示了对系统尺寸值的描述。这些描述存放在sysmetrics结构的szDesc栏位中。在这种情况下,TextOut的第二个参数设定为:
22 * cxCaps
第一列显示的最长的大写识别字有20个字元,因此第二列必须在第一列文字开头向右20 × cxCaps处开始。我使用22,以在两列之间加一点多余的空间。
第三条TextOut叙述显示从GetSystemMetrics函式取得的数值。变宽字体使得格式化向右对齐的数值有些棘手。从0到9的数字具有相同的宽度,但是这个宽度比空格宽度大。数值可以比一个数字宽,所以不同的数值应该从不同的横向位置开始。
那么,如果我们指定字串结束的图素位置,而不是指定字串的开始位置,以此向右对齐数值,是否会容易一些呢?用SetTextAlign函式就可以做到这一点。在SYSMETS1呼叫:
SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;
之後,传给後续TextOut函式的座标将指定字串的右上角,而不是左上角。
显示列数的TextOut函式的第二个参数设定为:
22 * cxCaps + 40 * cxChar
值40*cxChar包含了第二列的宽度和第三列的宽度。在TextOut函式之後,另一个对SetTextAlign的呼叫将对齐方式设定回普通方式,以进行下次回圈。
在SYSMETS1程式中存在著一个很难处理的问题:除非您有一个大萤幕跟高解析度的显示卡,否则就无法看到系统尺度列表的最後几行。如果视窗太窄,甚至根本看不到值。
SYSMETS1不知道这个问题。否则我们就会显示一个讯息方块说「抱歉!」程式甚至不知道它的显示区域有多大,它从视窗顶部开始输出文字,并仰赖Windows裁剪超出显示区域底部的内容。
显然,这很不理想。为了解决这个问题,我们的第一个任务是确定程式在显示区域内能输出多少内容。
显示区域的大小
如果您使用过现有的Windows应用程式,可能会发现视窗的尺寸变化极大。视窗最大化时(假定视窗只有标题列并且没有功能表),显示区域几乎占据了整个萤幕。这一最大化了的显示区域的尺寸可以通过以SM_CXFULLSCREEN和SM_CYFULLSCREEN为参数呼叫GetSystemMetrics来获得。视窗的最小尺寸可以很小,有时甚至不存在,更不用说显示区域了。
在最近一章,我们使用GetClientRect函式来取得显示区域的大小。使用这个函式没有什么不好,但是在您每次要使用资讯时就去呼叫它一遍是没有效率的。确定视窗显示区域大小的更好方法是在视窗讯息处理程式中处理WM_SIZE讯息。在视窗大小改变时,Windows给视窗讯息处理程式发送一个WM_SIZE讯息。传给视窗讯息处理程式的lParam参数的低字组中包含显示区域的宽度,高字组中包含显示区域的高度。要保存这些尺寸,需要在视窗讯息处理程式中定义两个静态变数:
static int cxClient, cyC
与cxChar和cyChar相似,这两个变数在视窗讯息处理程式内定义为静态变数,因为在以後处理其他讯息时会用到它们。处理WM_SIZE的方法如下:
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;
实际上您会在每个Windows程式中看到类似的程式码。LOWORD和HIWORD巨集在Windows表头档案WINDEF.H中定义。这些巨集的定义看起来像这样:
#define LOWORD(l) ((WORD)(l))
#define HIWORD(l) ((WORD)(((DWORD)(l) && 16) & 0xFFFF))
这两个巨集传回WORD值(16位元的无正负号整数,范围从0到0xFFFF)。一般,将这些值保存在32位元有号整数中。这就不会牵扯到任何转换问题,并使得这些值在以後需要的任何计算中易於使用。
在许多Windows程式中,WM_SIZE讯息必然跟著一个WM_PAINT讯息。为什么呢?因为在我们定义视窗类别时指定视窗类别样式为:
CS_HREDRAW | CS_VREDRAW
这种视窗类别样式告诉Windows,如果水平或者垂直大小发生改变, 则强制更新显示区域。
用如下公式计算可以在显示区域内显示的文字的总行数:
cyClient / cyChar
如果显示区域的高度太小以至无法显示一个完整的字元,这个公式的结果可以为0。类似地,在显示区域的水平方向可以显示的小写字元的近似数目为:
cxClient / cxChar
如果在处理WM_CREATE讯息处理期间取得cxChar和cyChar,则不用担心在这两个计算公式中会出现被0除的情况。在WinMain呼叫CreateWindow时,视窗讯息处理程式接收一个WM_CREATE讯息。在WinMain呼叫ShowWindow之後接收到第一个WM_CREATE讯息,此时cxChar和cyChar已经被赋予正的非零值了。
如果显示区域的大小不足以容纳所有的内容,那么,知道视窗显示区域的大小只是为使用者提供了在显示区域内卷动文字的第一步。如果您对其他有类似需求的Windows应用程式很熟悉,就很可能知道,这种情况下,我们需要使用「卷动列」。
卷动列是图形使用者介面中最好的功能之一,它很容易使用,而且提供了很好的视觉回馈效果。您可以使用卷动列显示任何东西--无论是文字、图形、表格、资料库记录、图像或是网页,只要它所需的空间超出了视窗的显示区域所能提供的空间,就可以使用卷动列。
卷动列既有垂直方向的(供上下移动),也有水平方向的(供左右移动)。使用者可以使用滑鼠在卷动列两端的箭头上或者在箭头之间的区域中点一下,这时,「卷动方块」在卷动列内的移动位置与所显示的资讯在整个文件中的近似相关位置成比例。使用者也可以用滑鼠拖动卷动方块到特定的位置。图4-5显示了垂直卷动列的建议用法。
图4-5 垂直卷动列
有时,程式写作者对卷动概念很难理解,因为他们的观点与使用者的观点不同:使用者向下卷动是想看到文件较下面的部分;但是,程式实际上是将文件相对於显示视窗向上移动。Windows文件和表头档案识别字是依据使用者的观点:向上卷动意味著朝文件的开头移动;向下卷动意味著朝文件尾部移动。
很容易在应用程式中包含水平或者垂直的卷动列,程式写作者只需要在CreateWindow的第三个参数中包括视窗样式(WS)识别字WS_VSCROLL(垂直卷动)和/或WS_HSCROLL(水平卷动)即可。这些卷动列通常放在视窗的右部和底部,伸展为显示区域的整个长度或宽度。显示区域不包含卷动列所占据的空间。对於特定的显示驱动程式和显示解析度,垂直卷动列的宽度和水平卷动列的高度是恒定的。如果需要这些值,可以使用GetSystemMetrics呼叫来取得(如前面的程式那样)。
Windows负责处理对卷动列的所有滑鼠操作,但是,视窗卷动列没有自动的键盘介面。如果想用游标键来完成卷动功能,则必须提供这方面的程式码(我们将在下一章另一个版本的SYSMETS程式中做到这一点)。
卷动列的范围和位置
每个卷动列均有一个相关的「范围」(这是一对整数,分别代表最小值和最大值)和「位置」(它是卷动方块在此范围内的位置)。当卷动方块在卷动列的顶部(或左部)时,卷动方块的位置是范围的最小值;在卷动列的底部(或右部)时,卷动方块的位置是范围的最大值。
在内定情况下,卷动列的范围是从0(顶部或左部)至100(底部或右部),但将范围改变为更方便於程式的数值也是很容易的:
SetScrollRange (hwnd, iBar, iMin, iMax, bRedraw) ;
参数iBar为SB_VERT或者SB_HORZ,iMin和iMax分别是范围的最小值和最大值。如果想要Windows根据新范围重画卷动列,则设置bRedraw为TRUE(如果在呼叫SetScrollRange後,呼叫了影响卷动列位置的其他函式,则应该将bRedraw设定为FALSE以避免过多的重画)。
卷动方块的位置总是离散的整数值。例如,范围为0至4的卷动列具有5个卷动方块位置,如图4-6所示。
图4-6 具有5个卷动方块位置的卷动列
您可以使用SetScrollPos在卷动列范围内设置新的卷动方块位置:
SetScrollPos (hwnd, iBar, iPos, bRedraw) ;
参数iPos是新位置,它必须在iMin至iMax的范围内。Windows提供了类似的函式(GetScrollRange和GetScrollPos)来取得卷动列的目前范围和位置。
在程式内使用卷动列时,程式写作者与Windows共同负责维护卷动列以及更新卷动方块的位置。下面是Windows对卷动列的处理:
处理所有卷动列滑鼠事件
  当使用者在卷动列内单击滑鼠时,提供一种「反相显示」的闪烁
  当使用者在卷动列内拖动卷动方块时,移动卷动方块
  为包含卷动列视窗的视窗讯息处理程式发送卷动列讯息
以下是程式写作者应该完成的工作:
初始化卷动列的范围和位置
  处理视窗讯息处理程式的卷动列讯息
  更新卷动列内卷动方块的位置
  更改显示区域的内容以回应对卷动列的更改
像生活中的大多数事情一样,在我们看一些程式码时这些会显得更加有意义。
卷动列讯息
在用滑鼠单击卷动列或者拖动卷动方块时,Windows给视窗讯息处理程式发送WM_VSCROLL(供上下移动)和WM_HSCROLL(供左右移动)讯息。在卷动列上的每个滑鼠动作都至少产生两个讯息,一条在按下滑鼠按钮时产生,一条在释放按钮时产生。
和所有的讯息一样, WM_VSCROLL和WM_HSCROLL也带有wParam和lParam讯息参数。对於来自作为视窗的一部分而建立的卷动列讯息,您可以忽略lParam;它只用于作为子视窗而建立的卷动列(通常在对话方块内)。
wParam讯息参数被分为一个低字组和一个高字组。wParam的低字组是一个数值,它指出了滑鼠对卷动列进行的操作。这个数值被看作一个「通知码」。通知码的值由以SB(代表「scroll bar(卷动列)」)开头的识别字定义。以下是在WINUSER.H中定义的通知码:
#define SB_LINEUP
#define SB_LINELEFT
#define SB_LINEDOWN
#define SB_LINERIGHT
#define SB_PAGEUP
#define SB_PAGELEFT
#define SB_PAGEDOWN
#define SB_PAGERIGHT
#define SB_THUMBPOSITION 4
#define SB_THUMBTRACK
#define SB_TOP
#define SB_LEFT
#define SB_BOTTOM
#define SB_RIGHT
#define SB_ENDSCROLL
包含LEFT和RIGHT的识别字用於水平卷动列,包含UP、DOWN、TOP和BOTTOM的识别字用於垂直卷动列。滑鼠在卷动列的不同区域单击所产生的通知码如图4-7所示。
图4-7 用於卷动列讯息的wParam值的识别字
如果在卷动列的各个部位按住滑鼠键,程式就能收到多个卷动列讯息。当释放滑鼠键後,程式会收到一个带有SB_ENDSCROLL通知码的讯息。一般可以忽略这个讯息,Windows不会去改变卷动方块的位置,而您可以在程式中呼叫SetScrollPos来改变卷动方块的位置。
当把滑鼠的游标放在卷动方块上并按住滑鼠键时,您就可以移动卷动方块。这样就产生了带有SB_THUMBTRACK和SB_THUMBPOSITION通知码的卷动列讯息。在wParam的低字组是SB_THUMBTRACK时,wParam的高字组是使用者在拖动卷动方块时的目前位置。该位置位於卷动列范围的最小值和最大值之间。在wParam的低字组是SB_THUMBPOSITION时,wParam的高字组是使用者释放滑鼠键後卷动方块的最终位置。对於其他的卷动列操作,wParam的高字组应该被忽略。
为了给使用者提供回馈,Windows在您用滑鼠拖动卷动方块时移动它,同时您的程式会收到SB_THUMBTRACK讯息。然而,如果不通过呼叫SetScrollPos来处理SB_THUMBTRACK或SB_THUMBPOSITION讯息,在使用者释放滑鼠键後,卷动方块会迅速跳回原来的位置。
程式能够处理SB_THUMBTRACK或SB_THUMBPOSITION讯息,但一般不同时处理两者。如果处理SB_THUMBTRACK讯息,在使用者拖动卷动方块时您需要移动显示区域的内容。而如果处理SB_THUMBPOSITION讯息,则只需在使用者停止拖动卷动方块时移动显示区域的内容。处理SB_THUMBTRACK讯息更好一些(但更困难),对於某些型态的资料,您的程式可能很难跟上产生的讯息。
WINUSER.H表头档案还包括SB_TOP、SB_BOTTOM、SB_LEFT和SB_RIGHT通知码,指出卷动列已经被移到了它的最小或最大位置。然而,对於作为应用程式视窗一部分而建立的卷动列来说,永远不会接收到这些通知码。
在卷动列范围使用32位元的值也是有效的,尽管这不常见。然而,wParam的高字组只有16位元的大小,它不能适当地指出SB_THUMBTRACK和SB_THUMBPOSITION操作的位置。在这种情况下,需要使用GetScrollInfo函式(在下面描述)来得到资讯。
在SYSMETS中加入卷动功能
前面的说明已经很详尽了,现在,要将那些东西动手做做看了。让我们开始时简单些,从垂直卷动著手,因为我们实在太需要垂直卷动了,而暂时还可以不用水平卷动。SYSMET2如程式4-3所示。这个程式可能是卷动列的最简单的应用。
SYSMETS2.C
/*------------------------------
SYSMETS2.C -- System Metrics Display Program No. 2
(c) Charles Petzold, 1998
------------------------------*/
#include &windows.h&
#include "sysmets.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
static TCHAR szAppName[] = TEXT ("SysMets2") ;
wndclass.style
= CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndP
wndclass.cbClsExtra
wndclass.cbWndExtra
wndclass.hInstance
wndclass.hIcon
= LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor
= LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground
= (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName
wndclass.lpszClassName
if (!RegisterClass (&wndclass))
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
hwnd = CreateWindow (szAppName, TEXT ("Get System Metrics No. 2"),
WS_OVERLAPPEDWINDOW | WS_VSCROLL,
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 msg.wP
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
static int
cxChar, cxCaps, cyChar, cyClient, iVscrollP
PAINTSTRUCT
szBuffer[10] ;
TEXTMETRIC
switch (message)
case WM_CREATE:
hdc = GetDC (hwnd) ;
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharW
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
cyChar = tm.tmHeight + tm.tmExternalL
ReleaseDC (hwnd, hdc) ;
SetScrollRange (hwnd, SB_VERT, 0, NUMLINES - 1, FALSE) ;
SetScrollPos
(hwnd, SB_VERT, iVscrollPos, TRUE) ;
return 0 ;
case WM_SIZE:
cyClient = HIWORD (lParam) ;
return 0 ;
case WM_VSCROLL:
switch (LOWORD (wParam))
case SB_LINEUP:
iVscrollPos -= 1 ;
case SB_LINEDOWN:
iVscrollPos += 1 ;
case SB_PAGEUP:
iVscrollPos -= cyClient / cyC
case SB_PAGEDOWN:
iVscrollPos += cyClient / cyC
case SB_THUMBPOSITION:
iVscrollPos = HIWORD (wParam) ;
iVscrollPos = max (0, min (iVscrollPos, NUMLINES - 1)) ;
if (iVscrollPos != GetScrollPos (hwnd, SB_VERT))
SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
for (i = 0 ; i & NUMLINES ; i++)
y = cyChar * (i - iVscrollPos) ;
TextOut (hdc, 0, y,
sysmetrics[i].szLabel,
lstrlen (sysmetrics[i].szLabel)) ;
TextOut (hdc, 22 * cxCaps, y,
sysmetrics[i].szDesc,
lstrlen (sysmetrics[i].szDesc)) ;
SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;
TextOut (hdc, 22 * cxCaps + 40 * cxChar, y, szBuffer,
wsprintf (szBuffer, TEXT ("%5d"),
GetSystemMetrics (sysmetrics[i].iIndex))) ;
SetTextAlign (hdc, TA_LEFT | TA_TOP) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
return DefWindowProc (hwnd, message, wParam, lParam) ;
新的CreateWindow呼叫在第三个参数中包含了WS_VSCROLL视窗样式,从而在视窗中加入了垂直卷动列,其视窗样式为:
WS_OVERLAPPEDWINDOW | WS_VSCROLL
WndProc视窗讯息处理程式在处理WM_CREATE讯息时增加了两条叙述,以设置垂直卷动列的范围和初始位置:
SetScrollRange (hwnd, SB_VERT, 0, NUMLINES - 1, FALSE) ;
SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;
sysmetrics结构具有NUMLINES行文字,所以卷动列范围被设定为0至NUMLINES-1。卷动列的每个位置对应于在显示区域顶部显示的一个文字行。如果卷动方块的位置为0,则第一行会被放置在显示区域的顶部。如果位置大於0,其他行就会出现在显示区域的顶部。当位置为NUMLINES-1时,则最後一行文字出现在显示区域的顶部。
为了有助於处理WM_VSCROLL讯息,在视窗讯息处理程式中定义了一个静态变数iVscrollPos,这一变数是卷动列内卷动方块的目前位置。对於SB_LINEUP和SB_LINEDOWN,只需要将卷动方块调整一个单位的位置。对於SB_PAGEUP和SB_PAGEDOWN,我们想移动一整面的内容,或者移动cyClient /cyChar个单位的位置。对於SB_THUMBPOSITION,新的卷动方块位置是wParam的高字组。SB_ENDSCROLL和SB_THUMBTRACK讯息被忽略。
在程式依据收到的WM_VSCROLL讯息计算出新的iVscrollPos值後,用min和max巨集来调整iVscrollPos,以确保它在最大值与最小值之间。程式然後将iVscrollPos与呼叫GetScrollPos取得的先前位置相比较,如果卷动位置发生了变化,则使用SetScrollPos来进行更新,并且呼叫InvalidateRect使整个视窗无效。
InvalidateRect呼叫产生一个WM_PAINT讯息。SYSMETS1在处理WM_PAINT讯息时,每一行的y座标计算公式为:
cyChar * i
在SYSMETS2中,计算公式为:
cyChar * (i - iVscrollPos)
回圈仍然显示NUMLINES行文字,但是对於非零值的iVscrollPos是负数。程式实际上在显示区域以外显示这些文字行。当然,Windows不会显示这些行,因此萤幕显得乾净和漂亮。
前面说过,我们一开始不想弄得太复杂,这样的程式码很浪费,效率很低。下面我们对此加以修改,但是先要考虑在WM_VSCROLL讯息之後更新显示区域的方法。
绘图程式的组织
在处理完卷动列讯息後,SYSMETS2不更新显示区域,相反,它呼叫InvalidateRect使显示区域失效。这导致Windows将一个WM_PAINT讯息放入讯息伫列中。
最好能使Windows程式在回应WM_PAINT讯息时完成所有的显示区域绘制功能。因为程式必须在一接收到WM_PAINT讯息时就更新整个显示区域,如果在程式的其他部分也绘制的话,将很可能使程式码重复。
首先,您可能对这种拐弯抹角的方式感到厌烦。在Windows的早期,因为这种方式与文字模式的程式设计差别太大,程式写作者感到这种概念很难理解。并且,程式要不断地通过马上绘制画面来回应键盘和滑鼠。这样做既方便又有效,但是在很多情况下,这完全不必要。当您掌握了在回应WM_PAINT讯息时积累绘制显示区域所需要的全部资讯的原则之後,会对这种结果感到满意的。
如同SYSMETS2示范的,程式仍然需要在处理非WM_PAINT讯息时更新特定的显示区域,使用InvalidateRect就很方便,您可以用它使显示区域内的特定矩形或者整个显示区域失效。
只将视窗显示区域标记为无效以产生WM_PAINT讯息,对於某些应用程式来说也许不是完全令人满意的选择。在呼叫InvalidateRect之後,Windows将WM_PAINT讯息放入讯息伫列中,最後由视窗讯息处理程式处理它。然而,Windows将WM_PAINT讯息当成低优先顺序讯息,如果系统有许多其他的动作正在发生,那么也许会让您等待一会儿工夫。这时,当对话方块消失时,将会出现一些空白的「洞」,程式仍然等待更新它的视窗。
如果您希望立即更新无效区域,可以在呼叫InvalidateRect之後呼叫UpdateWindow:
UpdateWindow (hwnd) ;
如果显示区域的任一部分无效,则UpdateWindow将导致Windows用WM_PAINT讯息呼叫视窗讯息处理程式(如果整个显示区域有效,则不呼叫视窗讯息处理程式)。这一WM_PAINT讯息不进入讯息伫列,直接由Windows呼叫视窗讯息处理程式。视窗讯息处理程式完成更新後立即退出,Windows将控制传回给程式中UpdateWindow呼叫之後的叙述。
您可能注意到,UpdateWindow与WinMain中用来产生第一个WM_PAINT讯息的函式相同。最初建立视窗时,整个显示区域内容变为无效,UpdateWindow指示视窗讯息处理程式绘制显示区域。
建立更好的滚动
SYSMETS2动作良好,但它只是模仿其他程式中的卷动列,并且效率很低。很快我将示范一个新的版本,改进它的不足。也许最有趣的是这个新版本不使用目前所讨论的四个卷动列函式。相反,它将使用Win32 API中才有的新函式。
卷动列资讯函式
卷动列文件(在/Platform SDK/User Interface Services/Controls/Scroll Bars中)指出SetScrollRange、SetScrollPos、GetScrollRange和GetScrollPos函式是「过时的」,但这并不完全正确。这些函式在Windows 1.0中就出现了,在Win32 API中升级以处理32位元参数。它们仍然具有良好的功能。而且,它们不与Windows程式设计中新函式相冲突,这就是我在此书中仍使用它们的原因。
Win32 API介绍的两个卷动列函式称作SetScrollInfo和GetScrollInfo。这些函式可以完成以前函式的全部功能,并增加了两个新特性。
第一个功能涉及卷动方块的大小。您可能注意到,卷动方块大小在SYSMETS2程式中是固定的。然而,在您可能使用到的一些Windows应用程式中,卷动方块大小与在视窗中显示的文件大小成比例。显示的大小称作「页面大小」。演算法为:
可以使用SetScrollInfo来设置页面大小(从而设置了卷动方块的大小),如将要看到的SYSMETS3程式所示。
GetScrollInfo函式增加了第二个重要的功能,或者说它改进了目前API的不足。假设您要使用65,536或更大单位的范围,这在16位元Windows中是不可能的。当然在Win32中,函式被定义为可接受32位元参数,因此是没有问题的。(记住如果使用这样大的范围,卷动方块的实际物理位置数仍然由卷动列的图素大小限制)。然而,当使用SB_THUMBTRACK或SB_THUMBPOSITION通知码得到WM_VSCROLL或WM_HSCROLL讯息时,只提供了16位元资料来指出卷动方块的目前位置。通过GetScrollInfo函式可以取得真实的32位元值。
SetScrollInfo和GetScrollInfo函式的语法是
SetScrollInfo (hwnd, iBar, &si, bRedraw) ;
GetScrollInfo (hwnd, iBar, &si) ;
像在其他卷动列函式中那样,iBar参数是SB_VERT或SB_HORZ,它还可以是用於卷动列控制的SB_CTL。SetScrollInfo的最後一个参数可以是TRUE或FALSE,指出了是否要Windows重新绘制计算了新资讯後的卷动列。
两个函式的第三个参数是SCROLLINFO结构,定义为:
typedef struct tagSCROLLINFO
UINT cbS // set to sizeof (SCROLLINFO)
UINT fM // values to set or get
// minimum range value
// maximum range value
UINT nP // page size
// current position
nTrackP// current tracking position
SCROLLINFO,

我要回帖

更多关于 一键命令方块凋零风暴 的文章

 

随机推荐