📄 见招拆招《windows程序设计》(二) .txt
字号:
最后,一个老经验是:在一些Windows范例程序中,您可能在WinMain中看到以下程序代码:
if (!hPrevInstance)
{
wndclass.cbStyle = CS_HREDRAW | CS_VREDRAW ;
初始化其它 wndclass
RegisterClass (&wndclass) ;
}
这是出于「旧习难改」的原因。在16位的Windows中,如果您启动正在执行的程序的一个新执行实体,WinMain的hPrevInstance参数将是前一个执行实体的执行实体句柄。为节省内存,两个或多个执行实体就可能会共享相同的窗口类别。这样,窗口类别就只在hPrevInstance是NULL的时候才注册,这表明程序没有其它执行实体。
在32位的Windows中,hPrevInstance总是NULL。此程序代码会正常执行,而实际上也没必要检查hPrevInstance。
建立窗口
窗口类别定义了窗口的一般特征,因此可以使用同一窗口类别建立许多不同的窗口。实际呼叫CreateWindowEx建立窗口时,可能指定有关窗口的更详细的信息。
Windows程序设计新手有时会混淆窗口类别和窗口之间的区别,以及为什么一个窗口的所有特征不能被一次设定好。实际上,以这种方式分开这些样式信息是非常方便的。例如,所有的按钮窗口都可以依据同样的窗口类别来建立,与这个窗口类别相关的窗口消息处理程序位于Windows内部。由窗口类别来负责处理按钮的键盘和鼠标输入,并定义按钮在屏幕上的外观形象。从这一点看来,所有的按钮都是以同样的方式工作的。但是并非所有的按钮都是一样的。它们可以有不同的大小,不同的屏幕位置,以及不同的字符串。后面的这样一些特征是窗口定义的一部分,而不是窗口类别定义的。
[细心的读者比对原文时会发现CreateWindows API并不存在,这是因为C语言会调用不同的API来"解释"这个宏]
传递给RegisterClass函数的信息会在一个数据结构中设定好,而传递给CreateWindowEx函数的信息会在函数单独的参数中设定好。下面是HELLOWIN.ASM中的CreateWindowEx呼叫,每一个字段都做了完整的说明:
invoke CreateWindowEx,
NULL,
ADDR szAppName, ;window class name
CTXT("http://www.aogosoft.com"), ;window caption
WS_OVERLAPPEDWINDOW, ;window style
200, ;initial x position
200, ;initial y position
400, ;initial x size
200, ;initial y size
NULL, ;parent window handle
NULL, ;window menu handle
hInst, ;program instance handle
NULL ;creation parameters
mov hWnd,eax
标记为「window class name」的参数是szAppName,它含有字符串「HelloWin」-这是程序注册的窗口类别名称。这就是我们建立的窗口联结窗口类别的方式。
此程序建立的窗口是一个普通的重迭式窗口。它含有一个标题列,标题列左边有一个系统菜单按钮,标题列右边有缩小、放大和关闭图示,四周还有一个表示窗口大小的边框。这是标准样式的窗口,名为WS_OVERLAPPEDWINDOW,出现在CreateWindow的「窗口样式」参数中。如果看一下Windows.inc,您将会发现此样式是几种位旗标的组合:
WS_OVERLAPPEDWINDOW equ
WS_OVERLAPPED OR
WS_CAPTION OR
WS_SYSMENU OR
WS_THICKFRAME OR
WS_MINIMIZEBOX OR
WS_MAXIMIZEBOX
「窗口标题」是显示在标题列中的文字。
注释着「initial x position」和「initial y position」的参数指定了窗口左上角相对于屏幕左上角的初始位置。由于这些参数使用CW_USEDEFAULT标识符,指示Windows使用重迭窗口的内定位置。(CW_USEDEFAULT定义为0x80000000。)内定情况下,Windows依次对新建立的窗口定位,使各窗口左上角的垂直和水平距离在屏幕上按一定的大小递增。与此类似,注释着「initial x size」和「initial y size」的参数分别指定窗口的宽度和高度。同样使用了CW_USEDEFAULT标识符,表明希望Windows使用内定尺寸。
在建立一个「最上层」窗口,如应用程序窗口时,注释为「父窗口句柄」的参数设定为NULL。通常,如果窗口之间存在有父子关系,则子窗口总是出现在父窗口的上面。应用程序窗口出现在桌面窗口的上面,但不必为呼叫CreateWindow而找出桌面窗口的句柄。
因为窗口没有菜单,所以「窗口菜单句柄」也设定为NULL。「程序执行实体句柄」设定为执行实体句柄,它是作为WinMain的参数传递给这个程序的。最后,「建立参数」指标设定为NULL,可以用这个参数存取稍后程序中可能引用到的数据。
CreateWindow传回被建立的窗口的句柄,该句柄存放在变量hwnd中,后者被定义为HWND型态(「窗口句柄型态」)。Windows中的每个窗口都有一个句柄,程序用句柄来使用窗口。许多Windows函数需要使用hwnd作为参数,这样,Windows才能知道函数是针对哪个窗口的。如果一个程序建立了许多窗口,则每个窗口均有一个句柄。窗口句柄是Windows程序所处理最重要的句柄之一。
显示窗口
在CreateWindow呼叫传回之后,Windows内部已经建立了这个窗口。这就是说,Windows已经配置了一块内存,用来保存在CreateWindow呼叫中指定窗口的全部信息跟一些其它信息,而Windows稍后就是依据窗口句柄找到这些信息的。
然而,光是这样子,窗口并不会出现在视讯显示器上。您还需要两个函数呼叫,一个是:
invoke ShowWindow,hWnd,SW_SHOWNORMAL
第一个参数是刚刚用CreateWindow建立的窗口句柄。第二个参数是作为参数传给WinMain的iCmdShow。它确定最初如何在屏幕上显示窗口,是一般大小、最小化还是最大化。在开始菜单中安装程序时,使用者可能做出最佳选择。如果窗口按一般大小显示,那么WinMain接收到后传递给ShowWindow的就是SW_SHOWNORMAL﹔如果窗口是最大化显示的,则为SW_SHOWMAXIMIZED。而如果窗口只显示在工作列上,则是SW_SHOWMINNOACTIVE。
ShowWindow函数在显示器上显示窗口。如果ShowWindow的第二个参数是SW_SHOWNORMAL,则窗口的显示区域就会被窗口类别中定义的背景画刷所覆盖。函数呼叫
invoke UpdateWindow,hWnd
会重画显示区域。它经由发送给窗口消息处理程序(即HELLOWIN.ASM中的WndProc函数)一个WM_PAINT消息做到这一点。后面,我们将说明WndProc如何处理这个消息。
消息循环
呼叫UpdateWindow之后,窗口就出现在显示器上。程序现在必须准备读入使用者用键盘和鼠标输入的数据。Windows为当前执行的每个Windows程序维护一个「消息队列」。在发生输入事件之后,Windows将事件转换为一个「消息」并将消息放入程序的消息队列中。
程序通过执行一块称之为「消息循环」的程序代码从消息队列中取出消息:
StartLoop:
invoke GetMessage,ADDR msg,NULL,0,0
cmp eax, 0
je ExitLoop
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
jmp StartLoop
ExitLoop:
msg变量是型态为MSG的结构,型态MSG在Windows.inc中定义如下:
MSG STRUCT
hwnd DWORD ?
message DWORD ?
wParam DWORD ?
lParam DWORD ?
time DWORD ?
pt POINT <>
MSG ENDS
POINT数据型态也是一个结构,它在Windows.inc中定义如下:
POINT STRUCT
x DWORD ?
y DWORD ?
POINT ENDS
消息循环以GetMessage呼叫开始,它从消息队列中取出一个消息:
invoke GetMessage,ADDR msg,NULL,0,0
这一呼叫传给Windows一个指标,指向名为msg的MSG结构。第二、第三和第四个参数设定为NULL或者0,表示程序接收它自己建立的所有窗口的所有消息。Windows用从消息队列中取出的下一个消息来填充消息结构的各个字段,结构的各个字段包括:
hwnd 接收消息的窗口句柄。在HELLOWIN程序中,这一参数与CreateWindowEx传回的hwnd值相同,因为这是该程序拥有的唯一窗口。
message 消息标识符。这是一个数值,用以标识消息。对于每个消息,均有一个对应的标识符,这些标识符定义于Windows表头文件(其中大多数在Windows.inc中),以前缀WM(「window message」,窗口消息)开头。例如,使用者将鼠标光标放在HELLOWIN显示区域之内,并按下鼠标左按钮,Windows就在消息队列中放入一个消息,该消息的message字段等于WM_LBUTTONDOWN。这是一个常数,其值为0x0201。
wParam 一个32位的「message parameter(消息参数)」,其含义和数值根据消息的不同而不同。
lParam 一个32位的消息参数,其值与消息有关。
time 消息放入消息队列中的时间。
pt 消息放入消息队列时的鼠标坐标。
只要从消息队列中取出消息的message字段不为WM_QUIT(其值为0x0012),GetMessage就传回一个非零值。WM_QUIT消息将导致GetMessage传回0。
叙述
invoke TranslateMessage, ADDR msg
将msg结构传给Windows,进行一些键盘转换。(关于这一点,我们将在后面深入讨论。)
叙述
invoke DispatchMessage, ADDR msg
又将msg结构回传给Windows。然后,Windows将该消息发送给适当的窗口消息处理程序,让它进行处理。这也就是说,Windows将呼叫窗口消息处理程序。在HELLOWIN中,这个窗口消息处理程序就是WndProe函数。处理完消息之后,WndProc传回到Windows。此时,Windows还停留在DispatchMessage呼叫中。在结束DispatchMessage呼叫的处理之后,Windows回到HELLOWIN,并且接着从下一个GetMessage呼叫开始消息循环。
窗口消息处理程序
以上我们所讨论的都是必要的负担:注册窗口类别,建立窗口,然后在屏幕上显示窗口,程序进入消息循环,然后不断从消息队列中取出消息来处理。
实际的动作发生在窗口消息处理程序中。窗口消息处理程序确定了在窗口的显示区域中显示些什么以及窗口怎样响应使用者输入。
在HELLOWIN中,窗口消息处理程序是命名为WndProc的函数。窗口消息处理程序可任意命名(只要求不和其它名字发生冲突)。一个Windows程序可以包含多个窗口消息处理程序。一个窗口消息处理程序总是与呼叫RegisterClass注册的特定窗口类别相关联。CreateWindowEx函数根据特定窗口类别建立一个窗口。但依据一个窗口类别,可以建立多个窗口。
窗口消息处理程序总是定义为如下形式:
WndProc proc hWin:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -