-
-
[原创]Nasm-Win32汇编学习4-绘制文本
-
发表于: 2009-11-11 20:35 7074
-
绘制文本
今天我们将学习如何在窗口的客户区“绘制”字符串。我们还将学习关于“设备环境”的概念。
Windows中的文本是一个GUI(图形用户界面)对象。每一个字符实际上是由许多的像素点组成,这些点在有笔画的地方显示出来,这样就会出现字符。这也是为什么我说“绘制”字符,而不是写字符。通常你都是在你应用程序的客户区“绘制”字符串(尽管你也可以在客户区外“绘制”)。Windows下的“绘制”字符串方法和Dos下的截然不同,在Dos下你可以把屏幕想象成85x25的一个平面,而Windows下由于屏幕上同时有几个应用程序的画面,所以你必须严格遵从规范。Windows通过把每一个应用程序限制在他的客户区来做到这一点。当然客户区的大小是可变的,你随时可以调整。
提示:客户区是指我们窗体与用户交互的部分。打个比方,比如我们Windows的notepad记事本程序的客户区就是它的编辑框,非客户区则是指它的标题栏和菜单栏及滚动条。
在你在客户区“绘制”字符串前,你必须从Windows那里得到你客户区的大小,确实你无法像在Dos下那样随心所欲地在屏幕上任何地方“绘制”,绘制前你必须得到Windows的允许,然后Windows会告诉你客户区的大小,字体,颜色和其它GUI对象的属性。你可以用这些来在客户区“绘制”。
什么是“设备环境”(DC)呢?它其实是由Windows内部维护的一个数据结构。一个“设备环境”和一个特定的设备相连。像打印机和显示器。对于显示器来说,“设备环境”和一个个特定的窗口相连。
“设备环境”中的有些属性和绘图有关,像:颜色,字体等。你可以随时改动那些缺省值,之所以保存缺省值是为了方便。你可以把“设备环境”想象成是Windows为你准备的一个绘图环境,而你可以随时根据需要改变某些缺省属性。
当应用程序需要绘制时,你必须得到一个“设备环境”的句柄。通常有几种方法:
在WM_PAINT消息中使用call BeginPaint。
在其他消息中使用call GetDC。
call CreateDC建立自己的DC。
你必须牢记的是,在处理单个消息后你必须释放“设备环境”句柄。不要在一个消息处理中获得“设备环境”句柄,而在另一个消息处理中再释放它。
我们在Windows发送WM_PAINT消息时处理绘制客户区,Windows不会保存客户区的内容,它用的方法是“重绘”机制(譬如当客户区刚被另一个应用程序的客户区覆盖),Windows会把WM_PAINT消息放入该应用程序的消息队列。重绘窗口的客户区是各个窗口自己的责任,你要做的是在窗口过程处理WM_PAINT的部分知道绘制什么和如何绘制。
你必须了解的另一个概念是“无效区域”。Windows把一个最小的需要重绘的正方形区域叫做“无效区域”。当Windows发现了一个“无效区域”后,它就会向该应用程序发送一个WM_PAINT消息,在WM_PAINT的处理过程中,窗口首先得到一个有关绘图的结构体,里面包括无效区的坐标位置等。你可以通过调用BeginPaint让“无效区”有效,如果你不处理WM_PAINT消息,至少要调用缺省的窗口处理函数DefWindowProc,或者调用ValidateRect让“无效区”有效。否则你的应用程序将会收到无穷无尽的WM_PAINT消息。
下面是响应该消息的步骤:
1、取得“设备环境”句柄
2、绘制客户区
3、释放“设备环境”句柄
假如,我们程序的客户区假设被另一个应用程序的客户区所覆盖,那些此刻另一个应用程序退出,相应我们程序被覆盖客户区需要发生重绘,而被覆盖的这部分叫做“无效区域”,这时候Windows就会检测到我们需要发生重绘的无效区域,然后发送给我们程序的消息队列中一个WM_PAINT消息。此刻我们就需要在我们的窗口过程中处理WM_PAINT消息,即使不处理也要调用缺省的DefWindowProc函数。BeginPaint函数默认使无效区有效。那么BeginPaint返回了一个设备环境的句柄。因为我们Windows下的硬件设备都是以相关的驱动形式来和Windows通信的,那么我们的设备环境也就关联了相应的设备。例如我们的窗体对象关联了相应的显示器设备,那么此时我们要想在窗体上显示文本,我们必须通过设备驱动,因为Windows提供给了我们相应的函数以及相应的数据类型,我们只需要取得设备环境的句柄,然后通过相应的函数来调用,此时我们不需要知道它是如何实现的,我们只需要遵守Windows的规则就可以了。下面我们通过一段代码来深入分析一下。
%include '..\inc\nasmx.inc'
%include '..\inc\win32\windows.inc'
%include '..\inc\win32\kernel32.inc'
%include '..\inc\win32\user32.inc'
entry start
[section .bss]
hInstance: resd 1
hWnd: resd 1
lpCommandLine: resd 1
[section .data]
szTitle: db "My First Window", 0x0
szClass: db "FirstWindow", 0x0
szText: db "My First Window Text", 0x0
wc:
istruc WNDCLASSEX ;声明结构体
at WNDCLASSEX.cbSize, dd NULL
at WNDCLASSEX.style, dd NULL
at WNDCLASSEX.lpfnWndProc, dd NULL
at WNDCLASSEX.cbClsExtra, dd NULL
at WNDCLASSEX.cbWndExtra, dd NULL
at WNDCLASSEX.hInstance, dd NULL
at WNDCLASSEX.hIcon, dd NULL
at WNDCLASSEX.hCursor, dd NULL
at WNDCLASSEX.hbrBackground, dd NULL
at WNDCLASSEX.lpszMenuName, dd NULL
at WNDCLASSEX.lpszClassName, dd NULL
at WNDCLASSEX.hIconSm, dd NULL
iend
message:
istruc MSG
at MSG.hwnd, dd NULL
at MSG.message, dd NULL
at MSG.wParam, dd NULL
at MSG.lParam, dd NULL
at MSG.time, dd NULL
at MSG.pt, dd NULL
iend
ps:
istruc PAINTSTRUCT
iend
rect:
istruc RECT
at RECT.left, dd NULL
at RECT.top, dd NULL
at RECT.right, dd NULL
at RECT.bottom, dd NULL
iend
[section .code]
proc start
invoke GetModuleHandleA, dword NULL
mov [hInstance], eax
invoke GetCommandLineA
mov [lpCommandLine], eax
invoke WinMain, dword [hInstance], dword NULL, dword lpCommandLine, dword SW_SHOWNORMAL
invoke ExitProcess, dword NULL
ret
endproc
proc WinMain
hinst argd ; Current instance handle
hpinst argd ; Previous instance handle
cmdln argd ; Command line arguments
dwshow argd ; Display style
mov [wc + WNDCLASSEX.cbSize], dword WNDCLASSEX_size
mov [wc + WNDCLASSEX.style], dword CS_HREDRAW | CS_VREDRAW
invoke LoadIconA, dword NULL, dword IDI_APPLICATION
mov edx, eax
mov eax, dword argv(hinst)
mov ebx, dword szClass
mov ecx, dword WndProc
mov [wc + WNDCLASSEX.hInstance], eax
mov [wc + WNDCLASSEX.hbrBackground], dword COLOR_WINDOW + 1
mov [wc + WNDCLASSEX.lpszClassName], ebx
mov [wc + WNDCLASSEX.lpfnWndProc], ecx
mov [wc + WNDCLASSEX.hIcon], edx
mov [wc + WNDCLASSEX.hIconSm], edx
invoke RegisterClassExA, dword wc
invoke CreateWindowExA, dword NULL, dword szClass, dword szTitle, dword WS_OVERLAPPEDWINDOW + WS_VISIBLE, dword CW_USEDEFAULT, dword CW_USEDEFAULT, dword CW_USEDEFAULT, dword CW_USEDEFAULT, dword NULL, dword NULL, dword [wc + WNDCLASSEX.hInstance], dword NULL
mov [hWnd], eax
invoke ShowWindow, dword hWnd, dword argv(dwshow)
invoke UpdateWindow, dword hWnd
.WHILE:
invoke GetMessageA, dword message, dword NULL, dword NULL, dword NULL
cmp eax, dword 0
je .ENDW
invoke TranslateMessage, dword message
invoke DispatchMessageA, dword message
jmp .WHILE
.ENDW:
mov eax, dword [message + MSG.wParam]
ret
endproc
proc WndProc
hwnd argd ; Window handle
umsg argd ; Window message
wparam argd ; wParam
lparam argd ; lParam
locals ;locals/local/endlocals,声明局部变量,使用var()宏获得变量地址
local hdc, Dword
endlocals
if argv(umsg), ==, dword WM_DESTROY
invoke PostQuitMessage, dword NULL
elsif argv(umsg), ==, dword WM_PAINT
invoke BeginPaint, dword argv(hwnd), dword ps ;重绘指定的窗口
mov dword var(hdc), eax
invoke GetClientRect, dword argv(hwnd), dword rect ;获取客户区的坐标,放到RECT结构体中
invoke DrawTextA, dword var(hdc), dword szText, dword -1, dword rect, dword DT_SINGLELINE | DT_CENTER | DT_VCENTER ;在指定窗口写入格式化文本。
invoke EndPaint, dword argv(hwnd), dword ps ;指定窗口的绘制过程结束,这个函数在每次调用BeginPaint函数之后被请求。
else
invoke DefWindowProcA, dword argv(hwnd), dword argv(umsg), dword argv(wparam), dword argv(lparam)
endif
ret
endproc
今天的代码跟我们前面写的窗口显示代码,只有几点不同的地方。
一、数据段中增加了两个结构体的声明:PAINTSTRUCT和RECT
ps:
istruc PAINTSTRUCT
iend
rect:
istruc RECT
at RECT.left, dd NULL
at RECT.top, dd NULL
at RECT.right, dd NULL
at RECT.bottom, dd NULL
iend
二、窗口过程中声明了局部变量hdc:
locals ;locals/local/endlocals,声明局部变量,使用var()宏获得变量地址
local hdc, Dword
endlocals
上面这两点中声明的变量,都是由处理WM_PAINT消息的GDI函数调用。locals/local/endlocals是nasmx提供给我们在函数中声明局部变量的宏,locals是局部变量区的开头,endlocals是局部变量区的结尾,中间用local声明,“local parm, 数据类型(byte,word,dword等)”。使用var(parm)宏来调用。
hdc用来存放调用BeginPaint返回的“设备环境”句柄。ps是一个PAINTSTRUCT数据类型的变量。它由Windows传递给BeginPaint,在结束绘制后再原封不动的传递给EndPaint。它的函数原型如下:
typedef struct tagPAINTSTRUCT {
HDC hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32];
} PAINTSTRUCT, *PPAINTSTRUCT;
PAINTSTRUCT 结构体包含了用于绘制窗口客户区的信息。
hdc是用于绘制的句柄,fErase如果为非零值则擦除背景,否则不擦除背景,rcPaint 通过制定左上角和右下角的坐标确定一个要绘制的矩形范围,该矩形单位相对于客户区左上角,后面三个参数都是系统预留的,编程一般用不到。
rect是一个RECT结构体类型的参数,它的定义如下:
STRUC RECT
.left RESD 1
.top RESD 1
.right RESD 1
.bottom RESD 1
ENDSTRUC
left和top是客户区正方形左上角的坐标。right和buttom是正方形右下角的坐标。
三、在窗口函数的消息判断中增加了对WM_PAINT消息的处理
elsif argv(umsg), ==, dword WM_PAINT
invoke BeginPaint, dword argv(hwnd), dword ps ;重绘指定的窗口
mov dword var(hdc), eax
invoke GetClientRect, dword argv(hwnd), dword rect ;获取客户区的坐标,放到RECT结构体中
invoke DrawTextA, dword var(hdc), dword szText, dword -1, dword rect, dword DT_SINGLELINE | DT_CENTER | DT_VCENTER ;在指定窗口写入格式化文本。
invoke EndPaint, dword argv(hwnd), dword ps ;指定窗口的绘制过程结束,这个函数在每次调用BeginPaint函数之后被请求。
大家注意到了吗?对hdc的引入是使用var(hdc)宏实现的。
在上面已经讲过,Windows只要检测到我们有需要重绘的无效区域,就会发送WM_PAINT消息,那么我们必须处理WM_PAINT或通过缺省的DefWindowProc函数来处理。因为BeginPaint函数接受一个窗口句柄和未初始化的PAINTSTRUCT型参数后,会自动的将我们的无效区域设置为有效,所以此时我们一般调用BeginPaint来设置我们的无效区域有效,而且BeginPaint函数返回我们相应的窗体对象的设备环境的句柄,我们此时获得相应设备环境句柄后,再调用GetClientRect获得我们应用程序窗体客户区的大小。大小放在rect结构体中。然后把它传给DrawTextA。DrawText的原型如下:
int DrawText(HDC hdc, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat);
hdc:“设备环境”的句柄。
lpString:要显示的文本串,该文本串要么以NULL结尾,要么在nCount中指出它的长度。
nCount:要输出的文本的长度。若以NULL结尾,该参数必须是-1。
lpRect:指向要输出文本串的正方形区域的指针,该正方形必须是一个裁剪区,也就是说超过该区域的字符将不能显示。
uFormat:指定如何显示。我们可以用“|”把以下标志“或”到一块。
DT_SINGLELINE:是否单行显示。
DT_CENTER:是否水平居中。
DT_VCENTER:是否垂直剧中。
四、在.data段中声明了窗口显示变量:
szText: db "My First Window Text", 0x0
结束绘制后,必须调用EndPaint释放“设备环境”的句柄。好了,现在我们把“绘制”文本串的要点总结如下:
1、必须在开始和结束处分别调用BeginPaint和EndPaint;
2、在BeginPaint和EndPaint之间调用所有的绘制函数;
3、如果在其它的消息处理中重新绘制客户区,你可以有两种选择;
(1)用GetDC和ReleaseDC代替BeginPaint和EndPaint;
(2)调用InvalidateRect或UpdateWindow让客户区无效,这将迫使Windows把WM_PAINT放入应用程序消息队列,从而使得客户区重绘。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
赞赏
- [原创]Nasm-Win32汇编学习4-绘制文本 7075
- [原创]Nasm-Win32汇编学习3-创建简单的窗口 10324
- [原创]NASM-Win32汇编学习二--消息框 9986
- [原创]NASM-Win32汇编学习--基本概念 14243
- [求助]最近无法正常浏览看雪…… 3671