怎么让笔记本键盘失灵键盘+-*/变成直接永久打出来是+-×÷

键盘上的问号打不出来是怎么回事_百度知道
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。
键盘上的问号打不出来是怎么回事
按键没问题
我有更好的答案
不是吧,你按 Shift+?试试!
你开启了小键盘功能按住Fn再加NumLk就解除了
你是不是吧小键盘锁了
按SHIFT+?键就出来了,不行的话就是键盘坏了
其他2条回答
为您推荐:
其他类似问题
键盘的相关知识
换一换
回答问题,赢新手礼包[硬件产品讨论] 机械键盘扫盲,以及个人使用心得。感谢47楼gif图
[color=red][size=150%]写在开头的话[/size][/color]1.机械键盘只是个外设2.机械键盘手感见仁见智,以下文字仅楼主个人使用心得。3.外设[del]是毒品[/del]入水需谨慎在硬件区来了有2个月了吧,AFK魔兽很久以后,楼主独自来到了异国他乡巴西,无聊之余在硬件区学到了很多东西,觉得应该对于硬件区有所贡献。但是楼主对于其他硬件知识略懂皮毛,看到硬件区很多人都求推荐机械键盘。也正巧,楼主对键盘中毒已久,或自己买过、或朋友买的楼主有幸运摸过的机械键盘也不下30把了。在这里写一些自己的使用心得。[color=crimson]读完这篇帖子你能知道[/color]1.红 茶 黑 青 轴到底有什么样的不同?2.静电容键盘 就一定比机械键盘好?[color=red][size=130%]正题[/size][/color]第一位Cherry G80-3000 白色 青轴[img]http://y./img?s=P5JWNv8Vu&l=y.jpg[/img]这把键盘是在国内时第一把入手的键盘,入手原因是因为Cherry的大名深入人心。现在楼主在巴西,借用一下别人的图片。这张图很直观的能看到机械键盘里面的轴到底是怎么样的,但是这不是重点,因为我想能上NGA的人一定会去百度机械键盘。那里对机械键盘的轴有很多介绍,比如春天 夏天 秋天,但是这么文艺的比喻 让我们这些不文艺的2B青年更加的迷糊了那么让我来说一下青轴的给我的感觉。只是按下去的第一下。没错,第一下就非常明显的感觉到,哦!这才是机械键盘嘛。大2段的青轴非常直接的让你感受到薄膜和机械的区别。我知道这么说2段你也不明白2段到底是怎么样的,电脑开机键知道么?去按几下,差不多2段就是这样的感觉,只不过开机键按下去比较长。键盘短一点而已误区:1.很多人都说青轴 很吵,青轴吵是因为它要发声2次,所以才觉得特别吵,但是机械键盘特别是带钢板的哪怕是红轴也一样很吵,所以想用机械,又想静音的朋友请放弃这个想法。
2.青轴只适合打字,其实不是的,青轴也适合玩游戏,RTS游戏用青轴就很好,而且很多青轴用习惯的人,也喜欢用青轴打游戏。至少楼主用青轴玩魔兽和DOTA从来不觉得别扭,也可能我比较虐?也许青轴的压力克数对于女生不讨喜?但是我也见过女生用青轴码字1下午都不觉得累。我相信没有一个游戏使用键盘的频率会高于码字的频率,所以既然说了,青轴适合打字,那么青轴打游戏时间长手指酸的说法就不成立。[size=110%][color=crimson]总结:其实对于第一把机械键盘,楼主一向推荐时给予青,或者红。青是因为青轴能让你最快的速度感受到机械和薄膜的区别,而红轴是因为,它和薄膜有很多相同性,而又有很多不同性,可以让人很快的适应。[/color][/size]OK第二位登场的就应该是耳熟能详,大名鼎鼎的赛睿7G了[img]http://y./img?s=QflWQABLC&l=y.jpg[/img][img]http://y./img?s=rp2crBU8I&l=y.jpg[/img][img]http://y./img?s=l2iHwsBjH&l=y.jpg[/img]好吧。自从入了G80以后,中了毒的楼主,鬼迷心窍的又败了一把7G 壮哉我大赛睿。其实这把键盘是被朋友忽悠买的。X:我靠黑轴啊!游戏绝配啊!X:我靠大托板啊,手放上面多爽啊!X:我靠......我:行了别靠了,下单了。打着游戏的幌子盛装出席的7G没有给我想象中的惊艳,其实是因为青轴当时给我的惊艳太大,导致我对黑轴的触感有点冷淡。相比青轴而言,黑轴的压力并不夸张,而直上直下的那种薄膜的感觉稍稍有点升温。不过那超大的托板的确让我烦恼了很久...桌子放不下了.....好吧我又扯远了....黑轴给我的感觉非常的直接,如果要形容的话,就是键帽触底以后,回弹的那种感觉非常的舒服,快?应该是快把。至少感觉比红轴要快,非常的干脆,说它有力也好,说它清脆也行。总之很干净,直上直下。这样的感觉其实在打游戏特别是DOTA类游戏或者拳皇时是非常舒服的,如果你要使用一套连招,那么黑轴可以非常干脆的就让你使用出来完全不拖泥带水,所以他们才会说黑轴是适合打游戏的键盘?在这里毋庸置疑的是,黑轴确实是大部分网友,DOTA类游戏的首选,因为在高频率使用技能时,黑轴表现的[color=blue]手感[/color]反应要比其他轴要烧快。虽然我喜欢使用青轴,但是在这里我还是要给黑轴加分。第三把我们晚点说。先说红轴技嘉新出的旗舰键盘,Aivia Osmium因为从中国空运到巴西,据货代说.盒子太大.超标所以把外盒给丢掉重新包装了...可以想象外盒很霸气...真想看的可以百度搜下。这里就拍了些渣图片[img]http://y./img?s=06tXF2Lng&l=y.jpg[/img][img]http://y./img?s=403dQeM6E&l=y.jpg[/img][img]http://y./img?s=l9bJ4Wwi4&l=y.jpg[/img][img]http://y./img?s=P0O8ncjeN&l=y.jpg[/img]好吧.远渡重洋的楼主,到了巴西以后,发现没有机械键盘玩游戏十分的不幸福,但是让娘亲寄家里的键盘不太现实,于是拜托基友给楼主空运了一把机械键盘过来。也许是对于未知领域的渴望......(说文艺了.其实就是想换换口味,好吧我承认我很花心。)鸭子和技嘉当时犹豫了很久由于技嘉的鼠标简直烂到爆,让我对技嘉的声望从当年的崇拜一下就掉到了中立,但是这把键盘的设计实在是很诱人....亮骚了的灯啊....宏编辑键啊....然后托板啊!!!!!然后送的键帽啊!!!!!然后还有USB 3.0啊!!!!哎哟,而且还是个红轴!!激动了激动了.....等了四十五天..终于等到了。一下就傻了.好软。。不过还挺舒服的,按下去之后回馈的速度没有黑轴快,其他基本相似,就压力克数来说,我感觉不到。我只感觉红轴更加软一点。黑轴的回弹力是男人,那么红轴就女性一点。红轴在设计上讲究的是蜻蜓点水的打字方式,很灵敏,轻轻一点就可以触发。打游戏也是绝对一流选择,但是....额....我力气比较大。无法蜻蜓点水,就是个壮男,键帽直接敲到钢板的那一阵阵的啪啪啪啪的声音....不知道的以为我在房里干嘛呢...总的来说,如果想追求机械键盘稍微安静点的朋友,那么红轴的选择就不错,你可以很小心的不要让键帽打在钢板上...这样的话声音就会小很多。总结:4轴最软的一轴,比较适合已经习惯薄膜很多年,想换机械但又怕长时间适应的人。红轴和贵点的薄膜键盘其实很像。但它又是确确实实的机械键盘。在此表扬一样技嘉这把键盘的做工。非常的精细,非常的人性化。好吧。茶轴咱先放着魔力鸭的9008 紫光 茶轴 为什么放着咱一会再说,免的鸭粉说我黑鸭子。先说另外一个异类 雷蛇。[img]http://y./img?s=pwtuuLYAV&l=y.jpg[/img][img]http://y./img?s=yp9D6CWE1&l=y.jpg[/img]对于灯厂的键盘鼠标,其实还是很有良心的。很多人黑灯厂,的确黑腹狼蛛烂到屎,但黑寡妇的改青还是非常不错的.这款键盘是在等待技嘉键盘中逛巴西商场,售货员拿错键盘拣便宜拣的。换算下450RMB一来键盘没到打游戏不爽,二来改青键盘也想摸摸看。三来自己中国邮寄了把键盘过来,女王没的用,她能放过我么这把改青键盘其实改的很不错,就我个人使用感觉,按下去的青,回弹的黑。非常犀利啊。但是一粒老鼠屎坏了一锅粥。F区奇葩的位置设定,加上宏编辑区的位置之奇葩,经常误操作。然后直接0分了.....适应了很久没法接受,直接丢给女王用了。灯厂改轴的那个师傅估计要恨死这个键位设计师了。。。。。好吧...该来的还是要来鸭子9008图NGA很多帖子里都有了...就不上了。这里上传有点累....紫光 茶轴这不是我第一把摸过的茶轴,茶轴的手感,可以用细腻来形容。小2段,没有青那么有感觉,但是确胜过黑。当然不是打游戏的感觉,是第一下接触。说实话。魔力鸭的键盘在我的朋友圈里口碑不是很好,但是我拿到插上以后,被那华丽丽的灯光还是着实吸引了一把。因为采用的是原厂轴,所以手感并没差多少。可惜,就像朋友诅咒的一样。用了1个礼拜Q的灯就报销了....然后可能是我玩灯玩的太过分,F区又报销了3个灯。想象就这么用吧,反正没差...但900多买的东西不免还是有点心疼。相对来说技嘉的那把红轴也是背光的。键帽的材质比鸭子要好不少。我一个高中同学,在12年3月买了把F记得圣手,那时候感觉真心亮。键盘的切口之类的都非常的细致,种种原因,我一直没机会弄一把F记得键盘是遗憾,而魔力鸭在我朋友口中一直都口碑不好,说做工很差,不值得。但是在NGA和其他外设区,有很多人吹捧鸭子多么多么好。无脑推荐鸭子。我不知道他是托还是什么。反正,真的感觉鸭子的键盘,质量没其他的好。说难听点,凯酷在这方面都比鸭子行。[size=130%][color=red]静电容键盘[/color][/size]很多人说起机械键盘就会有人提起静电容...他们是不同的概念,很多人一推荐到贵的机械,就会有人说加几百不如弄个静电容第一 静电容不比机械键盘好。他们是一样的,就好像薄膜和机械键盘,其实除了无冲。我不觉得机械键盘比薄膜好多少,手感这东西见仁见智,大有人觉得薄膜手感比机械好。第二 静电容 不是有一个静字就很安静,静电容一样很吵!别再说什么,静电容很安静的话。那就是笑话。第三 小弟有幸摸过一把静电容,忘记什么牌子什么型号了。我的评价,薄膜不薄膜 机械不机械,手感还算过的去,按下去的薄膜,弹上来的机械吧。只能说我主观不喜欢静电容的手感。所以就没多研究,大家可以自己去摸下再判断买什么。最后,国内100,200元的所谓的机械键盘,还有什么混轴键盘都不靠谱。我不是黑它们,手感出入略大。容易误导大家,所以大家谨慎购买,其实原厂轴400左右就能买到,多花点钱买个放心是杠杠的。对于做工的问题。1.只要是原厂轴标准键位 他们的做工没有实际影响操作体验2.机械键盘都用原厂轴,但是价格差距很大,主要在于软件和功能方面,宏编辑 插口 音频接入输出。等等。其他的在我摸过那么多键盘看来,没有实质区别。3.键帽是可以更换的东西没必要纠结什么材质。4.背光看个人需要。但是背光的键帽材质只能是ABS。但是材质问题参照第三条5.外设是个坑,入水需谨慎^^忘记补充了,如果有人要拿电路板说事,那楼主我还不够专业,也看不懂...水平有限请见谅了,不过我感觉,大厂的电路板焊接应该都不会过于奇葩把。
[硬件产品讨论] 机械键盘扫盲,以及个人使用心得。
好贴。收藏。搂住总结的挺好
再入一把3494,一把F青.我就不再玩外设鸟.
然后你会有新的目标的,加油...我就是这样的...
外设烧起来永无止尽哪。个人用键盘就图个手感以及手感的持久……灯,造型我都无所谓。原厂一把黑黑我就退烧了……
支持科普啊~穷逼只入手了一个机械就是DUCKY S2,主要是喜欢那背光。拿到手后灯光效果还不错,不过按哪哪亮那效果和预想还是有差距。手感方面大件确实太肉,和其他按键感觉可以说是完全不同。用了一周左右,还没出问题。。。
F的话,我抵制日货啊。。。
抵制日货美~
马克留名 外设什么自己用的开心就行
LZ就是那个电脑配件转了大半个地球才到巴西的人吗
一语道破 跪了
鸭子S2用了还几个月了,一天也要切换亮灯模式N次,至今正常...那些把灯闪坏的不知道是怎么用的...
学习下,在针扎中。。。g80-3000感觉性价比很低啊。。。[url=/store/apps/details?id=gov.pianzong.androidnga]----sent from my HTC HTC Desire HD A9191,Android 2.3.5[/url]
F白青 原厂红,LZ赶紧试试。
PS:本人原厂控,但是真心赞F的做工和外形。
学习姿势了,正准备入手个机械键盘
F圣手.....喀嚓喀嚓的声音路过...=.=
既然说到鸭子居然不提KBC?
羞愧没摸过,就不评论了
[quote][tid=5897174]Topic[/tid] [b]Post by 聆聽季風之夜 ( 10:27):[/b]对于做工的问题。1.只要是原厂轴标准键位 他们的做工没有实际影响操作体验2.机械键盘都用原厂轴,但是价格差距很大,主要在于软件和功能方面,宏编辑 插口 音频接入输出。等等。其他的在我摸过那么多键盘看来,没有实质区别。3.键帽是可以更换的东西没必要纠结什么材质。4.背光看个人需要。但是背光的键帽材质只能是ABS。但是材质问题参照第三条5.外设是个坑,入水需谨慎^^..[/quote]键帽是什么材质的很重要.个人喜好不同.有的人喜欢ABS的手感有的人可能就喜欢POM的手感.键帽价格也不便宜.一套好点的键帽都要2.3百了..买的时候就选对了当然好.有米有事没事就换一套的当然就更好了.
背光无爱。F记控飘过。F记白色的真心美的要命。黑茶非常舒服,白红手感不错,很好奇黑轴的什么感觉,有机会入一个。
很好奇传说中的DAS青是什么感觉,求达人说下。在Microsoft Windows 98中,键盘和鼠标是两个标准的使用者输入来源,在一些连贯操作中常产生互补作用。当然,鼠标在今天的应用程序中比十年前使用得更为广泛。甚至在一些应用程序中,我们更习惯于使用鼠标,例如在游戏、画图程序、音乐程序以及Web浏览器等程序中就是这样。然而,我们可以不使用鼠标,但绝对不能从一般的PC中把键盘拆掉。
相对于个人计算机的其它组件,键盘有非常久远的历史,它起源于1874年的第一台Remington打字机。早期的计算机程序员用键盘在Hollerith卡片上打孔,后来在终端机上用键盘直接与大型主机沟通。PC上的键盘在某些方面进行了扩充,加上了功能键、光标移动键和单独的数字键盘,但它们的输入原理基本相同。
您大概已经猜到Windows程序是如何获得键盘输入的:键盘输入以消息的形式传递给程序的窗口消息处理程序。实际上,第一次学习消息时,键盘事件就是一个消息如何将不同型态信息传递给应用程序的显例。
Windows用八种不同的消息来传递不同的键盘事件。这好像太多了,但是(就像我们所看到的一样)程序可以忽略其中至少一半的消息而不会有任何问题。并且,在大多数情况下,这些消息中包含的键盘信息会多于程序所需要的。处理键盘的部分工作就是识别出哪些消息是重要的,哪些是不重要的。
虽然键盘是Windows程序中使用者输入的主要来源,但是程序不必对它接收的所有消息都作出响应。Windows本身也能处理许多键盘功能。
例如,您可以忽略那些属于系统功能的按键,它们通常用到Alt键。程序不必监视这些按键,因为Windows会将按键的作用通知程序(当然,如果程序想这么做,它也能监视这些按键)。虽然呼叫程序菜单的按键将通过窗口的窗口消息处理程序,但通常内定的处理方式是将按键传递给DefWindowProc。最终,窗口消息处理程序将获得一个消息,表示一个菜单项被选择了。通常,这是所有窗口消息处理程序需要知道的(在将介绍菜单)。
有些Windows程序使用「键盘快捷键」来启动通用菜单项。快捷键通常是功能键或字母同Ctrl键的组合(例如,Ctrl-S用于保存文件)。这些键盘快捷键与程序菜单一起在程序的资源描述文件中定义(我们可以在看到)。Windows将这些键盘快捷键转换为菜单命令消息,您不必自己去进行转换。
对话框也有键盘接口,但是当对话框处于活动状态时,应用程序通常不必监视键盘。键盘接口由Windows处理,Windows把关于按键作用的消息发送给程序。对话框可以包含用于输入文字的编辑控件。它们一般是小方框,使用者可以在框中键入字符串。Windows处理所有编辑控件逻辑,并在输入完毕后,将编辑控件的最终内容传送给程序。关于对话框的详细信息,请参见。
编辑控件不必局限于单独一行,而且也不限于只在对话框中。一个在程序主窗口内的多行编辑控件就能够作为一个简单的文字编辑器了(参见、、和的POPPAD程序)。Windows甚至有一个Rich Text文字编辑控件,允许您编辑和显示格式化的文字(请参见/Platform SDK/User Interface Services/Controls/Rich Edit Controls)。
您将会发现,在开发Windows程序时,可以使用处理键盘和鼠标输入的子窗口控件来将较高层的信息传递回父窗口。只要这样的控件用得够多,您就不会因处理键盘消息而烦恼了。
谁获得了焦点
与所有的个人计算机硬件一样,键盘必须由在Windows下执行的所有应用程序共享。有些应用程序可能有多个窗口,键盘必须由该应用程序内的所有窗口共享。
回想一下,程序用来从消息队列中检索消息的MSG结构包括hwnd字段。此字段指出接收消息的窗口控件码。消息循环中的DispatchMessage函数向窗口消息处理程序发送该消息,此窗口消息处理程序与需要消息的窗口相联系。在按下键盘上的键时,只有一个窗口消息处理程序接收键盘消息,并且此消息包括接收消息的窗口控件码。
接收特定键盘事件的窗口具有输入焦点。输入焦点的概念与活动窗口的概念很相近。有输入焦点的窗口是活动窗口或活动窗口的衍生窗口(活动窗口的子窗口,或者活动窗口子窗口的子窗口等等)。
通常很容易辨别活动窗口。它通常是顶层窗口-也就是说,它的父窗口句柄是NULL。如果活动窗口有标题列,Windows将突出显示标题列。如果活动窗口具有对话框架(对话框中很常见的格式)而不是标题列,Windows将突出显示框架。如果活动窗口目前是最小化的,Windows将在工作列中突出显示该项,其显示就像一个按下的按钮。
如果活动窗口有子窗口,那么有输入焦点的窗口既可以是活动窗口也可以是其子窗口。最常见的子窗口有类似以下控件:出现在对话框中的下压按钮、单选钮、复选框、滚动条、编辑方块和清单方块。子窗口不能自己成为活动窗口。只有当它是活动窗口的衍生窗口时,子窗口才能有输入焦点。子窗口控件一般通过显示一个闪烁的插入符号或虚线来表示它具有输入焦点。
有时输入焦点不在任何窗口中。这种情况发生在所有程序都是最小化的时候。这时,Windows将继续向活动窗口发送键盘消息,但是这些消息与发送给非最小化的活动窗口的键盘消息有不同的形式。
窗口消息处理程序通过拦截WM_SETFOCUS和WM_KILLFOCUS消息来判定它的窗口何时拥有输入焦点。WM_SETFOCUS指示窗口正在得到输入焦点,WM_KILLFOCUS表示窗口正在失去输入焦点。我将在本章的后面详细说明这些消息。
队列和同步
当使用者按下并释放键盘上的键时,Windows和键盘驱动程序将硬件扫描码转换为格式消息。然而,这些消息并不保存在消息队列中。实际上,Windows在所谓的「系统消息队列」中保存这些消息。系统消息队列是独立的消息队列,它由Windows维护,用于初步保存使用者从键盘和鼠标输入的信息。只有当Windows应用程序处理完前一个使用者输入消息时,Windows才会从系统消息队列中取出下一个消息,并将其放入应用程序的消息队列中。
此过程分为两步:首先在系统消息队列中保存消息,然后将它们放入应用程序的消息队列,其原因是需要同步。就像我们刚才所学的,假定接收键盘输入的窗口就是有输入焦点的窗口。使用者的输入速度可能比应用程序处理按键的速度快,并且特定的按键可能会使焦点从一个窗口切换到另一个窗口,后来的按键就输入到了另一个窗口。但如果后来的按键已经记下了目标窗口的地址,并放入了应用程序消息队列,那么后来的按键就不能输入到另一个窗口。
按键和字符
应用程序从Windows接收的关于键盘事件的消息可以分为按键和字符两类,这与您看待键盘的两种方式一致。
首先,您可以将键盘看作是键的集合。键盘只有唯一的A键,按下该键是一次按键,释放该键也是一次按键。但是键盘也是能产生可显示字符或控制字符的输入设备。根据Ctrl、 Shift和Caps Lock键的状态,A键能产生几个字符。通常情况下,此字符为小写a。如果按下Shift键或者打开了Caps Lock,则该字符就变成大写A。如果按下了Ctrl,则该字符为Ctrl-A(它在ASCII中有意义,但在Windows中可能是某事件的键盘快捷键)。在一些键盘上,A按键之前可能有「死字符键(dead-character key)」或者Shift、Ctrl或者Alt的不同组合,这些组合可以产生带有音调标记的小写或者大写,例如,à、á、狻⒛、或拧?/p&
对产生可显示字符的按键组合,Windows不仅给程序发送按键消息,而且还发送字符消息。有些键不产生字符,这些键包括shift键、功能键、光标移动键和特殊字符键如Insert和Delete。对于这些键,Windows只产生按键消息。
当您按下一个键时,Windows把WM_KEYDOWN或者WM_SYSKEYDOWN消息放入有输入焦点的窗口的消息队列;当您释放一个键时,Windows把WM_KEYUP或者WM_SYSKEYUP消息放入消息队列中。
WM_KEYDOWN
WM_SYSKEYDOWN
WM_SYSKEYUP
通常「down(按下)」和「up(放开)」消息是成对出现的。不过,如果您按住一个键使得自动重复功能生效,那么当该键最后被释放时,Windows会给窗口消息处理程序发送一系列WM_KEYDOWN(或者WM_SYSKEYDOWN)消息和一个WM_KEYUP(或者WM_SYSKEYUP)消息。像所有放入队列的消息一样,按键消息也有时间信息。通过呼叫GetMessageTime,您可以获得按下或者释放键的相对时间。
系统按键与非系统按键
WM_SYSKEYDOWN和WM_SYSKEYUP中的「SYS」代表「系统」,它表示该按键对Windows比对Windows应用程序更加重要。WM_SYSKEYDOWN和WM_SYSKEYUP消息经常由与Alt相组合的按键产生,这些按键启动程序菜单或者系统菜单上的选项,或者用于切换活动窗口等系统功能(Alt-Tab或者Alt-Esc),也可以用作系统菜单快捷键(Alt键与一个功能键相结合,例如Alt-F4用于关闭应用程序)。程序通常忽略WM_SYSKEYUP和WM_SYSKEYDOWN消息,并将它们传送到DefWindowProc。由于Windows要处理所有Alt键的功能,所以您无需拦截这些消息。您的窗口消息处理程序将最后收到关于这些按键结果(如菜单选择)的其它消息。如果您想在自己的窗口消息处理程序中加上拦截系统按键的程序代码(如本章后面的和程序所作的那样),那么在处理这些消息之后再传送到DefWindowProc,Windows就仍然可以将它们用于通常的目的。
但是,请再考虑一下,几乎所有会影响使用者程序窗口的消息都会先通过使用者窗口消息处理程序。只有使用者把消息传送到DefWindowProc,Windows才会对消息进行处理。例如,如果您将下面几行叙述:
WM_SYSKEYDOWN:
WM_SYSKEYUP:
caseWM_SYSCHAR:
return 0 ;
加入到一个窗口消息处理程序中,那么当您的程序主窗口拥有输入焦点时,就可以有效地阻止所有Alt键操作(我将在本章的后面讨论WM_SYSCHAR),其中包括Alt-Tab、Alt-Esc以及菜单操作。虽然我怀疑您会这么做,但是,我相信您会感到窗口消息处理程序的强大功能。
WM_KEYDOWN和WM_KEYUP消息通常是在按下或者释放不带Alt键的键时产生的,您的程序可以使用或者忽略这些消息,Windows本身并不处理这些消息。
对所有四类按键消息,wParam是虚拟键代码,表示按下或释放的键,而lParam则包含属于按键的其它数据。
虚拟键码保存在WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN和WM_SYSKEYUP消息的wParam参数中。此代码标识按下或释放的键。
哈,又是「虚拟」,您喜欢这个词吗?虚拟指的是假定存在于思想中而不是现实世界中的一些事物,也只有熟练使用DOS汇编语言编写应用程序的程序写作者才有可能指出,为什么对Windows键盘处理如此基本的键码是虚拟的而不是真实的。
对于早期的程序写作者来说,真实的键码由实际键盘硬件产生。在Windows文件中将这些键码称为「扫描码(scan codes)」。在IBM兼容机种上,扫描码16是Q键,17是W键,18是E、19是R,20是T,21是Y等等。这时您会发现,扫描码是依据键盘的实际布局的。Windows开发者认为这些代码过于与设备相关了,于是他们试图通过定义所谓的虚拟键码,以便经由与设备无关的方式处理键盘。其中一些虚拟键码不能在IBM兼容机种上产生,但可能会在其它制造商生产的键盘中找到,或者在未来的键盘上找到。
您使用的大多数虚拟键码的名称在WINUSER.H表头文件中都定义为以VK_开头。表6-2列出了这些名称和数值(十进制和十六进制),以及与虚拟键相对应的IBM兼容机种键盘上的键。下表也标出了Windows执行时是否需要这些键。下表还按数字顺序列出了虚拟键码。
前四个虚拟键码中有三个指的是鼠标键:
WINUSER.H标识符
IBM兼容键盘
VK_LBUTTON
VK_RBUTTON
Ctrl-Break
VK_MBUTTON
您永远都不会从键盘消息中获得这些鼠标键代码。在可以看到,我们能够从鼠标消息中获得它们。VK_CANCEL代码是一个虚拟键码,它包括同时按下两个键(Ctrl-Break)。Windows应用程序通常不使用此键。
表6-3中的键--Backspace、Tab、Enter、Escape和Spacebar-通常用于Windows程序。不过,Windows一般用字符消息(而不是键盘消息)来处理这些键。
WINUSER.H标识符
IBM兼容键盘
Num Lock关闭时的数字键盘5
Enter (或者另一个)
Shift (或者另一个)
VK_CONTROL
Ctrl (或者另一个)
Alt (或者另一个)
VK_CAPITAL
另外,Windows程序通常不需要监视Shift、Ctrl或Alt键的状态。
表6-4列出的前八个码可能是与VK_INSERT和VK_DELETE一起最常用的虚拟键码:
WINUSER.H标识符
IBM兼容键盘
VK_EXECUTE
VK_SNAPSHOT
Print Screen
注意,许多名称(例如VK_PRIOR和VK_NEXT)都与键上的标志不同,而且也与滚动条中的标识符不统一。Print Screen键在平时都被Windows应用程序所忽略。Windows本身响应此键时会将视讯显示的位图影本存放到剪贴板中。假使有键盘提供了VK_SELECT、VK_PRINT、VK_EXECUTE和VK_HELP,大概也没几个人看过那样的键盘。
Windows也包括在主键盘上的字母和数字键的虚拟键码(数字键盘将单独处理)。
WINUSER.H标识符
IBM兼容键盘
主键盘上的0到9
注意,数字和字母的虚拟键码是ASCII码。Windows程序几乎从不使用这些虚拟键码;实际上,程序使用的是ASCII码字符的字符消息。
表6-6所示的代码是由Microsoft Natural Keyboard及其兼容键盘产生的:
WINUSER.H标识符
IBM兼容键盘
左Windows键
右Windows键
Applications键
Windows用VK_LWIN和VK_RWIN键打开「开始」菜单或者(在以前的版本中)启动「工作管理员程序」。这两个都可以用于登录或注销Windows(只在Microsoft Windows NT中有效),或者登录或注销网络(在Windows for Applications中)。应用程序能够通过显示辅助信息或者当成快捷方式键看待来处理application键。
表6-7所示的代码用于数字键盘上的键(如果有的话):
WINUSER.H标识符
IBM兼容键盘
VK_NUMPAD0到VK_ NUMPAD9
NumLock打开时数字键盘上的0到9
VK_MULTIPLY
数字键盘上的*
数字键盘上的+
VK_SEPARATOR
VK_SUBTRACT
数字键盘上的-
VK_DECIMAL
数字键盘上的.
数字键盘上的/
最后,虽然多数的键盘都有12个功能键,但Windows只需要10个,而位旗标却有24个。另外,程序通常用功能键作为键盘快捷键,这样,它们通常不处理表6-8所示的按键:
WINUSER.H标识符
IBM兼容键盘
VK_F1到VK_F10
功能键F1到F10
VK_F11到VK_F24
功能键F11到F24
VK_NUMLOCK
Scroll Lock
另外,还定义了一些其它虚拟键码,但它们只用于非标准键盘上的键,或者通常在大型主机终端机上使用的键。查看/ Platform SDK / User Interface Services / User Input / Virtual-Key Codes,可得到完整的列表。
lParam信息
在四个按键消息(WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN和WM_SYSKEYUP)中,wParam消息参数含有上面所讨论的虚拟键码,而lParam消息参数则含有对了解按键非常有用的其它信息。lParam的32位分为6个字段,如图6-1所示。
图6-1 lParam变量的6个按键消息字段
重复计数是该消息所表示的按键次数,大多数情况下,重复计数设定为1。不过,如果按下一个键之后,您的窗口消息处理程序不够快,以致不能处理自动重复速率(您可以在「控制台」的「键盘」中进行设定)下的按键消息,Windows就把几个WM_KEYDOWN或者WM_SYSKEYDOWN消息组合到单个消息中,并相应地增加重复计数。WM_KEYUP或WM_SYSKEYUP消息的重复计数总是为1。
因为重复计数大于1指示按键速率大于您程序的处理能力,所以您也可能想在处理键盘消息时忽略重复计数。几乎每个人都有文书处理或执行电子表格时画面卷过头的经验,因为多余的按键堆满了键盘缓冲区,所以当程序用一些时间来处理每一次按键时,如果忽略您程序中的重复计数,就能够解决此问题。不过,有时可能也会用到重复计数,您应该尝试使用两种方法执行程序,并从中找出一种较好的方法。
OEM扫描码是由硬件(键盘)产生的代码。这对中古时代的汇编程序写作者来说应该很熟悉,它是从PC相容机种的ROM BIOS服务中所获得的值(OEM指的是PC的原始设备制造商(Original Equipment Manufacturer)及其与「IBM标准」同步的内容)。在此我们不需要更多的信息。除非需要依赖实际键盘布局的样貌,不然Windows程序可以忽略掉几乎所有的OEM扫描码信息,参见。
扩充键旗标
如果按键结果来自IBM增强键盘的附加键之一,那么扩充键旗标为1(IBM增强型键盘有101或102个键。功能键在键盘顶端,光标移动键从数字键盘中分离出来,但在数字键盘上还保留有光标移动键的功能)。对键盘右端的Alt和Ctrl键,以及不是数字键盘那部分的光标移动键(包括Insert和Delete键)、数字键盘上的斜线(/)和Enter键以及Num Lock键等,此旗标均被设定为1。Windows程序通常忽略扩充键旗标。
右按键时,假如同时压下ALT键,那么内容代码为1。对WM_SYSKEYUP与WM_SYSKEYDOWN而言,此位总视为1;而对WM_SYSKEYUP与WM_KEYDOW消息而言,此位为0。除了两个之外:
如果活动窗口最小化了,则它没有输入焦点。这时候所有的按键都会产生WM_SYSKEYUP和WM_SYSKEYDOWN消息。如果Alt键未被按下,则内容代码字段被设定为0。Windows使用WM_SYSKEYUP和WM_SYSKEYDOWN消息,从而使最小化了的活动窗口不处理这些按键。
对于一些外国语文(非英文)键盘,有些字符是通过Shift、Ctrl或者Alt键与其它键相组合而产生的。这时内容代码为1,但是此消息并非系统按键消息。
键的先前状态
如果在此之前键是释放的,则键的先前状态为0,否则为1。对WM_KEYUP或者WM_SYSKEYUP消息,它总是设定为1;但是对WM_KEYDOWN或者WM_SYSKEYDOWN消息,此位可以为0,也可以为1。如果为1,则表示该键是自动重复功能所产生的第二个或者后续消息。
如果键正被按下,则转换状态为0;如果键正被释放,则转换状态为1。对WM_KEYDOWN或者WM_SYSKEYDOWN消息,此字段为0;对WM_KEYUP或者WM_SYSKEYUP消息,此字段为1。
在处理按键消息时,您可能需要知道是否按下了位移键(Shift、Ctrl和Alt)或开关键(Caps Lock、Num Lock和Scroll Lock)。通过呼叫GetKeyState函数,您就能获得此信息。例如:
iState = GetKeyState (VK_SHIFT) ;
如果按下了Shift,则iState值为负(即设定了最高位置位)。如果Caps Lock键打开,则从
iState = GetKeyState (VK_CAPITAL) ;
传回的值低位被设为1。此位与键盘上的小灯保持一致。
通常,您在使用GetKeyState时,会带有虚拟键码VK_SHIFT、VK_CONTROL和VK_MENU(在说明Alt键时呼叫)。使用GetKeyState时,您也可以用下面的标识符来确定按下的Shift、Ctrl或Alt键是左边的还是右边的:VK_LSHIFT、VK_RSHIFT、VK_LCONTROL、VK_RCONTROL、VK_LMENU、VK_RMENU。这些标识符只用于GetKeyState和GetAsyncKeyState(下面将详细说明)。
使用虚拟键码VK_LBUTTON、VK_RBUTTON和VK_MBUTTON,您也可以获得鼠标键的状态。不过,大多数需要监视鼠标键与按键相组合的Windows应用程序都使用其它方法来做到这一点-即在接收到鼠标消息时检查按键。实际上,位移状态信息包含在鼠标信息中,正如您在中将看到的一样。
请注意GetKeyState的使用,它并非实时检查键盘状态,而只是检查直到目前为止正在处理的消息的键盘状态。多数情况下,这正符合您的要求。如果您需要确定使用者是否按下了Shift-Tab,请在处理Tab键的WM_KEYDOWN消息时呼叫GetKeyState,带有参数VK_SHIFT。如果GetKeyState传回的值为负,那么您就知道在按下Tab键之前按下了Shift键。并且,如果在您开始处理Tab键之前,已经释放了Shift键也没有关系。您知道,在按下Tab键的时候Shift键是按下的。
GetKeyState不会让您获得独立于普通键盘消息的键盘信息。例如,您或许想暂停窗口消息处理程序的处理,直到您按下F1功能键为止:
while (GetKeyState (VK_F1) &= 0) ;
// WRONG !!!
不要这么做!这将让程序当死(除非在执行此叙述之前早就从消息队列中接收到了F1的WM_KEYDOWN)。如果您确实需要知道目前某键的状态,那么您可以使用GetAsyncKeyState。
使用按键消息
如果程序能够获得每个按键的信息,这当然很理想,但是大多数Windows程序忽略了几乎所有的按键,而只处理部分的按键消息。WM_SYSKEYDOWN和WM_SYSKEYUP消息是由Windows系统函数使用的,您不必为此费心,就算您要处理WM_KEYDOWN消息,通常也可以忽略WM_KEYUP消息。
Windows程序通常为不产生字符的按键使用WM_KEYDOWN消息。虽然您可能认为借助按键消息和位移键状态信息能将按键消息转换为字符消息,但是不要这么做,因为您将遇到国际键盘间的差异所带来的问题。例如,如果您得到wParam等于0x33的WM_KEYDOWN消息,您就可以知道使用者按下了键3,到此为止一切正常。这时,如果用GetKeyState发现Shift键被按下,您就可能会认为使用者输入了#号,这可不一定。比如英国使用者就是在输入£。
对于光标移动键、功能键、Insert和Delete键,WM_KEYDOWN消息是最有用的。不过, Insert、Delete和功能键经常作为菜单快捷键。因为Windows能把菜单快捷键翻译为菜单命令消息,所以您就不必自己来处理按键。
在Windows之前的MS-DOS应用程序中大量使用功能键与Shift、Ctrl和Alt键的组合,同样地,您也可以在Windows程序中使用(实际上,Microsoft Word将大量的功能键用作命令快捷方式),但并不推荐这样做。如果您确实希望使用功能键,那么这些键应该是重复菜单命令。Windows的目标之一就是提供不需要记忆或者使用复杂命令流程的使用者接口。
因此,可以归纳如下:多数情况下,您将只为光标移动键(有时也为Insert和Delete键)处理WM_KEYDOWN消息。在使用这些键的时候,您可以通过GetKeyState来检查Shift键和Ctrl键的状态。例如,Windows程序经常使用Shift与光标键的组合键来扩大文书处理里选中的范围。Ctrl键常用于修改光标键的意义。例如,Ctrl与右箭头键相组合可以表示光标右移一个字。
决定您的程序中使用键盘方式的最佳方法之一是了解现有的Windows程序使用键盘的方式。如果您不喜欢那些定义,当然可以对其加以修改,但是这样做不利于其它人很快地学会使用您的程序。
为SYSMETS加上键盘处理功能
在编写中三个版本的SYSMETS程序时,我们还不了解键盘,只能使用滚动条和鼠标来卷动文字。现在我们知道了处理键盘消息的方法,那么不妨在程序中加入键盘接口。显然,这是处理光标移动键的工作。我们将大多数光标键(Home、End、Page Up、Page Down、Up Arrow和Down Arrow)用于垂直卷动,左箭头键和右箭头键用于不太重要的水平卷动。
建立键盘接口的一种简单方法是在窗口消息处理程序中加入与WM_VSCROLL和WM_HSCROLL处理方式相仿,而且本质上相同的WM_KEYDOWN处理方法。不过这样子做是不聪明的,因为如果要修改滚动条的做法,就必须相对应地修改WM_KEYDOWN。
为什么不简单地将每一种WM_KEYDOWN消息都翻译成同等效用的WM_VSCROLL或者WM_HSCROLL消息呢?通过向窗口消息处理程序发送假冒消息,我们可能会让WndProc认为它获得了卷动信息。
在Windows中,这种方法是可行的。发送消息的函数叫做SendMessage,它所用的参数与传递到窗口消息处理程序的参数是相同的:
SendMessage (hwnd, message, wParam, lParam) ;
在呼叫SendMessage时,Windows呼叫窗口句柄为hwnd的窗口消息处理程序,并把这四个参数传给它。当窗口消息处理程序完成消息处理之后,Windows把控制传回到SendMessage呼叫之后的下一道叙述。您发送消息过去的窗口消息处理程序,可以是同一个窗口消息处理程序、同一程序中的其它窗口消息处理程序或者其它应用程序,中的窗口消息处理程序。
下面说明在SYSMETS程序中使用SendMessage处理WM_KEYDOWN代码的方法:
caseWM_KEYDOWN:
switch (wParam)
SendMessage (hwnd, WM_VSCROLL, SB_TOP, 0) ;
SendMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0) ;
SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0) ;
至此,您已经有了大概观念了吧。我们的目标是为滚动条添加键盘接口,并且也正在这么做。通过把卷动消息发送到窗口消息处理程序,我们实作了用光标移动键进行卷动列的功能。现在您知道在SYSMETS3中为WM_VSCROLL消息加上SB_TOP和SB_BOTTOM处理码的原因了吧。在那里并没有用到它,但是现在处理Home和End键时就有用了。如程序6-1所示的SYSENTS4就加上了这些变化。编译这个程序时还需要用到文件。
程序6-1 SYSMETS4
SYSMETS4.C
/*----------------------------------------------------------------------
SYSMETS4.C -- System Metrics Display Program No. 4
(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 ("SysMets4") ;
wndclass.style
= CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc
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= NULL ;
wndclass.lpszClassName= szAppN
if (!RegisterClass (&wndclass))
MessageBox (NULL, TEXT ("Program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
hwnd = CreateWindow (szAppName, TEXT ("Get System Metrics No. 4"),
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 msg.wP
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
static int
cxChar, cxCaps, cyChar, cxClient, cyClient, iMaxW
i, x, y, iVertPos, iHorzPos, iPaintBeg, iPaintE
PAINTSTRUCT
SCROLLINFO
szBuffer[10] ;
TEXTMETRIC
switch (message)
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) ;
// Save the width of the three columns
iMaxWidth = 40 * cxChar + 22 * cxC
return 0 ;
= LOWORD (lParam) ;
= HIWORD (lParam) ;
// Set vertical scroll bar range and page size
= sizeof (si) ;
= SIF_RANGE | SIF_PAGE ;
= NUMLINES - 1 ;
= cyClient / cyC
SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ;
// Set horizontal scroll bar range and page size
= sizeof (si) ;
= SIF_RANGE | SIF_PAGE ;
= 2 + iMaxWidth / cxC
= cxClient / cxC
SetScrollInfo (hwnd, SB_HORZ, &si, TRUE) ;
return 0 ;
case WM_VSCROLL:
// Get all the vertical scroll bar information
= sizeof (si) ;
= SIF_ALL ;
GetScrollInfo (hwnd, SB_VERT, &si) ;
// Save the position for comparison later on
iVertPos = si.nP
switch (LOWORD (wParam))
case SB_TOP:
si.nPos = si.nM
case SB_BOTTOM:
si.nPos = si.nM
case SB_LINEUP:
si.nPos -= 1 ;
case SB_LINEDOWN:
si.nPos += 1 ;
case SB_PAGEUP:
si.nPos -= si.nP
case SB_PAGEDOWN:
si.nPos += si.nP
case SB_THUMBTRACK:
si.nPos = si.nTrackP
// Set the position and then retrieve it.
Due to adjustments
by Windows it might not be the same as the value set.
si.fMask = SIF_POS ;
SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ;
GetScrollInfo (hwnd, SB_VERT, &si) ;
// If the position has changed, scroll the window and update it
if (si.nPos != iVertPos)
ScrollWindow (hwnd, 0, cyChar * (iVertPos - si.nPos),
NULL, NULL) ;
UpdateWindow (hwnd) ;
return 0 ;
case WM_HSCROLL:
// Get all the vertical scroll bar information
= sizeof (si) ;
= SIF_ALL ;
// Save the position for comparison later on
GetScrollInfo (hwnd, SB_HORZ, &si) ;
switch (LOWORD (wParam))
case SB_LINELEFT:
si.nPos -= 1 ;
case SB_LINERIGHT:
si.nPos += 1 ;
case SB_PAGELEFT:
si.nPos -= si.nP
case SB_PAGERIGHT:
si.nPos += si.nP
case SB_THUMBPOSITION:
si.nPos = si.nTrackP
// Set the position and then retrieve it.
Due to adjustments
by Windows it might not be the same as the value set.
si.fMask = SIF_POS ;
SetScrollInfo (hwnd, SB_HORZ, &si, TRUE) ;
GetScrollInfo (hwnd, SB_HORZ, &si) ;
// If the position has changed, scroll the window
if (si.nPos != iHorzPos)
ScrollWindow (hwnd, cxChar * (iHorzPos - si.nPos), 0,
NULL, NULL) ;
return 0 ;
case WM_KEYDOWN:
switch (wParam)
case VK_HOME:
SendMessage (hwnd, WM_VSCROLL, SB_TOP, 0) ;
case VK_END:
SendMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0) ;
case VK_PRIOR:
SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0) ;
case VK_NEXT:
SendMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0) ;
case VK_UP:
SendMessage (hwnd, WM_VSCROLL, SB_LINEUP, 0) ;
case VK_DOWN:
SendMessage (hwnd, WM_VSCROLL, SB_LINEDOWN, 0) ;
case VK_LEFT:
SendMessage (hwnd, WM_HSCROLL, SB_PAGEUP, 0) ;
case VK_RIGHT:
SendMessage (hwnd, WM_HSCROLL, SB_PAGEDOWN, 0) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
// Get vertical scroll bar position
= sizeof (si) ;
= SIF_POS ;
GetScrollInfo (hwnd, SB_VERT, &si) ;
// Get horizontal scroll bar position
GetScrollInfo (hwnd, SB_HORZ, &si) ;
// Find painting limits
= max (0, iVertPos + ps.rcPaint.top / cyChar) ;
= min (NUMLINES - 1,
iVertPos + ps.rcPaint.bottom / cyChar) ;
for (i = iPaintB i &= iPaintE i++)
x = cxChar * (1 - iHorzPos) ;
y = cyChar * (i - iVertPos) ;
TextOut (hdc, x, y,
sysmetrics[i].szLabel,
lstrlen (sysmetrics[i].szLabel)) ;
TextOut (hdc, x + 22 * cxCaps, y,
sysmetrics[i].szDesc,
lstrlen (sysmetrics[i].szDesc)) ;
SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;
TextOut (hdc, x + 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 ;
WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
return DefWindowProc (hwnd, message, wParam, lParam) ;
前面讨论了利用位移状态信息把按键消息翻译为字符消息的方法,并且提到,仅利用转换状态信息还不够,因为还需要知道与国家/地区有关的键盘配置。由于这个原因,您不应该试图把按键消息翻译为字符代码。Windows会为您完成这一工作,在前面我们曾看到过以下的程序代码:
(GetMessage (&msg, NULL, 0, 0))
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
这是WinMain中典型的消息循环。GetMessage函数用队列中的下一个消息填入msg结构的字段。DispatchMessage以此消息为参数呼叫适当的窗口消息处理程序。
在这两个函数之间是TranslateMessage函数,它将按键消息转换为字符消息。如果消息为WM_KEYDOWN或者WM_SYSKEYDOWN,并且按键与位移状态相组合产生一个字符,则TranslateMessage把字符消息放入消息队列中。此字符消息将是GetMessage从消息队列中得到的按键消息之后的下一个消息。
四类字符消息
字符消息可以分为四类,如表6-9所示。
非系统字符
WM_DEADCHAR
WM_SYSCHAR
WM_SYSDEADCHAR
WM_CHAR和WM_DEADCHAR消息是从WM_KEYDOWN得到的;而WM_SYSCHAR和WM_SYSDEADCHAR消息是从WM_SYSKEYDOWN消息得到的(我将简要地讨论一下什么是死字符)。
有一个好消息:在大多数情况下,Windows程序会忽略除WM_CHAR之外的任何消息。伴随四个字符消息的lParam参数与产生字符代码消息的按键消息之lParam参数相同。不过,参数wParam不是虚拟键码。实际上,它是ANSI或Unicode字符代码。
这些字符消息是我们将文字传递给窗口消息处理程序时遇到的第一个消息。它们不是唯一的消息,其它消息伴随以0结尾的整个字符串。窗口消息处理程序是如何知道该字符是8位的ANSI字符还是16位的Unicode宽字符呢?很简单:任何与您用RegisterClassA(RegisterClass的ANSI版)注册的窗口类别相联系的窗口消息处理程序,都会获得含有ANSI字符代码的消息。如果窗口消息处理程序用RegisterClassW(RegisterClass的宽字符版)注册,那么传递给窗口消息处理程序的消息就带有Unicode字符代码。如果程序用RegisterClass注册窗口类别,那么在UNICODE标识符被定义时就呼叫RegisterClassW,否则呼叫RegisterClassA。
除非在程序写作的时候混合了ANSI和Unicode的函数与窗口消息处理程序,用WM_CHAR消息(及其它三种字符消息)说明的字符代码将是:
(TCHAR) wParam
同一个窗口消息处理程序可能会用到两个窗口类别,一个用RegisterClassA注册,而另一个用RegisterClassW注册。也就是说,窗口消息处理程序可能会获得一些ANSI字符代码消息和一些Unicode字符代码消息。如果您的窗口消息处理程序需要晓得目前窗口是否处理Unicode消息,则它可以呼叫:
fUnicode = IsWindowUnicode (hwnd) ;
如果hwnd的窗口消息处理程序获得Unicode消息,那么变量fUnicode将为TRUE,这表示窗口是用RegisterClassW注册的窗口类别。
因为TranslateMessage函数从WM_KEYDOWN和WM_SYSKEYDOWN消息产生了字符消息,所以字符消息是夹在按键消息之间传递给窗口消息处理程序的。例如,如果Caps Lock未打开,而使用者按下再释放A键,则窗口消息处理程序将接收到如表6-10所示的三个消息:
按键或者代码
WM_KEYDOWN
「A」的虚拟键码(0x41)
「a」的字符代码(0x61)
「A」的虚拟键码(0x41)
如果您按下Shift键,再按下A键,然后释放A键,再释放Shift键,就会输入大写的A,而窗口消息处理程序会接收到五个消息,如表6-11所示:
按键或者代码
WM_KEYDOWN
虚拟键码VK_SHIFT (0x10)
WM_KEYDOWN
「A」的虚拟键码(0x41)
「A」的字符代码(0x41)
「A」的虚拟键码(0x41)
虚拟键码VK_SHIFT(0x10)
Shift键本身不产生字符消息。
如果使用者按住A键,以使自动重复产生一系列的按键,那么对每条WM_KEYDOWN消息,都会得到一条字符消息,如表6-12所示:
按键或者代码
WM_KEYDOWN
「A」的虚拟键码(0x41)
「a」的字符代码(0x61)
WM_KEYDOWN
「A」的虚拟键码(0x41)
「a」的字符代码(0x61)
WM_KEYDOWN
「A」的虚拟键码(0x41)
「a」的字符代码(0x61)
WM_KEYDOWN
「A」的虚拟键码(0x41)
「a」的字符代码(0x61)
「A」的虚拟键码(0x41)
如果某些WM_KEYDOWN消息的重复计数大于1,那么相应的WM_CHAR消息将具有同样的重复计数。
组合使用Ctrl键与字母键会产生从0x01(Ctrl-A)到0x1A(Ctrl-Z)的ASCII控制代码,其中的某些控制代码也可以由表6-13列出的键产生:
ANSI C控制字符
Ctrl-Enter
最右列给出了在ANSI C中定义的控制字符,它们用于描述这些键的字符代码。
有时Windows程序将Ctrl与字母键的组合用作菜单快捷键(我将在讨论),此时,不会将字母键转换成字符消息。
处理控制字符
处理按键和字符消息的基本规则是:如果需要读取输入到窗口的键盘字符,那么您可以处理WM_CHAR消息。如果需要读取光标键、功能键、Delete、Insert、Shift、Ctrl以及Alt键,那么您可以处理WM_KEYDOWN消息。
但是Tab键怎么办?Enter、Backspace和Escape键又怎么办?传统上,这些键都产生表6-13列出的ASCII控制字符。但是在Windows中,它们也产生虚拟键码。这些键应该在处理WM_CHAR或者在处理WM_KEYDOWN期间处理吗?
经过10年的考虑(回顾这些年来我写过的Windows程序代码),我更喜欢将Tab、Enter、Backspace和Escape键处理成控制字符,而不是虚拟键。我通常这样处理WM_CHAR:
case WM_CHAR:
//其它行程序
switch (wParam)
case '\b':
// backspace
//其它行程序
case '\t':
//其它行程序
case '\n':
// linefeed
//其它行程序
case '\r':
// carriage return
//其它行程序
// character codes
//其它行程序
return 0 ;
死字符消息
Windows程序经常忽略WM_DEADCHAR和WM_SYSDEADCHAR消息,但您应该明确地知道死字符是什么,以及它们工作的方式。
在某些非U.S.英语键盘上,有些键用于给字母加上音调。因为它们本身不产生字符,所以称之为「死键」。例如,使用德语键盘时,对于U.S.键盘上的+/=键,德语键盘的对应位置就是一个死键,未按下Shift键时它用于标识锐音,按下Shift键时则用于标识抑音。
当使用者按下这个死键时,窗口消息处理程序接收到一个wParam等于音调本身的ASCII或者Unicode代码的WM_DEADCHAR消息。当使用者再按下可以带有此音调的字母键(例如A键)时,窗口消息处理程序会接收到WM_CHAR消息,其中wParam等于带有音调的字母「a」的ANSI代码。
因此,使用者程序不需要处理WM_DEADCHAR消息,原因是WM_CHAR消息已含有程序所需要的所有信息。Windows的做法甚至还设计了内部错误处理。如果在死键之后跟有不能带此音调符号的字母(例如「s」),那么窗口消息处理程序将在一行接收到两条WM_CHAR消息-前一个消息的wParam等于音调符号本身的ASCII代码(与传递到WM_DEADCHAR消息的wParam值相同),第二个消息的wParam等于字母s的ASCII代码。
当然,要感受这种做法的运作方式,最好的方法就是实际操作。您必须加载使用死键的外语键盘,例如前面讲过的德语键盘。您可以这样设定:在「控制台」中选择「键盘」,然后选择「语系」页面标签。然后您需要一个应用程序,该程序可以显示它接收的每一个键盘消息的详细信息。下面的KEYVIEW1就是这样的程序。
键盘消息和字符集
本章剩下的范例程序有缺陷。它们不能在所有版本的Windows下都正常执行。这些缺陷不是特意引过程序代码中的;事实上,您也许永远不会遇到这些缺陷。只有在不同的键盘语言和键盘布局间切换,以及在多字节字符集的远东版Windows下执行程序时,这些问题才会出现-所以我不愿将它们称为「错误」。
不过,如果程序使用Unicode编译并在Windows NT下执行,那么程序会执行得更好。我在提到过这个问题,并且展示了Unicode对简化棘手的国际化问题的重要性。
KEYVIEW1程序
了解键盘国际化问题的第一步,就是检查Windows传递给窗口消息处理程序的键盘内容和字符消息。程序6-2所示的KEYVIEW1会对此有所帮助。该程序在显示区域显示Windows向窗口消息处理程序发送的8种不同键盘消息的全部信息。
程序6-2 KEYVIEW1
KEYVIEW1.C
/*---------------------------------------------------------------------
KEYVIEW1.C --Displays Keyboard and Character Messages
(c) Charles Petzold, 1998
---------------------------------------------------------------------*/
#include &windows.h&
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
static TCHAR szAppName[]
= TEXT ("KeyView1") ;
wndclass.style
= CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc
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 ("Keyboard Message Viewer #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
cxClientMax, cyClientMax, cxClient, cyClient, cxChar, cyC
static int
cLinesMax, cL
static PMSG
static RECT
static TCHAR szTop[] = TEXT ("Message Key
TEXT ("Repeat Scan Ext ALT Prev Tran") ;
static TCHAR szUnd[] = TEXT ("_______
TEXT ("______ ____ ___ ___ ____ ____") ;
static TCHAR * szFormat[2] = {
TEXT ("%-13s %3d %-15s%c%6u %4d %3s %3s %4s %4s"),
TEXT ("%-13s
0x%04X%1s%c %6u %4d %3s %3s %4s %4s") } ;
static TCHAR * szYes
= TEXT ("Yes") ;
static TCHAR * szNo
= TEXT ("No") ;
static TCHAR * szDown = TEXT ("Down") ;
static TCHAR * szUp
= TEXT ("Up") ;
static TCHAR * szMessage [] = {
TEXT ("WM_KEYDOWN"),
TEXT ("WM_KEYUP"),
TEXT ("WM_CHAR"),
TEXT ("WM_DEADCHAR"),
TEXT ("WM_SYSKEYDOWN"),TEXT ("WM_SYSKEYUP"),
TEXT ("WM_SYSCHAR"),
TEXT ("WM_SYSDEADCHAR") } ;
PAINTSTRUCT
szBuffer[128], szKeyName [32] ;
TEXTMETRIC
switch (message)
case WM_CREATE:
case WM_DISPLAYCHANGE:
// Get maximum size of client area
cxClientMax = GetSystemMetrics (SM_CXMAXIMIZED) ;
cyClientMax = GetSystemMetrics (SM_CYMAXIMIZED) ;
// Get character size for fixed-pitch font
hdc = GetDC (hwnd) ;
SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharW
cyChar = tm.tmH
ReleaseDC (hwnd, hdc) ;
// Allocate memory for display lines
free (pmsg) ;
cLinesMax = cyClientMax / cyC
pmsg = malloc (cLinesMax * sizeof (MSG)) ;
cLines = 0 ;
// fall through
case WM_SIZE:
if (message == WM_SIZE)
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
// Calculate scrolling rectangle
rectScroll.left
rectScroll.right
rectScroll.top
rectScroll.bottom
= cyChar * (cyClient / cyChar) ;
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
case WM_KEYDOWN:
case WM_KEYUP:
case WM_CHAR:
case WM_DEADCHAR:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_SYSCHAR:
case WM_SYSDEADCHAR:
// Rearrange storage array
for (i = cLinesMax - 1 ; i & 0 ; i--)
pmsg[i] = pmsg[i - 1] ;
// Store new message
pmsg[0].hwnd =
pmsg[0].message =
pmsg[0].wParam = wP
pmsg[0].lParam = lP
cLines = min (cLines + 1, cLinesMax) ;
// Scroll up the display
ScrollWindow (hwnd, 0, -cyChar, &rectScroll, &rectScroll) ;
// i.e., call DefWindowProc so Sys messages work
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
SetBkMode (hdc, TRANSPARENT) ;
TextOut (hdc, 0, 0, szTop, lstrlen (szTop)) ;
TextOut (hdc, 0, 0, szUnd, lstrlen (szUnd)) ;
for (i = 0 ; i & min (cLines, cyClient / cyChar - 1) ; i++)
pmsg[i].message == WM_CHAR ||
pmsg[i].message == WM_SYSCHAR ||
pmsg[i].message == WM_DEADCHAR ||
pmsg[i].message == WM_SYSDEADCHAR ;
GetKeyNameText (pmsg[i].lParam, szKeyName,
sizeof (szKeyName) / sizeof (TCHAR)) ;
TextOut (hdc, 0, (cyClient / cyChar - 1 - i) * cyChar, szBuffer,
wsprintf (szBuffer, szFormat [iType],
szMessage [pmsg[i].message - WM_KEYFIRST],
pmsg[i].wParam,
(PTSTR) (iType ? TEXT (" ") : szKeyName),
(TCHAR) (iType ? pmsg[i].wParam : ' '),
LOWORD (pmsg[i].lParam),
HIWORD (pmsg[i].lParam) & 0xFF,
0x & pmsg[i].lParam ? szYes
0x & pmsg[i].lParam ? szYes
0x & pmsg[i].lParam ? szDown : szUp,
0x & pmsg[i].lParam ? szUp
: szDown)) ;
EndPaint (hwnd, &ps) ;
return 0 ;
WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
return DefWindowProc (hwnd, message, wParam, lParam) ;
KEYVIEW1显示窗口消息处理程序接收到的每次按键和字符消息的内容,并将这些消息储存在一个MSG结构的数组中。该数组的大小依据最大化窗口的大小和等宽的系统字体。如果使用者在程序执行时调整了视讯显示的大小(在这种情况下KEYVIEW1接收WM_DISPLAYCHANGE消息),将重新分配此数组。KEYVIEW1使用标准C的malloc函数为数组配置内存。
图6-2给出了在键入「Windows」之后KEYVIEW1的屏幕显示。第一列显示了键盘消息;第二列在键名称的前面显示了按键消息的虚拟键代码,此代码是经由GetKeyNameText函数取得的;第三列(标注为「Char」)在字符本身的后面显示字符消息的十六进制字符代码。其余六列显示了lParam消息参数中六个字段的状态。
图6-2 KEYVIEW1的屏幕显示
为便于以分行的方式显示此信息,KEYVIEW1使用了等宽字体。与所讨论的一样,这需要呼叫GetStockObject和SelectObject:
SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
KEYVIEW1在显示区域上部画了一个标题以确定分成九行。此列文字带有底线。虽然可以建立一种带底线的字体,但这里使用了另一种方法。我定义了两个字符串变量szTop(有文字)和szUnd(有底线),并在WM_PAINT消息处理期间将它们同时显示在窗口顶部的同一位置。通常,Windows以一种「不透明」的方式显示文字,也就是说显示字符时Windows将擦除字符背景区。这将导致第二个字符串(szUnd)擦除掉前一个(szTop)。要防止这一现象的发生,可将设备内容切换到「透明」模式:
SetBkMode (hdc, TRANSPARENT) ;
这种加底线的方法只有在使用等宽字体时才可行。否则,底线字符将无法与显现在底线上面的字符等宽。
外语键盘问题
如果您执行美国英语版本的Windows,那么您可安装不同的键盘布局,并输入外语。可以在 控制台的键盘中安装外语键盘布局。选择 语系页面标签,按下新增 键。要查看死键的工作方式,您可能想安装「德语」键盘。此外,我还要讨论「俄语」和「希腊语」的键盘布局,因此您也可安装这些键盘布局。如果在「键盘」显示的列表中找不到「俄语」和「希腊语」的键盘布局,则需要安装多语系支持:从「控制台」中选择 新增/删除程序,然后选择 Windows安装程序页面卷标,确认选中 多语系支持复选框。在任何情况下,这些变更都需要原始的Windows光盘。
安装完其它键盘布局后,您将在工作列右侧的通知区看到一个带有两个字母代码的蓝色框。如果内定的是英语,那么这两个字母是「EN」。单击此图标,将得到所有已安装键盘布局的列表。从中单击需要的键盘布局即可更改目前活动程序的键盘。此改变只影响目前活动的程序。
现在开始进行实验。不使用UNICODE标识符定义来编译KEYVIEW1程序(在本书附带的光盘中,非Unicode版本的KEYVIEW1程序位于RELEASE子目录)。在美国英语版本的Windows下执行该程序,并输入字符『abcde』。 WM_CHAR消息与您所期望的一样:ASCII字符代码0x61、0x62、0x63、0x64和0x65以及字母a、b、c、d和e。
现在,KEYVIEW1还在执行,选择德语键盘布局。按下=键然后输入一个元音(a、e、i、o或者u)。=键将产生一个WM_DEADCHAR消息,元音产生一个WM_CHAR消息和(单独的)字符代码0xE1、0xE9、0xED、0xF3、0xFA和字符á、é、í、ó或ú。这就是死键的工作方式。
现在选择希腊键盘布局。输入『abcde』,您会得到什么?您将得到WM_CHAR消息和字符代码0xE1、0xE2、0xF8、0xE4、0xE5和字符á、狻ⅱ、?#160;和濉T谡饫镉行┳址?荒苷?废允尽D训滥?挥Ω玫玫较@白帜副碇械淖帜嘎穑?/p&
现在切换到俄语键盘并重新输入『abcde』。现在您得到WM_CHAR消息和字符代码0xF4、0xE8、0xF1、0xE2和0xF3,以及字符簟ⅷāⅠ、?#160;和ó。而且,还是有些字母不能正常显示。您应从斯拉夫字母表中得到这些字母。
问题在于:您已经切换键盘以产生不同的字符代码,但您还没有将此切换通知GDI,好让GDI能选择适当的符号来显示解释这些字符代码。
如果您非常勇敢,还有可用的备用PC,并且是专业或全球版Microsoft Developer Network(MSDN)的订阅户,那么您也许想安装(例如)希腊版的Windows,您还可以把那四种键盘布局(英语、希腊语、德语和俄语)安装上去。现在执行KEYLOOK1,切换到英语键盘布局,然后输入『abcde』。您应得到ASCII字符代码0x61、0x62、0x63、0x64和0x65以及字符a、b、c、d和e(并且您可以放心:即使在希腊版,ASCII还是正常通行的)。
在希腊版的Windows中,切换到希腊键盘布局并输入『abcde』。您将得到WM_CHAR消息和字符代码0xE1、0xE2、0xF8、0xE4和0xE5。这与您在安装希腊键盘布局的英语版Windows中得到的字符代码相同。但现在显示的字符是?、?、?、?和?。这些确实是小写的希腊字母alpha、beta、psi、delta和epsilon(gamma怎么了?是这样,如果使用希腊版的Windows,那么您将使用键帽上带有希腊字母的键盘。与英语c相对应的键正好是psi。gamma由与英语g相对应的键产生。您可在Nadine Kano编写的《Developing International Software for Windows 95 and Windows NT》的第587页看到完整的希腊字母表)。
继续在希腊版的Windows下运行KEYVIEW1,切换到德语键盘布局。输入『=』键,然后依次输入a、e、i、o和u。您将得到WM_CHAR消息和字符代码0xE1、0xE9、0xED、0xF3和0xFA。这些字符代码与安装德语键盘布局的英语版Windows中的一样。不过,显示的字符却是?、?、?、?和?,而不是正确的á、é、í、ó和ú。
现在切换到俄语键盘并输入『abcde』。您会得到字符代码0xF4、0xE8、0xF1、0xE2和0xF3,这与安装俄语键盘的英语版Windows中得到的一样。不过,显示的字符是?、?、?、?和?,而不是斯拉夫字母表中的字母。
您还可安装俄语版的Windows。现在您可以猜到,英语和俄语键盘都可以工作,而德语和希腊语则不行。
现在,如果您真的很勇敢,您还可安装日语版的Windows并执行KEYVIEW1。如果再依美国键盘输入,那么您将输入英语文字,一切似乎都正常。不过,如果切换到德语、希腊语或者俄语键盘布局,并且试著作上述介绍的任何练习,您将看到以点显示的字符。如果输入大写的字母-无论是带重音符号的德语字母、希腊语字母还是俄语字母-您将看到这些字母显示为日语中用于拼写外来语的片假名。您也许对输入片假名感兴趣,但那不是德语、希腊语或者俄语。
远东版本的Windows包括一个称作「输入法编辑器」(IME)的实用程序,该程序显示为浮动的工具列,它允许您用标准键盘输入象形文字,即汉语、日语和朝鲜语中使用的复杂字符。一般来说,输入一组字母后,组成的字符将显示在另一个浮动窗口内。然后按 Enter键,合成的字符代码就发送到了活动窗口(即KEYVIEW1)。KEYVIEW1几乎没什么响应-WM_CHAR消息带来的字符代码大于128,但这些代码没有意义(Nadine Kano的书中有许多关于使用IME的内容)。
这时,我们已经看到了许多KEYLOOK1显示错误字符的例子-当执行安装了俄语或希腊语键盘布局的英语版Windows时,当执行安装了俄语或德语键盘布局的希腊版Windows时,以及执行安装了德语、俄语或者希腊语键盘布局的俄语版Windows时,都是这样。我们也看到了从日语版Windows的输入法编辑器输入字符时的错误显示。
字符集和字体
KEYLOOK1的问题是字体问题。用于在屏幕上显示字符的字体和键盘接收的字符代码不一致。因此,让我们看一下字体。
我将在进行详细讨论,Windows支持三类字体-点阵字体、向量字体和(从Windows 3.1开始的)TrueType字体。
事实上向量字体已经过时了。这些字体中的字符由简单的线段组成,但这些线段没有定义填入区域。向量字体可以较好地缩放到任意大小,但字符通常看上去有些单薄。
TrueType字体是定义了填入区域的文字轮廓字体。TrueType字体可缩放;而且该字符的定义包括「提示」,以消除可能带来的文字不可见或者不可读的圆整问题。使用TrueType字体,Windows就真正实现了WYSIWYG(「所见即所得」),即文字在视讯显示器显示与打印机输出完全一致。
在点阵字体中,每个字符都定义为与视讯显示器上的图素对应的位点阵。点阵字体可拉伸到较大的尺寸,但看上去带有锯齿。点阵字体通常被设计成方便在视讯显示器上阅读的字体。因此,Windows中的标题列、菜单、按钮和对话框的显示文字都使用点阵字体。
在内定的设备内容下获得的点阵字体称为系统字体。您可通过呼叫带有SYSTEM_FONT标识符的GetStockObject函数来获得字体句柄。KEYVIEW1程序选择使用SYSTEM_FIXED_FONT表示的等宽系统字体。GetStockObject函数的另一个选项是OEM_FIXED_FONT。
这三种字体有(各自的)字体名称-System、FixedSys和Terminal。程序可以在CreateFont或者CreateFontIndirect函数呼叫中使用字体名称来指定字体。这三种字体储存在两组放在Windows目录内的FONTS子目录下的三个文件中。Windows使用哪一组文件取决于「控制台」里的「显示器」是选择显示「小字体」还是「大字体」(亦即,您希望Windows假定视讯显示器是96 dpi的分辨率还是120 dpi的分辨率)。表6-14总结了所有的情况:
GetStockObject标识符
小字体文件
大字体文件
SYSTEM_FONT
VGASYS.FON
8514SYS.FON
SYSTEM_FIXED_FONT
VGAFIX.FON
8514FIX.FON
OEM_FIXED_FONT
VGAOEM.FON
8514OEM.FON
在文件名称中,「VGA」指的是视频图形数组(Video Graphics Array),IBM在1987年推出的显示卡。这是IBM第一块可显示640×480图素大小的PC显示卡。如果在「控制台」的「显示器」中选择了「小字体」(表示您希望Windows假定视讯显示的分辨率为96 dpi),则Windows使用的这三种字体文件名将以「VGA」开头。如果选择了「大字体」(表示您希望分辨率为120 dpi),Windows使用的文件名将以「8514」开头。8514是IBM在1987年推出的另一种显示卡,它的最大显示尺寸为8。
Windows不希望您看到这些文件。这些文件的属性设定为系统和隐藏,如果用Windows Explorer来查看FONTS子目录的内容,您是不会看到它们的,即使选择了查看系统和隐藏文件也不行。从开始菜单选择「寻找」选项来寻找文件名满足 *.FON限定条件的文件。这时,您可以双击文件名来查看字体字符是些什么。
对于许多标准控件和使用者接口组件,Windows不使用系统字体。相反地,使用名称为MS Sans Serif的字体(「MS」代表Microsoft)。这也是一种点阵字体。文件(名为SSERIFE.FON)包含依据96 dpi视讯显示器的字体,点值为8、10、12、14、18和24。您可在GetStockObject函数中使用DEFAULT_GUI_FONT标识符来得到该字体。Windows使用的点值取决于「控制台」的「显示」中选择的显示分辨率。
到目前为止,我已提到四种标识符,利用这四种标识符,您可以用GetStockObject来获得用于设备内容的字体。还有三种其它字体标识符:ANSI_FIXED_FONT、ANSI_VAR_FONT和DEVICE_DEFAULT_FONT。为了开始处理键盘和字符显示问题,让我们先看一下Windows中的所有备用字体。显示这些字体的程序是STOKFONT,如程序6-3所示。
程序6-3 STOKFONT
STOKFONT.C
/*----------------------------------------------------------------------
STOKFONT.C -- Stock Font Objects
(c) Charles Petzold, 1998
-----------------------------------------------------------------------*/
#include &windows.h&
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
static TCHAR szAppName[] = TEXT ("StokFont") ;
wndclass.style
= CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc
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 = NULL ;
wndclass.lpszClassName= szAppN
if (!RegisterClass (&wndclass))
MessageBox (
NULL, TEXT ("Program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
hwnd = CreateWindow ( szAppName, TEXT ("Stock Fonts"),
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 struct
TCHAR * szStockF
stockfont [] = { OEM_FIXED_FONT,
"OEM_FIXED_FONT",
ANSI_FIXED_FONT,
"ANSI_FIXED_FONT",
ANSI_VAR_FONT,
"ANSI_VAR_FONT",
SYSTEM_FONT,
"SYSTEM_FONT",
DEVICE_DEFAULT_FONT,"DEVICE_DEFAULT_FONT",
SYSTEM_FIXED_FONT,
"SYSTEM_FIXED_FONT",
DEFAULT_GUI_FONT,
"DEFAULT_GUI_FONT" } ;
static int
iFont, cFonts = sizeof stockfont / sizeof stockfont[0] ;
i, x, y, cxGrid, cyG
PAINTSTRUCT
szFaceName [LF_FACESIZE], szBuffer [LF_FACESIZE + 64] ;
TEXTMETRIC
switch (message)
WM_CREATE:
SetScrollRange (hwnd, SB_VERT, 0, cFonts - 1, TRUE) ;
return 0 ;
WM_DISPLAYCHANGE:
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
WM_VSCROLL:
switch (LOWORD (wParam))
case SB_TOP:
iFont = 0 ;
case SB_BOTTOM:
iFont = cFonts - 1 ;
case SB_LINEUP:
case SB_PAGEUP:
iFont -= 1 ;
case SB_LINEDOWN:
case SB_PAGEDOWN:
iFont += 1 ;
case SB_THUMBPOSITION:iFont = HIWORD (wParam) ;
iFont = max (0, min (cFonts - 1, iFont)) ;
SetScrollPos (hwnd, SB_VERT, iFont, TRUE) ;
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
WM_KEYDOWN:
switch (wParam)
case VK_HOME: SendMessage (hwnd, WM_VSCROLL, SB_TOP, 0) ;
case VK_END:
SendMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0) ;
case VK_PRIOR:
case VK_LEFT:
case VK_UP:
SendMessage (hwnd, WM_VSCROLL, SB_LINEUP, 0) ;
case VK_NEXT:
case VK_RIGHT:
case VK_DOWN: SendMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0) ;
return 0 ;
hdc = BeginPaint (hwnd, &ps) ;
SelectObject (hdc, GetStockObject (stockfont[iFont].idStockFont)) ;
GetTextFace (hdc, LF_FACESIZE, szFaceName) ;
GetTextMetrics (hdc, &tm) ;
cxGrid = max (3 * tm.tmAveCharWidth, 2 * tm.tmMaxCharWidth) ;
cyGrid = tm.tmHeight + 3 ;
TextOut (hdc, 0, 0, szBuffer,
wsprintf (
szBuffer, TEXT (" %s: Face Name = %s, CharSet = %i"),
stockfont[iFont].szStockFont,
szFaceName, tm.tmCharSet)) ;
SetTextAlign (hdc, TA_TOP | TA_CENTER) ;
// vertical and horizontal lines
for (i = 0 ; i & 17 ; i++)
MoveToEx (hdc, (i + 2) * cxGrid,
2 * cyGrid, NULL) ;
(hdc, (i + 2) * cxGrid, 19 * cyGrid) ;
MoveToEx (hdc,
cxGrid, (i + 3) * cyGrid, NULL) ;
(hdc, 18 * cxGrid, (i + 3) * cyGrid) ;
// vertical and horizontal headings
for (i = 0 ; i & 16 ; i++)
TextOut (hdc, (2 * i + 5) * cxGrid / 2, 2 * cyGrid + 2, szBuffer,
wsprintf (szBuffer, TEXT ("%X-"), i)) ;
TextOut (hdc, 3 * cxGrid / 2, (i + 3) * cyGrid + 2, szBuffer,
wsprintf (szBuffer, TEXT ("-%X"), i)) ;
// characters
for (y = 0 ; y & 16 ; y++)
for (x = 0 ; x & 16 ; x++)
TextOut (hdc, (2 * x + 5) * cxGrid / 2,
(y + 3) * cyGrid + 2, szBuffer,
wsprintf (szBuffer, TEXT ("%c"), 16 * x + y)) ;
EndPaint (hwnd, &ps) ;
return 0 ;
WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
return DefWindowProc (hwnd, message, wParam, lParam) ;
这个程序相当简单。它使用滚动条和光标移动键让您选择显示七种备用字体之一。该程序在一个网格中显示一种字体的256个字符。顶部的标题和网格的左侧显示字符代码的十六进制值。
在显示区域的顶部,STOKFONT用GetStockObject函数显示用于选择字体的标识符。它还显示由GetTextFace函数得到的字体样式名称和TEXTMETRIC结构的tmCharSet字段。这个「字符集标识符」对理解Windows如何处理外语版本的Windows是非常重要的。
如果在美国英语版本的Windows中执行STOKFONT,那么您看到的第一个画面将显示使用OEM_FIXED_FONT标识符呼叫GetStockObject函数得到的字体。如图6-3所示。
图6-3 美国版Windows中的OEM_FIXED_FONT
在本字符集中(与本章其它部分一样),您将看到一些ASCII。但请记住ASCII是7位代码,它定义了从代码0x20到0x7E的可显示字符。到IBM开发出IBM PC原型机时,8位字节代码已被稳固地建立起来,因此可使用全8位代码作为字符代码。IBM决定使用一系列由线和方块组成的字符、带重音字母、希腊字母、数学符号和一些其它字符来扩展ASCII字符集。许多文字模式的MS-DOS程序在其屏幕显示中都使用绘图字符,并且许多MS-DOS程序都在文件中使用了一些扩展字符。
这个特殊的字符集给Windows最初的开发者带来了一个问题。一方面,因为Windows有完整的图形程序设计语言,所以线和方块字元在Windows中不需要。因此,这些字符使用的48个代码最好用于许多西欧语言所需要的附带重音字母。另一方面,IBM字符集定义了一个无法完全忽略的标准。
因此,Windows最初的开发者决定支持IBM字符集,但将其重要性降低到第二位-它们大多用于在窗口中执行的旧MS-DOS应用程序,和需要使用由MS-DOS应用程序建立文件的Windows程序。Windows应用程序不使用IBM字符集,并且随着时间的推移,其重要性日渐衰退。然而,如果需要,您还是可以使用。在此环境下,「OEM」指的就是「IBM」。
(您应知道外语版本的Windows不必支持与美国英语版相同的OEM字符集。其它国家有其自己的MS-DOS字符集。这是个独立的问题,就不在本书中讨论了。)
因为IBM字符集被认为不适合Windows,于是选择了另一种扩展字符集。此字符集称作「ANSI字符集」,由美国国家标准协会(American National Standards Institute)制定,但它实际上是ISO(International Standards Organization,国际标准化组织)标准,也就是ISO标准8859。它还称为Latin 1、Western European、或者代码页1252。图6-4显示了ANSI字符集的一个版本-美国英语版Windows的系统字体。
图6-4 美国版Windows中的SYSTEM_FONT
粗的垂直条表示这些字符代码没有定义。注意,代码0x20到0x7E还是ASCII。此外,ASCII控制字符(0x00到0x1F以及0x7F)并不是可显示字符。它们本应如此。
代码0xC0到0xFF使得ANSI字符集对外语版Windows来说非常重要。这些代码提供64个在西欧语言中普遍使用的字符。字符0xA0,看起来像空格,但实际上定义为非断开空格,例如「WW II」中的空格。
之所以说这是ANSI字符集的「一个版本」,是因为存在代码0x80到0x9F的字符。等宽的系统字体只包括其中的两个字符,如图6-5所示。
图6-5 美国版Windows中的SYSTEM_FIXED_FONT
在Unicode中,代码0xF与ASCII相同,代码0xF复制了0xF的控制字符,代码0x00A0到0x00FF与Windows中使用的ANSI字符集相同。
如果执行德语版的Windows,那么当您用SYSTEM_FONT或者SYSTEM_FIXED_FONT标识符来呼叫GetStockObject函数时会得到同样的ANSI字符集。其它西欧版Windows也是如此。ANSI字符集中含有这些语言所需要的所有字符。
不过,当您执行希腊版的Windows时,内定的字符集就改变了。相反地,SYSTEM_FONT如图6-6所示。
图6-6 希腊版Windows中的SYSTEM_FONT
SYSTEM_FIXED_FONT有同样的字符。注意从0xC0到0xFF的代码。这些代码包含希腊字母表中的大写字母和小写字母。当您执行俄语版Windows时,内定的字符集如图6-7所示。
图6-7 俄语版Windows中的SYSTEM_FONT
此外, 注意斯拉夫字母表中的大写和小写字母占用了代码0xC0和0xFF。
图6-8显示了日语版Windows的SYSTEM_FONT。从0xA5到0xDF的字符都是片假名字母表的一部分。
图6-8 日语版Windows中的SYSTEM_FONT
图6-8所示的日文系统字体不同于前面显示的那些,因为它实际上是双字节字符集(DBCS),称为「Shift-JIS」(「JIS」代表日本工业标准,Japanese Industrial Standard)。从0x81到0x9F以及从0xE0到0xFF的大多数字符代码实际上只是双字节代码的第一个字节,其第二个字节通常在0x40到0xFC的范围内(关于这些代码的完整表格,请参见Nadine Kano书中的附录G)。
现在,我们就可以看看KEYVIEW1中的问题在哪里:如果您安装了希腊键盘布局并键入『abcde』,不考虑执行的Windows版本,Windows将产生WM_CHAR消息和字符代码0xE1、0xE2、0xF8、0xE4和0xE5。但只有执行带有希腊系统字体的希腊版Windows时,这些字符代码才能与?、?、?、?和?相对应。
如果您安装了俄语键盘布局并敲入『abcde』,不考虑所使用的Windows版本,Windows将产生WM_CHAR消息和字符代码0xF4、0xE8、0xF1、0xE2和0xF3。但只有在使用俄语版Windows或者使用斯拉夫字母表的其它语言版,并且使用斯拉夫系统字体时,这些字符代码才会与字符φ、и、с、в和у相对应。
如果您安装了德语键盘布局并按下=键(或者位于同一位置的键),然后按下a、e、i、o或者u键,不考虑使用的Windows版本,Windows将产生WM_CHAR消息和字符代码0xE1、0xE9、0xED、0xF3和0xFA。只有执行西欧版或者美国版的Windows时,也就是说有西欧系统字体,这些字符代码才会和字符á、é、í、ó和ú相对应。
如果安装了美国英语键盘布局,则您可在键盘上键入任何字符,Windows将产生WM_CHAR消息以及与字符正确匹配的字符代码。
Unicode怎么样?
我在谈到过Windows NT支持的Unicode有助于为国际市场程序写作。让我们编译一下定义了UNICODE标识符的KEYVIEW1,并在不同版本的Windows NT下执行(在本书附带的光盘中,Unicode版的KEYVIEW1位于DEBUG目录中)。
如果程序编译时定义了UNICODE标识符,则「KeyView1」窗口类别就用RegisterClassW函数注册,而不是RegisterClassA函数。这意味着任何带有字符或文字数据的消息传递给WndProc时都将使用16位字符而不是8位字符。特别是WM_CHAR消息,将传递16位字符代码而不是8位字符代码。
请在美国英语版的Windows NT下执行Unicode版的KEYVIEW1。这里假定您已经安装了至少三种我们试验过的键盘布局-即德语、希腊语和俄语。
使用美国英语版的Windows NT,并安装了英语或者德语的键盘布局,Unicode版的KEYVIEW1在工作时将与非Unicode版相同。它将接收相同的字符代码(所有0xFF或者更低的值),并显示同样正确的字符。这是因为最初的256个Unicode字符与Windows中使用的ANSI字符集相同。
现在切换到希腊键盘布局,并键入『abcde』。WM_CHAR消息将含有Unicode字符代码0x03B1、 0x03B2、0x03C8、 0x03B4和0x03B5。注意,我们先看到的字符代码值比0xFF高。这些Unicode字符代码与希腊字母?、?、?、d和?相对应。不过,所有这五个字符都显示为方块!这是因为SYSTEM_FIXED_FONT只含有256个字符。
现在切换到俄语键盘布局,并键入『abcde』。KEYVIEW1显示WM_CHAR消息和Unicode字符代码0x8、0x2和0x0443,这些字符对应于斯拉夫字母φ、и、с、в和у。不过,所有这五个字母也显示为实心方块。
简言之,非Unicode版的KEYVIEW1显示错误字符的地方,Unicode版的KEYVIEW1就显示实心方块,以表示目前的字体没有那种特殊字符。虽然我不愿说Unicode版的KEYVIEW1是非Unicode版的改进,但事实确实如此。非Unicode版显示错误字符,而Unicode版不会这样。
Unicode和非Unicode版KEYVIEW1的不同之处主要在两个方面。
首先,WM_CHAR消息伴随一个16位字符代码,而不是8位字符代码。在非Unicode版本的KEYVIEW1中,8位字符代码的含义取决于目前活动的键盘布局。如果来自德语键盘,则0xE1代码表示á,如果来自希腊语键盘则代表?,如果来自俄语键盘则代表?。在Unicode版本程序中,16位字符代码的含义很明确:a字符是0x00E1,?字符是0x03B1,而?字符是0x0431。
第二,Unicode的TextOutW函数显示的字符依据16位字符代码,而不是非Unicode的TextOutA函数的8位字符代码。因为这些16位字符代码含义明确,GDI可以确定目前在设备内容中选择的字体是否可显示每个字符。
在美国英语版Windows NT下执行Unicode版的KEYVIEW1多少让人感到有些迷惑,因为它所显示的就好像GDI只显示了0xFF之间的字符代码,而没有显示高于0x00FF的代码。也就是说,只是在字符代码和系统字体中256个字符之间简单的一对一映射。
然而,如果安装了希腊或者俄语版的Windows NT,您将发现情况就大不一样了。例如,如果安装了希腊版的Windows NT,则美国英语、德语、希腊语和俄语键盘将会产生与美国英语版Windows NT同样的Unicode字符代码。不过,希腊版的Windows NT将不显示德语重音字符或者俄语字符,因为这些字符并不在希腊系统字体中。同样,俄语版的Windows NT也不显示德语重音字符或者希腊字符,因为这些字符也不在俄语系统字体中。
其中,Unicode版的KEYVIEW1的区别在日语版Windows NT下更具戏剧性。您从IME输入日文字符,这些字符可以正确显示。唯一的问题是格式:因为日文字符通常看起来非常复杂,它们的显示宽度是其它字符的两倍。
TrueType 和大字体
我们使用的点阵字体(在日文版Windows中带有附

我要回帖

更多关于 怎么让机械键盘发光 的文章

 

随机推荐