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