本篇文章面對(duì)的對(duì)象是初級(jí)VB程序員,最好有些Windows系統(tǒng)應(yīng)用程序開發(fā)知識(shí)。在結(jié)束文章以前,前邊任何內(nèi)容均有可能更改,如果文中代碼有觸犯版權(quán)等問題,請(qǐng)與作者聯(lián)系,如果作者本人同意,將會(huì)署名發(fā)布,如果不同意將予以刪除。 由于本人文筆欠佳,所以還真不知能寫出什么文章,本來打算寫些.net方面的文章,但是由于剛剛接觸.net不久,還沒有什么經(jīng)驗(yàn)體會(huì),對(duì)于我而言經(jīng)驗(yàn)最多的應(yīng)該還是VB吧,畢竟已經(jīng)用了兩年多了。但是這僅僅是我自身比較而言并不是和其他人比較,因此決定將一些平時(shí)經(jīng)驗(yàn)和技巧寫下來,也就算整理一下資料吧,雖然是班門弄斧,但是希望這篇文章能對(duì)一些VB初學(xué)者有些幫助。希望使用VB開發(fā)應(yīng)用的人能通過這篇文章得到一些啟發(fā),能從另一個(gè)角度來看待VB程序開發(fā)。 先談?wù)勎业膫€(gè)人想法: 不管我們使用什么計(jì)算機(jī)語言開發(fā),VC,VB,BCB,JAVA,NET你都脫離不開操作系統(tǒng),它就是我們軟件的生存土壤,JAVA的跨平臺(tái)其實(shí)是因?yàn)樗奶摂M機(jī),實(shí)質(zhì)上虛擬機(jī)還是要依靠操作系統(tǒng),.net可以說博大精深但是它最終還是調(diào)用操作系統(tǒng)提供的服務(wù),在Windows2003上運(yùn)行.net程序和在windows95上運(yùn)行效果肯定不同,因此只要一種語言提供給我們一種直接調(diào)用操作系統(tǒng)服務(wù)的接口(API)我們就不能武斷的說它某些事做不了。只不過是方便與否,難以程度有差別,說這些話有些位VB申冤的嫌疑,可以說我確實(shí)有這點(diǎn)私心,但是如果你選擇了VB你就要相信它,想辦法了解到,這樣才能充分發(fā)揮它的功能。 通常一種語言用久了就不知不覺中用它來思考,這有好處也有壞處,真正了解一門語言就要學(xué)會(huì)在使用中用它來思考,但是因此也會(huì)帶來思維定式。很多時(shí)候局限的不是語言本身而是我們的思想。因此我們需要不停的思考,從不同角度思考,正如我的bLog的標(biāo)題“我思故我在”,這句話有些唯心,但實(shí)際上在沒有上學(xué)之前,在沒有接收唯物主義的哲學(xué)思想之前我應(yīng)該算是唯心主義者。當(dāng)時(shí)我總是想我眼里的世界包括人,動(dòng)物,植物等所有的一切是否僅僅是在我眼里才呈現(xiàn)這個(gè)樣子而在別人的眼中,是否這個(gè)世界是另一個(gè)模樣,這個(gè)我現(xiàn)在也不知道,因?yàn)榻佑|的是用我的雙手,我看到的是用的眼睛,我感知的是用我的心靈,我無法代替別人,別人無法代替我。話題好像扯遠(yuǎn)了。 Visual Basic 是一種RAD工具,之所以說它是RAD工具就是因?yàn)楹芏嗟讓映跫?jí)的東西已經(jīng)被IDE封裝好,我們只要直接用就好了,因此我們可以用VB來進(jìn)行快速的應(yīng)用開發(fā)。 舉個(gè)例子: 如果用代碼創(chuàng)建一個(gè)正常工作的窗體至少需要調(diào)用如下幾個(gè)API: RegisterClass或RegisterClassEx:該函數(shù)為隨后在調(diào)用Createwindow函數(shù)和CreatewindowEx函數(shù)中使用的窗口注冊(cè)一個(gè)窗口類 UnregisterClass:刪除一個(gè)窗口類,清空該類所需的內(nèi)存 DefWindowProc:該函數(shù)調(diào)用缺省的窗口過程來為應(yīng)用程序沒有處理的任何窗口消息提供缺省的處理。該函數(shù)確保每一個(gè)消息得到處理。調(diào)用DefWindowProc函數(shù)時(shí)使用窗口過程接收的相同參數(shù) GetMessage:該函數(shù)從調(diào)用線程的消息隊(duì)列里取得一個(gè)消息并將其放于指定的結(jié)構(gòu) TranslateMessage:該函數(shù)將虛擬鍵消息轉(zhuǎn)換為字符消息 DispatchMessage:該函數(shù)調(diào)度一個(gè)消息給窗口程序,通常調(diào)度從GetMessage取得的消息 ShowWindow:用于設(shè)置窗口的狀態(tài),其中包括窗口的隱藏、顯示、最小化、最大化、激活等 UpdateWindow: 立即更新窗口內(nèi)需要更新的任何部分 CreateWindowEx:該函數(shù)創(chuàng)建一個(gè)具有擴(kuò)展風(fēng)格的重疊式窗口、彈出式窗口或子窗口,其他與CreateWindow函數(shù)相同 CallWindowProc:該函數(shù)CallWindowProc將消息信息傳送給指定的窗口過程。 SetWindowLong,GetWindowLong:用于獲取或設(shè)置與窗口有關(guān)的信息 PostQuitMessage:將一條消息投遞到指定窗口的消息隊(duì)列 DestroyWindow:清除指定的窗口以及下屬所有子窗口與包容窗口. 進(jìn)行幾個(gè)繁瑣的操作才能創(chuàng)建一個(gè)窗體。然后還有進(jìn)行各種消息處理等等,但是有了VB這種RAD工具所有這些我們都可以不用關(guān)心,因?yàn)閂B已經(jīng)為我們封裝好了。 我們所要做的且關(guān)心的就是怎么設(shè)計(jì)我們自己的應(yīng)用。 做個(gè)比喻就像我們已經(jīng)有了房子只需要按照自己的需要進(jìn)行裝修即可,但是非RAD工具是從樓房的地基(地址有操作系統(tǒng)提供)開始。 但是,凡事沒有絕對(duì)的優(yōu)點(diǎn)也沒有絕對(duì)的缺點(diǎn)。站在不同的角度看待同一個(gè)事物卻會(huì)有不同的結(jié)果。 如果我想在VB中在反過來深入底層將是很麻煩的事。 按照自己的想法蓋房子和將已經(jīng)建好的樓房進(jìn)行改建更麻煩(我這里用的是麻煩,并不是困難),它的難點(diǎn)就是如何找到切入點(diǎn)。 但是如果能夠靈活運(yùn)用系統(tǒng)API,能夠找到切入點(diǎn),將會(huì)起到事半功倍的效果。 下面用實(shí)際的例子進(jìn)行一些演示說明,由于本人技術(shù)及篇幅有限,不事宜做復(fù)雜的說明。那些做為專題討論,寫這篇主要目的是起到拋磚引玉的作用。 嚴(yán)格說來操作系統(tǒng)只知道窗口控件(WinControl)的存在,我這里說的窗口控件可以這么理解就是在VB中具有hWnd(窗口句柄)的控件。他們都靠系統(tǒng)的消息驅(qū)動(dòng),因?yàn)槲以谶@篇文章主要側(cè)重點(diǎn)是利用API來發(fā)掘VB,因此涉及的對(duì)象基本都是指窗口控件,非窗口控件的創(chuàng)建、更新、銷毀又它的父窗口控件來負(fù)責(zé)。 使用VC++編程的人一定會(huì)熟悉很多窗體控件風(fēng)格常量,然后按照自己的需要?jiǎng)?chuàng)建窗體控件樣式,而我們?cè)赩B中,這些統(tǒng)統(tǒng)被IDE包裝起來的,我和根本看不到,但是利用API我們可以重新定義窗體控件的樣式,下面就用實(shí)際例子來演示一下: (這里我沒有列出詳細(xì)的API和常量聲明,因?yàn)槲抑饕塍w現(xiàn)的是方法和思路) 任何一個(gè)窗體控件,我們都可以給它加上ControlBox(所謂ControlBox,就是窗體的圖標(biāo)+最小化+最大化+關(guān)閉按鈕) Public Sub ControlSysMenu(ControlName As Control, SetTrue As Boolean) Dim dwStyle As Long dwStyle = GetWindowLong(ControlName.hwnd, GWL_STYLE) If SetTrue Then dwStyle = dwStyle Or WS_SYSMENU Else dwStyle = dwStyle - WS_SYSMENU End If dwStyle = SetWindowLong(ControlName.hwnd, GWL_STYLE, dwStyle) SetWindowPos ControlName.hwnd, ControlName.Parent.hwnd, 0, 0, 0, 0, SWP_NOZORDER Or SWP_NOSIZE Or SWP_NOMOVE Or SWP_DRAWFRAME End Sub 任何一個(gè)窗體組件,我們都可以給它加上標(biāo)題欄,通過拖動(dòng)標(biāo)題欄,可以實(shí)現(xiàn)控件的運(yùn)行時(shí)移動(dòng)。 Public Sub ControlCaption(ControlName As Control, SetTrue As Boolean) Dim dwStyle As Long dwStyle = GetWindowLong(ControlName.hwnd, GWL_STYLE) If SetTrue Then dwStyle = dwStyle Or WS_CAPTION Or WS_THICKFRAME Else dwStyle = dwStyle - WS_CAPTION - WS_THICKFRAME End If dwStyle = SetWindowLong(ControlName.hwnd, GWL_STYLE, dwStyle) SetWindowPos ControlName.hwnd, ControlName.Parent.hwnd, 0, 0, 0, 0, SWP_NOZORDER Or SWP_NOSIZE Or SWP_NOMOVE Or SWP_DRAWFRAME End Sub 任何一個(gè)窗體組件,我們都可以控制其顯示風(fēng)格為模式對(duì)話框的風(fēng)格 Public Sub ControlModal(ControlName As Control, SetTrue As Boolean) Dim dwStyle As Long dwStyle = GetWindowLong(ControlName.hwnd, GWL_STYLE) If SetTrue Then dwStyle = dwStyle Or WS_POPUP Else dwStyle = dwStyle - WS_POPUP End If dwStyle = SetWindowLong(ControlName.hwnd, GWL_STYLE, dwStyle) SetWindowPos ControlName.hwnd, ControlName.Parent.hwnd, 0, 0, 0, 0, SWP_NOZORDER Or SWP_NOSIZE Or SWP_NOMOVE Or SWP_DRAWFRAME End Sub 任何一個(gè)窗體組件,我們都可以控制其顯示風(fēng)格為對(duì)話框的風(fēng)格。 Public Sub ControlDialog(ControlName As Control, SetTrue As Boolean) Dim dwStyle As Long dwStyle = GetWindowLong(ControlName.hwnd, GWL_STYLE) If SetTrue Then dwStyle = dwStyle Or WS_DLGFRAME Else dwStyle = dwStyle - WS_DLGFRAME End If dwStyle = SetWindowLong(ControlName.hwnd, GWL_STYLE, dwStyle) SetWindowPos ControlName.hwnd, ControlName.Parent.hwnd, 0, 0, 0, 0, SWP_NOZORDER Or SWP_NOSIZE Or SWP_NOMOVE Or SWP_DRAWFRAME End Sub 只要有窗口,這是我們的前提,你可以在運(yùn)行時(shí)隨便更改它的大小。 Public Sub ControlSize(ControlName As Control, SetTrue As Boolean) Dim dwStyle As Long dwStyle = GetWindowLong(ControlName.hwnd, GWL_STYLE) If SetTrue Then dwStyle = dwStyle Or WS_THICKFRAME Else dwStyle = dwStyle - WS_THICKFRAME End If dwStyle = SetWindowLong(ControlName.hwnd, GWL_STYLE, dwStyle) SetWindowPos ControlName.hwnd, ControlName.Parent.hwnd, 0, 0, 0, 0, SWP_NOZORDER Or SWP_NOSIZE Or SWP_NOMOVE Or SWP_DRAWFRAME End Sub 通過以上幾個(gè)實(shí)例,我們知道了如何使用VB沒有提供的窗體風(fēng)格樣式。下面將根據(jù)我的實(shí)際經(jīng)歷介紹幾個(gè)具體的窗體控件的某些比較使用的擴(kuò)展功能。 有兩個(gè)知識(shí)點(diǎn)不能不說,消息機(jī)制,子類(Subclass)。 Windows消息機(jī)制: 眾所周知,Windows系統(tǒng)是靠消息來驅(qū)動(dòng)的。一個(gè)應(yīng)用程序內(nèi)部的運(yùn)行不是靠順序,而是靠事件的觸發(fā)來控制,而各個(gè)事件與組件間的通訊就是靠消息來完成的。 Windows是一個(gè)多任務(wù)的操作系統(tǒng),在同一時(shí)刻,系統(tǒng)中有著多個(gè)應(yīng)用程序的實(shí)例在運(yùn)行。在這樣的一個(gè)操作系統(tǒng)中,不可能像過去的DOS那樣,由一個(gè)應(yīng)用程序來享用所有的系統(tǒng)資源,這些資源是由Windows統(tǒng)一管理的。那么,系統(tǒng)如何協(xié)調(diào)各個(gè)應(yīng)用實(shí)例的運(yùn)行,如何為各個(gè)應(yīng)用實(shí)例分配CPU時(shí)間呢?如何相應(yīng)用戶的輸入呢?事實(shí)上,Windows時(shí)刻監(jiān)視著用戶的一舉一動(dòng),并分析用戶的動(dòng)作與哪一個(gè)應(yīng)用程序相關(guān),然后,將用戶的動(dòng)作以消息的形式發(fā)送到系統(tǒng)中的消息隊(duì)列,這個(gè)消息隊(duì)列就是應(yīng)用程序正常運(yùn)行的基礎(chǔ),也是一條紐帶,將應(yīng)用程序中各個(gè)部分連接成一個(gè)整體來完成特定的任務(wù)。直到應(yīng)用程序終止它會(huì)不停的檢測(cè)系統(tǒng)的消息隊(duì)列,對(duì)隊(duì)列中未處理的消息進(jìn)行分析,根據(jù)消息所包含的內(nèi)容采取適當(dāng)?shù)膭?dòng)作來響應(yīng)用戶所作的操作。 舉個(gè)簡(jiǎn)單的例子: 假設(shè)我們的應(yīng)用程序的某個(gè)窗體有個(gè)File菜單,那么,在運(yùn)行該應(yīng)用程序的時(shí)候,如果用戶單擊了File菜單,這個(gè)動(dòng)作將被Windows (而不是應(yīng)用程序本身!)所捕獲,Windows經(jīng)過分析得知這個(gè)動(dòng)作應(yīng)該由上面所說的那個(gè)應(yīng)用程序去處理,因此,Windows就發(fā)送了個(gè)叫做WM_COMMAND的消息給應(yīng)用程序,該消息所包含的信息告訴應(yīng)用程序:“用戶單擊了File菜單”,應(yīng)用程序得知這一消息之后,采取相應(yīng)的動(dòng)作來響應(yīng)它,在VB中默認(rèn)會(huì)去執(zhí)行File Click事件。這個(gè)過程被稱為消息處理。Windows為每一個(gè)應(yīng)用程序(確切地說是每一個(gè)線程)維護(hù)了相應(yīng)的消息隊(duì)列,應(yīng)用程序的任務(wù)就是不停的從它的消息隊(duì)列中獲取消息,分析消息和處理消息,直到一條接到叫做WM_QUIT消息為止,這個(gè)過程通常是由一種叫做消息循環(huán)的程序結(jié)構(gòu)來實(shí)現(xiàn)的。 Do While GetMessage(wMsg, 0&, 0&, 0&) Call TranslateMessage(wMsg)’翻譯消息 Call DispatchMessage(wMsg)’ 撤去消息 Loop 其中wMsg就是一個(gè)消息結(jié)構(gòu),它可以這樣定義: Public Type Msg hwnd As Long message As Long wParam As Long lParam As Long time As Long pt As POINTAPI End Type 參數(shù)1:hwnd是消息要發(fā)送到的那個(gè)窗口的句柄,這個(gè)窗口就是咱們用CreateWindows函數(shù)創(chuàng)建的那一個(gè)。如果是在一個(gè)有多個(gè)窗口的應(yīng)用程序中,用這個(gè)參數(shù)就可決定讓哪個(gè)窗口接收消息。 參數(shù)2:message是一個(gè)數(shù)字,它唯一標(biāo)識(shí)了一種消息類型。每種消息類型都在Windows文件中定義了,這些常量都以WM_開始后面帶一些描述了消息特性的名稱。比如說當(dāng)應(yīng)用程序退出時(shí),Windows就向應(yīng)用程序發(fā)送一條WM_QUIT消息。 參數(shù)3:一個(gè)32位的消息參數(shù),這個(gè)值的確切意義取決于消息本身。 參數(shù)4:同上。 參數(shù)5:消息放入消息隊(duì)列中的時(shí)間,在這個(gè)域中寫入的并不是日期,而是從Windows啟動(dòng)后所測(cè)量的時(shí)間值。Windows用這個(gè)域來使用消息保持正確的順序。 參數(shù)6:消息放入消息隊(duì)列時(shí)的鼠標(biāo)坐標(biāo). 消息循環(huán)以GetMessage調(diào)用開始,它從消息隊(duì)列中取出一個(gè)消息: GetMessage(&msg,NULL,0,0),第一個(gè)參數(shù)是要接收消息的MSG結(jié)構(gòu)的地址,第二個(gè)參數(shù)表示窗口句柄,NULL則表示要獲取該應(yīng)用程序創(chuàng)建的所有窗口的消息;第三,四參數(shù)指定消息范圍。后面三個(gè)參數(shù)被設(shè)置為默認(rèn)值,這就是說你打算接收發(fā)送到屬于這個(gè)應(yīng)用程序的任何一個(gè)窗口的所有消息。在接收到除WM_QUIT之外的任何一個(gè)消息后,GetMessage()都返回TRUE。如果GetMessage收到一個(gè)WM_QUIT消息,則返回FALSE,如收到其他消息,則返回TRUE。因此,在接收到WM_QUIT之前,帶有GetMessage()的消息循環(huán)可以一直循環(huán)下去。只有當(dāng)收到的消息是WM_QUIT時(shí),GetMessage才返回FALSE,結(jié)束消息循環(huán),從而終止應(yīng)用程序。 均為NULL時(shí)就表示獲取所有消息。 消息用GetMessage讀入后(注意這個(gè)消息可不是WM_QUIT消息),它首先要經(jīng)過函數(shù)TranslateMessage()進(jìn)行翻譯,這個(gè)函數(shù)會(huì)轉(zhuǎn)換成一些鍵盤消息,它檢索匹配的WM_KEYDOWN和WM_KEYUP消息,并為窗口產(chǎn)生相應(yīng)的ASCII字符消息(WM_CHAR),它包含指定鍵的ANSI字符.但對(duì)大多數(shù)消息來說它并不起什么作用,所以現(xiàn)在沒有必要考慮它。 下一個(gè)函數(shù)調(diào)用DispatchMessage()要求Windows將消息傳送給在MSG結(jié)構(gòu)中為窗口所指定的窗口過程。我們?cè)谥v到登記窗口類時(shí)曾提到過,登記窗口類時(shí),我們?cè)付╓indows把函數(shù)WindosProc作為咱們這個(gè)窗口的窗口過程(就是指處理這個(gè)消息的東東)。就是說,Windows會(huì)調(diào)用函數(shù)WindowsProc()來處理這個(gè)消息。在WindowProc()處理完消息后,代碼又循環(huán)到開始去接收另一個(gè)消息,這樣就完成了一個(gè)消息循環(huán)。 因此,從某種角度上來看,Windows應(yīng)用程序是由一系列的消息處理代碼來實(shí)現(xiàn)的。這和傳統(tǒng)的過程式編程方法很不一樣,編程者只能夠預(yù)測(cè)用戶所利用應(yīng)用程序用戶界面對(duì)象所進(jìn)行的操作以及為這些操作編寫處理代碼,卻不可以這些操作在什么時(shí)候發(fā)生或者是以什么順序來發(fā)生,也就是說,我們不可能知道什么消息會(huì)在什么時(shí)候以什么順序來臨。那么windows是如何解決這個(gè)問題的呢?windows采用一種叫做回調(diào)函數(shù)(callback function)的特殊函數(shù),這個(gè)函數(shù)由Windows直接調(diào)用。實(shí)際上每個(gè)窗口類都必須有一個(gè)回調(diào)函數(shù)。在Windows中消息循環(huán)和窗口類的回調(diào)函數(shù)已經(jīng)都被封裝起來,我們一般情況不會(huì)接觸,如果我們想重新注冊(cè)窗口過程函數(shù)WindowProc(就是這個(gè)回調(diào)函數(shù)),我們必須使用子類(Subclass)的技術(shù)。 (這部分說的可能比較多,而且都是開發(fā)Windows應(yīng)用程序的基礎(chǔ)部分(基礎(chǔ)的東西雖然難度不大,但是非常重要)。但是因?yàn)閷?duì)于部分VB程序員來說可能接觸不多,因此說的多了點(diǎn)。) 子類(Subclass): 按照上文提到的因?yàn)閷?duì)于正常的VB通常不能直接處理Windows系統(tǒng)的消息,但是我們可以通過子類的方法截獲Windows消息并且自定義其處理方法(而如果想截獲其他應(yīng)用程序的消息就需要使用鉤子(Hook)技術(shù))。 應(yīng)用程序可以用過SetWindowLong API 函數(shù)為具有窗口句柄(hWnd)的窗體、控件或其他對(duì)象安裝新的消息處理(Message handler)過程函數(shù)WindowProc。這個(gè)新的WindowProc過程必須被定義在模塊(.BAS)文件中。 Private Sub Form_Load() OldWindowProc = SetWindowLong( _ hwnd, GWL_WNDPROC, _ AddressOf NewWindowProc) End Sub 現(xiàn)在,如果窗體收到Windows消息,系統(tǒng)將調(diào)用新的WindowProc 過程(NewWindowProc),這個(gè)新的窗口過程函數(shù)將檢查當(dāng)前的消息行為是否被指定,如果沒有指定具體的行為,將被傳遞給源窗口過程函數(shù)WindowProc,有源WindowProc進(jìn)行默認(rèn)的處理。這個(gè)過程是非常重要的,否則因?yàn)楫?dāng)前窗口可能會(huì)因?yàn)橄⑦z失,造成不能進(jìn)行重繪、更新等其他窗口默認(rèn)的標(biāo)準(zhǔn)行為。而且新的窗口過程必須返回源過程函數(shù)返回的結(jié)果。 下面用一個(gè)實(shí)際代碼例子演示處理WM_SYSCOMMAND消息的過程: WM_SYSCOMMAND: 當(dāng)用戶選擇“窗口菜單”的一條命令是觸發(fā)。 Public Function NewWindowProc( _ ByVal hwnd As Long, ByVal msg As Long, _ ByVal wParam As Long, lParam As WINDOWPOS) As Long Const WM_SYSCOMMAND = &H112 Const SC_SIZE = &HF000& ‘ 檢查是否是WM_SYSCOMMAND消息 If msg = WM_SYSCOMMAND Then ‘ 如果收到的消息是WM_SYSCOMMAND ,進(jìn)一步檢查命令參數(shù)是否是SC_SIZE, 如果是就忽略它,不進(jìn)行任何處理。 If (wParam And &HFFF0) = SC_SIZE Then Exit Function End If ‘*其余的消息傳遞給源窗口過程函數(shù)*非常重要 NewWindowProc = CallWindowProc( _ OldWindowProc, hwnd, msg, wParam, _ lParam) End Function 上面的過程函數(shù)首先檢查收到的消息是否是WM_SYSCOMMAND消息,如果是,那么再進(jìn)一步檢查參數(shù)(wParam)是否是SC_SIZE命令。如果是表示窗體想要調(diào)整大小。但是我們自定義的窗口過程函數(shù)已經(jīng)對(duì)它進(jìn)行了處理,因此這個(gè)消息將不會(huì)被傳遞到源窗口過程函數(shù)。而我們自定義的這個(gè)窗口過程沒有處理的消息將全部進(jìn)一步傳遞給源窗口過程函數(shù)(它的地址保存在OldWindowProc中)。 需要注意的是,當(dāng)我們卸載我們子類的對(duì)象前,我們必須恢復(fù)它的窗口過程函數(shù)。 Private Sub Form_Unload(Cancel As Integer) SetWindowLong hwnd, GWL_WNDPROC, OldWindowProc End Sub 因?yàn)槲覀冃遁d一個(gè)窗口對(duì)象,系統(tǒng)會(huì)發(fā)送WM_NCDESTROY消息給對(duì)象,因此我們可以通過檢測(cè)這個(gè)消息來自動(dòng)恢復(fù)對(duì)象的源窗口過程。 Public Function NewWindowProc( ByVal hwnd As Long, ByVal msg As Long, _ ByVal wParam As Long, lParam As WINDOWPOS) As Long Const WM_NCDESTROY = &H82 Const WM_SYSCOMMAND = &H112 Const SC_SIZE = &HF000& ‘ 如果組件被銷毀,恢復(fù)源窗口過程處理函數(shù) If msg = WM_NCDESTROY Then SetWindowLong hwnd, GWL_WNDPROC,OldWindowProc End If If msg = WM_SYSCOMMAND Then If (wParam And &HFFF0) = SC_SIZE Then Exit Function End If NewWindowProc = CallWindowProc( _ OldWindowProc, hwnd, msg, wParam, _ lParam) End Function 需要注意的一點(diǎn)是,這種方式很容易造成VB IDE的崩潰。不要在調(diào)試模式中途暫?;蚪K于應(yīng)用程序,因?yàn)檫@樣可能不能恢復(fù)源窗口過程函數(shù),造成無法處理正常的消息,變得異?;騃DE崩潰,因此切記調(diào)試前一定存盤。 除了使用子類的方法,我沒還可以使用幾個(gè)API函數(shù)向?qū)ο笾鲃?dòng)發(fā)消息。我們可以用SendMessage和PostMessage: PostMessage將消息直接加入到應(yīng)用程序的消息隊(duì)列中,不等程序返回就退出;而SendMessage()則剛好相反,應(yīng)用程序處理完此消息后,它才返回,可以參考下圖: 下面就對(duì)具體實(shí)際應(yīng)用舉幾個(gè)例子: TextBox控件: a. 控制Textbox輸入格式,我想大多人都遇到這個(gè)問題,在TextBox作為輸入接口時(shí),有時(shí)我們希望用戶只能輸入數(shù)字、大寫、字母等,一般的做法是對(duì)用戶輸入的字符這個(gè)檢查。但是如果我們使用API,將很容易實(shí)現(xiàn)這些功能,比如: 只允許輸入數(shù)字: Public Function NumbersOnly(tBox As TextBox) Dim DefaultStyle As Long DefaultStyle = GetWindowLong(tBox.hwnd, GWL_STYLE) NumbersOnly = SetWindowLong(tBox.hwnd, GWL_STYLE, DefaultStyle Or ES_NUMBER) End Function 只允許大寫: Public Function UpperCaseOnly(tBox As TextBox) Dim DefaultStyle As Long DefaultStyle = GetWindowLong(tBox.hwnd, GWL_STYLE) UpperCaseOnly = SetWindowLong(tBox.hwnd, GWL_STYLE, DefaultStyle Or ES_UPPERCASE) End Function 只允許小寫: Public Function LowerCaseOnly(tBox As TextBox) Dim DefaultStyle As Long DefaultStyle = GetWindowLong(tBox.hwnd, GWL_STYLE) LowerCaseOnly = SetWindowLong(tBox.hwnd, GWL_STYLE, DefaultStyle Or ES_LOWERCASE) End Function 當(dāng)然上邊三個(gè)函數(shù)可以合成一個(gè)函數(shù),因?yàn)樗麄兎椒ㄊ且粯拥?,只是風(fēng)格參數(shù)不同而已。 b.外觀風(fēng)格: VB本身提供兩種風(fēng)格:Flat和3D,但是也許你想改變一下外觀,比如讓TextBox的邊界介于Flat和3D之間那種效果,如圖: 怎么做呢?在VC中我們?cè)趧?chuàng)建一個(gè)窗口對(duì)象時(shí)可以制定它的風(fēng)格,但是在VB中,IDE已經(jīng)按照它自己的想法給我創(chuàng)建好了,如果我們想要改變它只能把已經(jīng)存在的進(jìn)行修改,這時(shí)我們就需要借助的GetWindowLong和SetWindowLong兄弟的幫助來完成這個(gè)任務(wù)了。 Public Sub FlatBorder(ByVal hwnd As Long) Dim TFlat As Long ‘首先將原始的窗口屬性讀出來 TFlat = GetWindowLong(hwnd, GWL_EXSTYLE) ‘進(jìn)行適當(dāng)修改 TFlat = TFlat And Not WS_EX_CLIENTEDGE Or WS_EX_STATICEDGE ‘寫回去 SetWindowLong hwnd, GWL_EXSTYLE, TFlat ‘這個(gè)函數(shù)能為窗口指定一個(gè)新位置和狀態(tài)。它也可改變窗口在內(nèi)部窗口列表中的位置。該函數(shù)與DeferWindowPos函數(shù)相似,只是它的作用是立即表現(xiàn)出來的(在vb里使用:針對(duì)vb窗體,如它們?cè)趙in32下屏蔽或最小化,則需重設(shè)最頂部狀態(tài)。如有必要,請(qǐng)用一個(gè)子類處理模塊來重設(shè)最頂部狀態(tài)) SetWindowPos hwnd, 0, 0, 0, 0, 0, SWP_NOACTIVATE Or SWP_NOZORDER Or SWP_FRAMECHANGED Or SWP_NOSIZE Or SWP_NOMOVE End Sub *當(dāng)然上邊的函數(shù)可以用在所有窗口對(duì)象上,只不夠有些窗口對(duì)象不需要這么做。 如果窗體中有很多TextBox需要這樣設(shè)置,而且不都是控件數(shù)組,那么可以在包裝一下上面的函數(shù): Public Sub AddBorderToAllTextBoxes(frmX As Form) Dim X As Control On Error Resume Next For Each X In frmX.Controls If TypeOf X Is TextBox Then FlatBorder X.hWnd End If Next End Sub b. 改變文字布局: VB 中可以設(shè)置TextBox中文本水平方向居左、居右、居中,但是不能設(shè)置垂直方向,也不能微調(diào)文本距離左邊界的距離,但是我們還是可以借助API的幫助來完成這個(gè)需求: 文本垂直居中: Public Sub VerMiddleText(mText As TextBox) Dim rc As RECT Dim tmpTop As Long Dim tmpBot As Long ‘實(shí)現(xiàn)這個(gè)效果首先TextBox的MultiLine屬性必須為True(多行文本,其實(shí)這個(gè)屬性關(guān)系創(chuàng)建TextBox內(nèi)部使用哪個(gè)類,因此一旦創(chuàng)建就不能修改這個(gè)屬性,所以不能在代碼中修改這個(gè)屬性) If mText.MultiLine = False Then Exit Sub ‘獲得沒個(gè)窗口區(qū)域的邊界我們可以通過發(fā)送EM_GETRECT消息來獲得 Call SendMessage(mText.hwnd, EM_GETRECT, 0, rc) ‘進(jìn)行位置數(shù)據(jù)計(jì)算 With Me.Font .Name = mText.Font.Name .Size = mText.Font.Size .Bold = mText.Font.Bold End With tmpTop = ((rc.Bottom - rc.Top) - (mText.Parent.TextHeight("H") \ Screen.TwipsPerPixelY)) \ 2 tmpBot = ((rc.Bottom - rc.Top) + (mText.Parent.TextHeight("H") \ Screen.TwipsPerPixelY)) \ 2 rc.Top = tmpTop rc.Bottom = tmpBot mText.Alignment = vbCenter ‘?dāng)?shù)據(jù)計(jì)算完畢,再發(fā)送EM_SETRECTNP消息(為一個(gè)編輯控件設(shè)置格式化矩形,與EM_SETRECT類似,只是控件此時(shí)不會(huì)重畫) Call SendMessage(mText.hwnd, EM_SETRECTNP, 0&, rc) mText.Refresh End Sub 這樣我們就達(dá)到了文本垂直居中的目的,其實(shí)只要用的熟了,找到切入點(diǎn),還是很容易實(shí)現(xiàn)的。 調(diào)整邊距: 如果你查看TextBox中常用的消息,你會(huì)發(fā)現(xiàn)有這樣一對(duì)消息:EM_GETMARGINS 和EM_SETMARGINS,MSDN的解釋是:獲取和設(shè)置編輯控件的左、右邊距(不得用于NT3.51)。具體是左還是右由該消息的參數(shù)決定。 看到這些也許你就知道我們可以用這兩個(gè)消息完成我們的需求,好下面實(shí)際著手進(jìn)行驗(yàn)證: Private Sub SetMargin(nLeft As Integer, nRight As Integer, lhWnd As Long) Dim lLongValue As Long ‘高四位表示右邊距,低四位為左邊距 lLongValue = nRight * &H10000 + nLeft SendMessage lhWnd, EM_SETMARGINS, _ EC_LEFTMARGIN Or EC_RIGHTMARGIN, lLongValue End Sub 好經(jīng)過測(cè)試目的達(dá)到,但是這樣做有什么意義呢?有的時(shí)候如果你想在texebox中放入其他對(duì)象,而又不希望文本被覆蓋掉,你就需要用到這個(gè)方法。 |
|