📄 48.asp
字号:
<P>改变 Form1 的视窗程序之前,必须先记录原视窗程序的位址,方法如下:</P>
<P> </P>
<DIR>
<P><FONT SIZE=+0>Dim prevWndProc As Long</FONT></P>
<P><FONT SIZE=+0>prevWndProc = GetWindowLong(Form1.hWnd, GWL_WNDPROC)</FONT></P>
<P> </P>
</DIR>
<P>传回值 prevWndProc 即为 Form1 原视窗程序的位址。</P>
<P> </P>
<H3 ALIGN=CENTER><FONT SIZE=+1>插队用视窗程序的写法</FONT>
<HR WIDTH="50%"></H3>
<P> </P>
<P>首先假设插队视窗程序的目的只为了插队,但插队之後什麼是也不做,那麼此一视窗程序收到讯息时,就原原本本地呼叫原视窗程序,则
WndProc 视窗程序如下:</P>
<P> </P>
<DIR>
<P><FONT SIZE=+0>Function WndProc(ByVal hWnd As Long, ByVal Msg As Long,
ByVal wParam As Long, ByVal lParam As Long) As Long</FONT></P>
<P><FONT SIZE=+0>WndProc = CallWindowProc(prevWndProc, hWnd, Msg, wParam,
lParam)</FONT></P>
<P><FONT SIZE=+0>End Function</FONT></P>
<P><FONT SIZE=+0> </FONT></P>
</DIR>
<P>比较值得注意的是 CallWindowProc 的第一个参数必须传入原视窗程序的位址
prevWndProc。</P>
<P> </P>
<P>如果想要在插队的视窗程序中处理某些讯息,则结构如下:</P>
<P> </P>
<DIR>
<PRE><FONT SIZE=+1>Function WndProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long,
ByVal lParam As Long) As Long</FONT></PRE>
<PRE><FONT SIZE=+1> Select Case Msg
Case WM_xxxx
... 处理 WM_xxxx 的程式
Case WM_xxxx
... 处理 WM_xxxx 的程式
Case Else
WndProc = CallWindowProc(prevWndProc, hWnd, Msg, wParam, lParam)
End Select</FONT></PRE>
<PRE><FONT SIZE=+1>End Function</FONT></PRE>
<P> </P>
</DIR>
<CENTER><P><B><FONT SIZE=+1>取消插队行为</FONT></B>
<HR WIDTH="50%"></P></CENTER>
<P> </P>
<P>一旦发生插队的行为,在视窗结束以前,一定要取消插队行为,否则程式會当掉,此时须将原视窗程序的位址设定回
Form1,如下:</P>
<P> </P>
<DIR>
<P><FONT SIZE=+0>ret = SetWindowLong(Form1.hWnd, GWL_WNDPROC, prevWndProc)</FONT></P>
<P> </P>
</DIR>
<CENTER><P><B><FONT SIZE=+1>实例解析解说</FONT></B>
<HR WIDTH="50%"></P></CENTER>
<P> </P>
<P>最後请直接参考笔者所完成的范例 WndProc.vbp,您可以进入笔者的网站下载此一程式,首先请检视
WndProc.bas 档案,如下:(笔者省略了 API 函数的宣告)</P>
<P> </P>
<DIR>
<P><FONT SIZE=+0>Option Explicit<BR>
Public prevWndProc As Long<BR>
<BR>
Function WndProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As
Long, ByVal lParam As Long) As Long</FONT></P>
<DIR>
<P><FONT SIZE=+0>Debug.Print hWnd, Msg, wParam, lParam</FONT></P>
<P><FONT SIZE=+0>WndProc = CallWindowProc(prevWndProc, hWnd, Msg, wParam,
lParam)</FONT></P>
</DIR>
<P><FONT SIZE=+0>End Function</FONT></P>
<P> </P>
</DIR>
<P>在此一 .bas 程式中有几件事值得注意:</P>
<P> </P>
<OL>
<LI>Option Explicit 叙述:撰写呼叫 Windows API 的程式时,最好利用此一叙述禁止使用未宣告的变数,因为呼叫
Windows API 时,一旦资料型别不符合就很容易产生错误。</LI>
<LI>prevWndProc 变数:用来记录原视窗程序的位址。</LI>
<LI>视窗程序内容:在此一视窗程序中,暂时不处理任何讯息,因此直接呼叫 CallWindowProc
将讯息传给原视窗程序,此外也利用 Debug.Print 将视窗程序的几个参数显示出来,藉以瞭解讯息传递给视窗程序的情况。</LI>
<P> </P>
</OL>
<P>接著请检视 WndProc.frm 档案,如下:</P>
<P> </P>
<DIR>
<PRE><FONT SIZE=+1>Option Explicit
Private Sub Form_Load()
Dim ret As Long
prevWndProc = GetWindowLong(Me.hWnd, GWL_WNDPROC)
ret = SetWindowLong(Me.hWnd, GWL_WNDPROC, AddressOf WndProc)
End Sub</FONT></PRE>
<PRE><FONT SIZE=+1>Private Sub Form_Unload(Cancel As Integer)
Dim ret As Long
ret = SetWindowLong(Me.hWnd, GWL_WNDPROC, prevWndProc)
End Sub</FONT></PRE>
<DIR>
<P> </P>
</DIR>
</DIR>
<P>此一 .frm 程式的重点在於 Form_Load 及 Form_Unload 事件程序,其中笔者在
Form_Load 事件程序中(也就是 Form 载入时),将 WndProc.bas 的 WndProc 视窗程序插在原视窗程序之前,而在
Form_Unload 事件程序中则将原视窗程序还原回来。(特别注意:想结束程式,请按下
Form 的关闭钮,不要使用 VB 的结束钮,否则 Form_Unload 将不會被执行到)</P>
<P> </P>
<H1>
<HR WIDTH="100%">强化视窗程序的实例
<HR WIDTH="100%"></H1>
<P> </P>
<P>延续前面的 WndProc.vbp 程式,让我们来观察两个强化 VB 原视窗程序的实例。</P>
<P> </P>
<H3 ALIGN=CENTER><FONT SIZE=+1>利用</FONT> WM_QUERYOPEN 讯息禁止视窗还原
<HR WIDTH="50%"></H3>
<P> </P>
<P>WM_QUERYOPEN 讯息发生於视窗由「最小化」还原时,在此笔者想利用此一讯息让视窗一直保留在最小化的状态,首先假设我们不利用此一讯息,而想利用
VB 既有的事件来达成相同的目的,那麼想到的方法是在 Form_Resize 事件程序中撰写以下程式:</P>
<P> </P>
<DIR>
<DIR>
<PRE><FONT SIZE=+1>Private Sub Form_Resize()
If WindowState <> vbMinimized Then
WindowState = vbMinimized
End If
End Sub</FONT></PRE>
<P> </P>
</DIR>
</DIR>
<P>这一段程式确实可以让视窗一直保持在最小化的状态,但使用者将视窗还原时,视窗會被先还原,然後才再缩小,结果可以看到视窗被还原然後又被缩小的过程。如果我们利用
WM_QUERYOPEN 讯息,则 WndProc 视窗程序只要如下修改即可:</P>
<P> </P>
<DIR>
<PRE><FONT SIZE=+1>Function WndProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long,
ByVal lParam As Long) As Long</FONT></PRE>
<PRE><FONT SIZE=+1> If Msg = WM_QUERYOPEN Then
' 吃掉此一讯息,不再传递给原视窗程序
' 也就是不让原视窗程序得到视窗还原的讯息
Else
WndProc = CallWindowProc(prevWndProc, hWnd, Msg, wParam, lParam)
End If
End Function</FONT></PRE>
<DIR>
<P> </P>
</DIR>
</DIR>
<P>以上程式十分简单,只要在 WM_QUERYOPEN 讯息发生时,不再呼叫原视窗程序,使得原视窗程序不知有
WM_QUERYOPEN 讯息发生,於是便不會由最小化还原回来。此一完成之程式笔者收录於
queryopn.vbp 专案中。</P>
<P> </P>
<CENTER><P><B><FONT SIZE=+1>非显示区的滑鼠事件</FONT></B>
<HR WIDTH="100%"></P></CENTER>
<P> </P>
<P>在 VB 既有的事件中,只含有视窗「显示区」(client rect)的滑鼠事件,当我们把滑鼠移到非显示区(例如视窗标题区),则收不到
MouseMove 事件,假设我们的需求是:「滑鼠移到视窗上面时,即让视窗变成使用中」,若单纯使用事件程序来撰写程式,则如下:</P>
<P> </P>
<DIR>
<DIR>
<P><FONT SIZE=+0>Private Sub Form_MouseMove(Button As Integer, Shift As
Integer, X As Single, Y As Single)</FONT></P>
<DIR>
<P><FONT SIZE=+0>Me.SetFocus</FONT></P>
</DIR>
<P><FONT SIZE=+0>End Sub</FONT></P>
<P> </P>
</DIR>
</DIR>
<P>但这个程式有点小瑕疵,那就是滑鼠移到非显示区,而没有移到显示区时,程式并没有作用,为了让程式能够收到滑鼠移到非显示区的讯息,我们可以撰写以下的视窗程序:</P>
<P> </P>
<DIR>
<PRE><FONT SIZE=+1>Function WndProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long,
ByVal lParam As Long) As Long</FONT></PRE>
<PRE><FONT SIZE=+1> Dim ret As Long
If Msg = WM_NCMOUSEMOVE Then
Form1.SetFocus
End If
WndProc = CallWindowProc(prevWndProc, hwnd, Msg, wParam, lParam)
End Function</FONT></PRE>
<P> </P>
</DIR>
<P>在以上程式中,判断 Msg 是否等於 WM_NCMOUSEMOVE 讯息,便可得知使用者是否将滑鼠移到了非显示区,而由於我们不想改变视窗的行为,所以紧接著又呼叫了
CallWindowProc 将讯息传给原视窗程序。以上完成之程式笔者收录於 ncfocus.vbp
专案中。</P>
<P> </P>
<H1>
<HR WIDTH="100%">结语:平心而论
<HR WIDTH="100%"></H1>
<P>虽然笔者本期介绍了视窗程序的强化功能,但说实在话,平常自己写程式时,却很少这麼做,主要原因是笔者觉得搞了一些花样,未必會得到使用者的认同,倒不如采用使用者已经习惯的标准操作方式。这麼说来,笔者本期好像讲了半天的废话,浪费了
Run!PC 宝贵的篇幅,其实不然,讯息的运作模式在 Windows 的程式设计中是很重要的观念,要使
Windows API 善尽其用,讯息的运作模式绝对不可不知,除了讯息之外,Callback
的功能则让 VB 向前跨了一大步,它除了应用在视窗程序的插队之外,呼叫许多
Windows API 也少不了它,上一期笔者所写的「萤幕保护程式」也曾经利用 Callback
的功能将侦测滑鼠与键盘的程式挂在系统之下,本期虽然没有介绍什麼花俏的程式,但建议您不妨把它当作使用
Windows API 的垫底工作。</P>
<DIV ALIGN=right><ADDRESS>
<HR WIDTH="100%"><BR>
<A HREF="http://www.kj.com.tw" Target="_top">学 VB(Visual Basic) 找王国荣</A></ADDRESS></DIV>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -