⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 48.asp

📁 ASPWeb数据库范例总览
💻 ASP
📖 第 1 页 / 共 2 页
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
   <TITLE>VB 与 Windows API 讲座(三) - Windows 的讯息系统</TITLE>
   <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=big5">
   <META NAME="Generator" CONTENT="Microsoft Word 97">
   <META NAME="GENERATOR" CONTENT="Mozilla/3.01Gold (Win95; I) [Netscape]">
</HEAD>
<BODY TEXT="#000000" BGCOLOR="#FFFFFF" LINK="#0000EE" VLINK="#551A8B" ALINK="#FF0000">

<P>VB 与 Windows API 讲座(三)</P>

<P> </P>

<H1 ALIGN=CENTER>Windows 的讯息系统 
<HR WIDTH="100%"></H1>

<DIV ALIGN=right><ADDRESS>╱王国荣</ADDRESS></DIV>

<P>VB 程式有两种工作模式─主动模式及事件驱动模式,主动模式与传统的 DOS
程式很像,程式载入系统後就會一直执行,直到结束为止,事件驱动模式则會在程式载入系统後,先暂时停止执行,直到事件发生时,才驱动对应的事件程序而执行某段程式,执行此段程式之後,又會暂停执行,等待下一次事件的发生。</P>

<P> </P>

<P>主动模式的程式在 Windows 3.1 底下是被禁止的,主要的原因是 Windows 3.1
不是真正的多工作业系统,而为了让不同程式能够同时执行,Windows 规定每一个程式必须不定时地呼叫
Yield、GetMessage、PeekMessage 等 API 函数(VB 程式则是呼叫 DoEvents),而当程式呼叫这些函数时,Windows
就會趁此一时机把 CPU 交给其他程式使用,也因此让每一个程式都有机會使用 CPU
而达到多工作业的目的。</P>

<P> </P>

<P>但如果有某一个程式忽略了此一规定,它就會一直霸占著 CPU 不放,而使得整个
Windows 的作业环境像当掉一样,不过这个问题到了 Windows 95 及 NT 之後,已经不再存在,因为
Windows 95 及 NT 具有主动切换各个程式使用 CPU 权利的能力,所以即使某一程式进入了无穷回圈,Windows
依然會在一小段时间後将 CPU 的使用权转移给其他程式,我们可以把 Windows 与程式之间的关系表示成图-1:</P>

<P> </P>

<CENTER><P><A HREF="48-1.gif">图-1 程式使用 CPU 的时间是由 Windows 来主控的</A></P></CENTER>

<P> </P>

<P>尽管主动模式的程式在 Windows 底下不再有问题,但笔者必须强调的是事件驱动模式才是
Windows 程式的主流,为什麼呢?在 Windows 的多工作业环境底下,萤幕的输出、键盘的输入、滑鼠的输入…等,都必须由
Windows 来统筹管理,如果每个程式都想主动控制这些输出与输入装置,势必造成你抢我抢的混乱现象,反观事件驱动模式是以物件为核心,程式的执行是把负责不同工作的物件派到
Windows 的工作环境底下,接著这些物件就會静候 Windows 所产生的事件,然後加以处理,由於事件是由
Windows 统筹管理以及产生的,因此不同程式之间便能够在 Windows 的环境底下共同生存及工作。</P>

<P> </P>

<P>除了从多工作业的角度来看,事件驱动模式的程式也比较容易管理,由於我们會引用不同的物件以处理不同的工作,因此便比较不會把某一项工作的程式写得太大,而造成将来侦错及维护的困难。</P>

<P> </P>

<H1>
<HR WIDTH="100%"><FONT SIZE=+3>从</FONT> VB 的事件回溯到 Windows 的讯息

<HR WIDTH="100%"></H1>

<P> </P>

<P>以上是从 VB 的角度来看事件,尽管笔者说 Windows 會产生事件以驱动物件,但比较正确的说法是
Windows 先产生讯息,经由 VB 的转化才成为事件,然後才驱动物件的,如图-2。</P>

<P> </P>

<CENTER><P><A HREF="48-2.gif">图-2 事件的产生乃源自於 Windows 的讯息</A></P></CENTER>

<P> </P>

<P>但是 VB 是如何把讯息转化成事件的呢?而讯息又是什麼东西呢?请回忆我们在
46 期介绍的 hWnd(handle of window),它代表视窗的唯一识别码,由於许多物件都有
hWnd 这个属性,所以我们可以把物件表示成图-3,而视窗是 Windows 传递讯息的对象,这便使得
VB 得以将讯息转化成事件,进而驱动物件中的事件程序。</P>

<P> </P>

<CENTER><P><A HREF="48-3.gif">图-3 物件与视窗的关系</A></P></CENTER>

<P> </P>

<P>至於 VB 怎样把讯息转化成事件的呢?在视窗的内部,有一样最重要的东西称为「视窗程序」(window
procedure),它的用途是接收来自 Windows 的讯息,当 VB 建立物件(视窗)时,也會提供一个视窗程序,用以接受来自
Windows 的讯息、处理讯息、并且會将某些讯息转化成为事件,进而驱动物件的事件程序,如图-4,只是它始终是隐藏的,所以对大部分的
VB 程式设计人员来说,并不晓得有这一号人物存在。</P>

<P> </P>

<CENTER><P><A HREF="48-4.gif">图-4 将讯息转化成事件的藏镜人是视窗程序</A></P></CENTER>

<P> </P>

<H1>
<HR WIDTH="100%">认识视窗程序 
<HR WIDTH="100%"></H1>

<P> </P>

<P>关於视窗程序,如果表示成 VB 的函数,则如下:</P>

<P> </P>

<DIR>
<PRE><FONT SIZE=+1>Function WndProcName(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, 
ByVal lParam As Long) As Long
    Select Case Msg
        Case WM_???? ' 讯息</FONT>-1
            ...
        Case WM_???? ' 讯息-2
            ...
        Case Else
            ret = DefWindowProc(hWnd, Msg, wParam, lParam)</PRE>

<PRE><FONT SIZE=+1>    End Select
    WndProcName = ret
End Function</FONT></PRE>

<P> </P>
</DIR>

<P>首先来看视窗程序的四个参数:</P>

<OL>
<LI>hWnd:handle of window,这个不必再解释了吧!</LI>

<LI>Msg:讯息编号,用来表示 Windows 所传递过来的是怎样的讯息。在 Win32api.txt
档案中,我们可以找到许多以 WM_(表示 Window Message) 为开头的常数定义,例如「Public
Const WM_LBUTTONDOWN = &amp;H201」,其中常数符号部分(例如 LBUTTONDOWN 表示
Left Button Down)可以让我们望文生义,而常数数值部分(&amp;H201)则是讯息的编号。而视窗程序會利用
If Msg = WM_???? 或 Select Case Msg/Case WM_???? 叙述来判断收到的讯息是否为某一种讯息,进而加以处理。</LI>

<LI>wParam 及 lParam:这两个参数称为讯息参数,其意义會随著讯息(参数二 Msg)的不同而有所不同,想瞭解每一种讯息参数的意义,则必须查阅
SDK(Software Development Kits) 参考手册,不过 SDK 参考手册蛮贵的,但您可以在以下两个地方寻得
SDK 的线上参考手册:(1) VC++ (2) MSDN/CD (Microsoft Developer Network/CD)。</LI>

<P> </P>
</OL>

<P>至於视窗程序之中最值得注意的地方是 DefWindowProc API 函数,这是 Windows
为了减少视窗程序的负担所提供的函数,由於 Windows 可能传递给视窗程序的讯息多达数百种,而大部分讯息是不需要特别处理的,此时若呼叫
DefWindowProc,可将讯息交由 API 代为处理。</P>

<P> </P>

<H3 ALIGN=CENTER><B><FONT SIZE=+1>VB </FONT>提供的视窗程序</B> 
<HR WIDTH="50%"></H3>

<P> </P>

<P>如果是自己撰写视窗程序,可以自由地处理收到的讯息,不过在 VB 程式中,视窗程序是由
VB 提供的,以下让笔者先来介绍 VB 所提供的视窗程序是如何处理讯息、以及如何产生事件以驱动物件的。</P>

<P> </P>

<P>刚才笔者说过 Windows 的讯息多达数百种,对 VB 所提供的视窗程序而言,也一样不处理那麼多讯息,所以大部分的情况都是呼叫
DefWindowProc 交由 API 代劳。但由於 VB 提供的物件都含有某些事件,因此当
VB 的视窗程序收到讯息时,會判断此一讯息是否应转化成为事件,例如 LBUTTONDOWN
讯息应转化成为 MouseDown 事件,如果是,则判断程式中是否含有该事件的事件程序(例如
Form_MouseDown),如果有,则呼叫此一事件程序,参考图-4 就不难瞭解其间的运作过程。</P>

<P> </P>

<P>笔者觉得视窗程序与事件程序在概念上并没有太大的差别,但不可否认的 VB
的视窗程序會吃掉了许多讯息(也就是说这些讯息并不會产生对应的事件),VB 这麼做也不是没有道理,只保留最为常用的讯息可减少程式设计人员的负担,而实际上,也可以提升程式的执行效能。不过话说回来,因为许多讯息被
VB 吃掉了,这也使得 VB 程式无法完成某些特殊的功能,过去 VB 一直被批评为容易使用但功能不足,这是重要的原因,不过这种现象到了
VB 5.0 以後,有了重大的改变,请继续读下去,稍後笔者就會说明。</P>

<P> </P>

<H1>
<HR WIDTH="100%">让 VB 程式具有 Callback 的能力 
<HR WIDTH="100%"></H1>

<P> </P>

<P>想要强化 VB 的视窗程序是可能的,不过这必须先瞭解 Callback 的工作模式。</P>

<P> </P>

<P>请回顾图-4,Windows 會传递讯息给视窗程序,但何谓传递呢?其实就是 Windows
准备好 hWnd、Msg、wParam、lParam 参数,然後呼叫 VB 所提供的函数(视窗程序),这原本是没什麼特别的事情,但由於平常都是由应用程式(或
VB)来呼叫 Windows 的,而讯息的传递则是由 Windows 倒回来呼叫应用程式,故称为
Callback。</P>

<P> </P>

<P>想要强化 VB 的视窗程序首先必撰写好强化的程序,并且把这个程序设定成 Windows
可以 Callback 的程序,此时最重要的一件事情是告诉 Windows 此一程序的「位址」,但
VB 4.0 以前的版本却无法取得程序的位址,因此举凡与 Callback 有关的程式设计都与
VB 程式无缘,不过到5.0 版以後,VB 提供了 AddressOf 叙述可供程式取得程序的位址,也因此为
VB 程式开启了 Callback 的後门。</P>

<P> </P>

<H3 ALIGN=CENTER><B><FONT SIZE=+1>Callback </FONT>程式范例</B> 
<HR WIDTH="50%"></H3>

<P> </P>

<P>接下来让笔者举个 VB 程式的 Callback 范例,首先请开启 VB 的线上手册(选取
VB 功能表的「说明/线上手册」),然後寻找「AddressOf」关键字,在找到的几个主题中,请选取「AddressOf
运算子范例」,然後依照指示将范例移植到程式中,最後执行程式,结果此一程式會列举系统中的字型,并且显示在表单上的
ListBox 中,如图-5。(注:如果您无法顺利将 AddressOf 范例移植到程式中,可进入笔者的网站下载完成的程式)</P>

<P> </P>

<CENTER><P><A HREF="48-5.gif">图-5 VB5 的 AddressOf 范例</A></P></CENTER>

<P> </P>

<P>此一范例的重点在於 EnumFontFamilies API 函数的呼叫(位於 Module1 的 FillListWithFonts
的副程式),EnumFontFamilies 的用途是列举系统的所有字型,但由於系统的字型数目是可变的,因此
Windows 规定呼叫此一函数时必须传入一个 Callback 程序让 Windows 把列举得到的字型一一传递给这个程序,为了将
Callback 程序的位址传给 Windows,我们可以发现 EnumFontFamilies 的参数三如下:</P>

<P> </P>

<DIR>
<DIR>
<P><B><U>AddressOf</U></B> EnumFontFamProc</P>

<P> </P>
</DIR>
</DIR>

<P>作用就是取得 EnumFontFamProc 程序的位址。</P>

<P> </P>

<H1>
<HR WIDTH="100%">强化 VB 的视窗程序 
<HR WIDTH="100%"></H1>

<P> </P>

<P>由於 VB 的视窗程序是隐藏起来的,所以我们无法改变其中的程式码,但如果已知
hWnd,那麼 Windows 允许我们换掉既有的视窗程序,假设我们想将 Form1 预设的视窗程序改成我们所撰写的视窗程序(假设名称是
WndProc),则呼叫的 API 如下:</P>

<P> </P>

<DIR>
<P><FONT SIZE=+0>ret = SetWindowLong(Form1.hWnd, GWL_WNDPROC, AddressOf
WndProc)</FONT></P>

<P> </P>
</DIR>

<P>如此一来,VB 预设给 Form1 的视窗程序就會失效,而取而代之的是 WndProc
视窗程序,所以接下来就由 WndProc 来接收 Windows 的讯息了。</P>

<P> </P>

<H3 ALIGN=CENTER><B><FONT SIZE=+1>视窗程序的插队游戏</FONT></B> 
<HR WIDTH="50%"></H3>

<P> </P>

<P>利用以上方法来改变 Form 的视窗程序,乍看之下,好像还不错,但实际却很危险,为什麼呢?想想,当我们想换掉某一个视窗程序时,至少要提供与原视窗程序相同的功能,而
VB 的视窗程序到底提供了哪些功能,我们并不清楚,所谓破坏容易建设难啊!解决此一问题,其根本原理请参考图-6,作法上是在原视窗程序前面插入另一个视窗程序,用以撷取我们希望处理的讯息,进而达到强化原视窗程序的效果,至於不处理的讯息,则呼叫
CallWindowProc 将讯息交由原视窗程序来处理,如此一来便不至於破坏原视窗程序的功能。此一作法
Windows 称之为 SubClassing,笔者则称之为视窗程序的插队游戏,要玩插队游戏,还有一些细节要注意。</P>

<P> </P>

<CENTER><P><A HREF="48-6.gif">图-6 在原有的视窗程序之前插入另一个视窗程序</A></P></CENTER>

<P> </P>

<H3 ALIGN=CENTER><FONT SIZE=+1>记录原视窗程序的位址</FONT>
<HR WIDTH="50%"></H3>

<P> </P>

<P>由於我们必须将不处理的讯息丢回原视窗程序,所以呼叫:</P>

<P> </P>

<DIR>
<P><FONT SIZE=+0>ret = SetWindowLong(Form1.hWnd, GWL_WNDPROC, AddressOf
WndProc)</FONT></P>

<P> </P>
</DIR>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -