📄 见招拆招《windows程序设计》(三) .txt
字号:
invoke GetTextMetrics,hdc,addr tm
mov eax,tm.tmAveCharWidth
mov cxChar,eax ;cxChar = tm.tmAveCharWidth
mov eax,2 ;cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2
test DWORD ptr tm.tmPitchAndFamily,1
jz @f
inc eax
@@:
push eax
mov eax,cxChar
pop ebx
mul ebx
shr eax,1
mov cxCaps,eax
mov eax,tm.tmHeight ;cyChar = tm.tmHeight + tm.tmExternalLeading
add eax,DWORD ptr tm.tmExternalLeading
mov cyChar,eax
invoke ReleaseDC,hwnd, hdc
ret
.elseif message == WM_PAINT
invoke BeginPaint,hwnd,addr ps
mov hdc,eax
mov DWORD ptr i,0
lea esi,sysmetrics
add esi,4 ;指向后面的字符串的地址
@@:
mov eax,cyChar ;y_Pos=cyChar * i
mul DWORD PTR i
mov y_Pos,eax
mov edi,[esi] ;esi指向字符串的地址
;edi指向字符串
invoke lstrlen,edi ;取字符串长度
mov ebx,eax
;TextOut (hdc, 0, cyChar * i,sysmetrics[i].szLabel,lstrlen (sysmetrics[i].szLabel))
invoke TextOut,hdc,0,y_Pos,edi,ebx
add esi,4
mov ebx,22 ;x_Caps=cxCaps*22
mov eax,cxCaps
mul ebx
mov x_Caps,eax
mov edi,[esi] ;指向一个字符串地址
invoke lstrlen,edi
mov ebx,eax
;TextOut (hdc, 22 * cxCaps, cyChar * i,sysmetrics[i].szDesc,lstrlen (sysmetrics[i].szDesc))
invoke TextOut,hdc,x_Caps,y_Pos,edi,ebx
invoke SetTextAlign,hdc,TA_RIGHT or TA_TOP
sub esi,8 ;x_Caps=22 * cxCaps + 40 * cxChar
mov eax,cxChar
mov ebx,40
mul ebx
add x_Caps,eax
mov edi,[esi] ;edi=sysmetrics[i].iIndex
invoke GetSystemMetrics,edi
invoke wsprintf,addr szBuffer,CTXT("%5d"),eax
mov ebx,eax
invoke TextOut,hdc,x_Caps,y_Pos,addr szBuffer,ebx
invoke SetTextAlign,hdc,TA_LEFT or TA_TOP
inc i
add esi,16
cmp DWORD ptr i,80
jNz @b
invoke EndPaint,hwnd,addr ps
ret
.elseif message == WM_DESTROY
invoke PostQuitMessage,NULL
.endif
invoke DefWindowProc,hwnd, message, wParam, lParam
ret
WndProc endp
END START
图4-4显示了在标准VGA上执行的SYSMETS1。在程序显示区域的前两行可以看到,屏幕宽度是800个图素,屏幕高度是600个图素,这两个值以及程序所显示的其它值可能会因显示器型态的不同而不同。
图4-4 SYSMETS1的显示
总结:动手试试看上面的程序,再试试将定义字符串的部分放到一个文件中,也方便后面的程序使用
SYSMETS1.ASM窗口消息处理程序
SYSMETS1.ASM程序中的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:
mov eax,2 ;cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2
test DWORD ptr tm.tmPitchAndFamily,1
jz @f
inc eax
@@:
SYSMETS1在处理WM_PAINT消息处理期间完成所有窗口建立工作。通常,窗口消息处理程序先呼叫BeginPaint取得设备内容句柄,然后用一个循环对中定义的sysmetrics结构的每一行进行循环。三列文字用三个TextOut函数显示,对于每一列,TextOut的第三个参数都设定为:
cyChar * i
这个参数指示了字符串顶端相对于显示区域顶部的图素位置。
第一次TextOut调用在第一列显示了大写标识符。TextOut的第二个参数是0,这是说文字从显示区域的左边缘开始。文字的内容来自sysmetrics结构给出的地址。我使用Windows函数lstrlen来计算字符串的长度,它是TextOut需要的最后一个参数。
第二次TextOut调用显示了对系统尺寸值的描述。在这种情况下,TextOut的第二个参数设定为:
22 * cxCaps
第一列显示的最长的大写标识符有20个字符,因此第二列必须在第一列文字开头向右20 × cxCaps处开始。我使用22,以在两列之间加一点多余的空间。
第三次TextOut调用显示从GetSystemMetrics函数取得的数值。变宽字体使得格式化向右对齐的数值有些棘手。从0到9的数字具有相同的宽度,但是这个宽度比空格宽度大。数值可以比一个数字宽,所以不同的数值应该从不同的横向位置开始。
那么,如果我们指定字符串结束的图素位置,而不是指定字符串的开始位置,以此向右对齐数值,是否会容易一些呢?用SetTextAlign函数就可以做到这一点。在SYSMETS1呼叫:
invoke SetTextAlign,hdc,TA_RIGHT or 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参数的低字组中包含显示区域的宽度,高字组中包含显示区域的高度。要保存这些尺寸,需要定义两个变量:
cxClient dd ?
cyClient dd ?
与cxChar和cyChar相似,这两个变量在窗口消息处理程序内定义为静态变量,因为在以后处理其它消息时会用到它们。处理WM_SIZE的方法如下:
if message == WM_SIZE
mov eax,lParam ;cxClient = LOWORD (lParam)
and eax,0FFFFh
mov cxClient,eax
mov eax,lParam
shr eax,16
mov cyClient,eax ;cyClient = HIWORD (lParam)
在许多Windows程序中,WM_SIZE消息必然跟着一个WM_PAINT消息。为什么呢?因为在我们定义窗口类别时指定窗口类别样式为:
CS_HREDRAW or 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, iBa
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -