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