中文字幕理论片,69视频免费在线观看,亚洲成人app,国产1级毛片,刘涛最大尺度戏视频,欧美亚洲美女视频,2021韩国美女仙女屋vip视频

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
VB使用API的簡明教程
userphoto

2023.01.02 寧夏

關注

第一話 從消息說起

 

  由于這是《細水長流話API》的第一話,我必須注意到所講的內(nèi)容要簡單,并且讓你有耐心可以看到往后的文章,所以我希望可以通過一個比較特別的例子來引起你的注意(這樣的情況不會總是有的)。讓我們想想,VB里的CommandButton控件讓我們可以做什么?按下、彈起,還有呢?請看看圖3,這樣的情況在你的程序運行時出現(xiàn)過嗎?

 

  Windows是以消息來傳遞信息的。當出現(xiàn)某個操作,比如按鈕被按下,就產(chǎn)生按鈕被按下的消息。消息被傳送到被操作對象(按鈕),事件就產(chǎn)生了。應注意不是按鈕產(chǎn)生消息,而是Windows知道這個操作的發(fā)生,向按鈕發(fā)送這個消息,按鈕收到后再做相應的處理——如改變外觀成為按下的狀態(tài)。

 

  Windows允許第三者向某個對象發(fā)送消息,因此當某個操作沒有發(fā)生時,我們是可以讓對象如同收到消息一樣產(chǎn)生效果的,這就需要用到API函數(shù)——SendMessage了。

 

  SendMessage的聲明前面已經(jīng)說過(注意以Public開頭應放在標準模塊中,否則用Private開頭),它的各個參數(shù)中,hwnd是對象的句柄,wMsg是消息的值(具體什么消息),另外兩個參數(shù)根據(jù)不同消息和不同應用有不同的值。

 

  你看到的圖3的情況,是由于我的程序向Command Button控件發(fā)送了WM_NCLBUTTONDOWN消息。這個消息發(fā)生在鼠標在窗口的非客戶區(qū)域上按下時。所謂非客戶區(qū)域,你可以理解成一個窗口的邊緣和標題欄(當然是指一般情況,這種情況是可以被程序改變的)。

 

  在我這個按鈕的MouseDown事件中,只寫了短短的幾句:

Private Sub cmdResize_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)

Dim nParam As Long

 

With cmdResize

'之所以在0和100之間以及下面 .Width-100 和 .Width 之間,是讓鼠標只在按鈕邊緣才可以拉動按鈕

 

If X > 0 And X < 100 Then

nParam = HTLEFT

ElseIf X > .Width - 100 And X < .Width Then

nParam = HTRight

End If

 

If nParam Then

Call ReleaseCapture

Call SendMessage(.hwnd, WM_NCLBUTTONDOWN, nParam, 0)

End If

 

End With

 

End Sub

 

 

 

可以看到,我讓鼠標拉動按鈕時,拉按鈕左邊是用 HTLEFT做參數(shù),拉右邊是用HTRIGHT做參數(shù)。這兩個都是常量,可以從API瀏覽器中得到值。同樣的,若想拉按鈕的上面和下面,可用HTTOP和 HTBOTTOM做參數(shù),而 HTTOPLEFT和HTBOTTOMRIGHT則分別是左上角和右下角。

 

  在發(fā)送消息之前有一個 ReleaseCapture的API。這個API是讓Windows釋放對鼠標的捕捉以便使鼠標位置的信息不能被收到,CommandButton不知道鼠標在哪里,也就不會發(fā)生按鈕在這時被按下的情況。當然,可以放心,Windows釋放對鼠標的捕捉只是暫時的,當你放開鼠標再次發(fā)生移動時,Windows又會捕捉鼠標了——它是時時都在發(fā)生的。

 

  你可能希望如同我的程序一樣在按鈕邊緣光標會變化,下面是我寫的程序段:

Private Sub cmdResize_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)

Dim NewPointer As MousePointerConstants

 

With cmdResize

If X > 0 And X < 100 Then

NewPointer = vbSizeWE

ElseIf X > .Width - 100 And X < .Width Then

NewPointer = vbSizeWE

Else

NewPointer = vbDefault

End If

 

If NewPointer <> .MousePointer Then

. MousePointer = NewPointer

End If

End With

 

End Sub

 

  作用很明顯,而且很簡單,所以我就不對這段代碼作解釋了。

 

  這個例子很簡單,但相信起的作用是不小的。SendMessage可以發(fā)送很多消息,當然我不會對這些消息一一作解釋,但以后還是會經(jīng)常接觸到的,所以更多的知識就等慢慢再學吧。

 

 

================

用過VB5.0或者更早版本的讀者應該知道VB有一個測試字符串長度的函數(shù): Len。但當你升級到VB6時,會發(fā)現(xiàn)這里的Len并沒有以前那么好用了——它變成了測試字符個數(shù)而不是字符串長度。就是說,當你用以前版本的VB執(zhí)行Len("字符abc")時,返回值是7,因為中文字符每個有2個字節(jié),所以總共有7個字節(jié);而在VB6中執(zhí)行,返回值是5。

  VB6不再有一個直接計算出字符串總字節(jié)數(shù)的函數(shù)了,因為VB6內(nèi)部已經(jīng)把字符串轉(zhuǎn)換成了Unicode——一種比ANSI更新的字符編碼方式。

 

  Unicode把每一個字,無論是中文還是其他文字都當成兩個字節(jié),如果是英文,則這兩個字節(jié)中第二個字節(jié)保留著不使用,如果是雙字節(jié)字符(如中文,雙字節(jié)日文以及韓文),而由這兩個字節(jié)的組合表示一個字符。所以Len可以方便地知道一共有多少個雙字節(jié)字符,多少個單字節(jié)字符,也就出現(xiàn)了上面所說的情況。

  不過既然VB內(nèi)部把ANSI字符轉(zhuǎn)換成Unicode,那么它一定有對應方法轉(zhuǎn)換回來。所以這里提供一個比較方便的方法來得到總字節(jié)數(shù): LenB(StrConv("字符abc", vbFromUnicode))。

 

 

***   這里用到了一個LenB() 函數(shù),你可以自己試試它,比如 LenB("字符")、LenB("abc")、LenB("字符abc"),會發(fā)現(xiàn)返回值分別是4、6和10。

  為什么是4、6和10呢?

  我說過VB內(nèi)部把ANSI字符轉(zhuǎn)換為Unicode,每個Unicode字符用2個字節(jié)來表示,所以,LenB() 的作用是返回字符串的實際字節(jié)數(shù)。但是,這個實際字節(jié)數(shù)已經(jīng)不是我所輸入的字符串的,而是被VB轉(zhuǎn)換過的(我們無法讓VB函數(shù)在轉(zhuǎn)換之前先算好長度),所以我們需要先把字符串轉(zhuǎn)換回來,使用的是 StrConv() 函數(shù)。

  對于這個函數(shù)我不想太過詳細解釋它(一般應用中比較少用),你可以參考MSDN,我只提一提它的第二個參數(shù):vbFromUnicode。

 

  StrConv()函數(shù)的第二個函數(shù)指定轉(zhuǎn)換的類型,vbFromUnicode 指定把字符串從Unicode轉(zhuǎn)換回來,如果是vbUnicode,則把字符串轉(zhuǎn)換為Unicode。注意,雖然你的程序中寫的是ANSI的字符而不是 Unicode字符,但當這個函數(shù)執(zhí)行時,它得到的卻是已經(jīng)被轉(zhuǎn)換成為Unicode的字符串了。

  現(xiàn)在問題可以算解決了,但我們還需要另一個解決方法,因為這種方法太費時。想想看,每一次算長度都要進行 Unicode->ANSI 的轉(zhuǎn)換,這將會花費太多時間。對少量字符還可以,對長字符串,時間就變得更長了。

  所以我們再講一個API:lstrlen。

Public Declare Function lstrlen Lib "kernel32" Alias "lstrlenA" (ByVal lpString As String) As Long

 

  以上是lstrlen的聲明。lstrlen的作用只有一個:

  得到字符串的字節(jié)數(shù)。所以執(zhí)行 lstrlen("字符abc") 將返回7。我們不需要知道它內(nèi)部是如何工作的,但它總是返回該字符串是ANSI時的長度,并且速度很快。

 

==============

 

這是一個顯示W(wǎng)indows的Temp目錄、Windows安裝目錄以及System目錄的路徑的程序。這里用到了三個API分別得到這三個目錄的路徑?! ”容^一下,可以看到這三個API都用到兩個參數(shù),一個是字符串緩存,用來保存得到的路徑,另一個是指定該緩存的大小。為什么這里要指定大小呢?我把我的代碼貼下來,你看一看。

Private Declare Function GetSystemDirectory Lib "kernel32" Alias "GetSystemDirectoryA" (ByVal lpBuffer As String, ByVal nSize As Long) As Long

 

Private Declare Function GetTempPath Lib "kernel32" Alias "GetTempPathA" (ByVal nBufferLength As Long, ByVal lpBuffer As String) As Long

 

Private Declare Function GetWindowsDirectory Lib "kernel32" Alias "GetWindowsDirectoryA" (ByVal lpBuffer As String, ByVal nSize As Long) As Long

 

 

Private Sub Form_Load()

Dim sPath As String * 260, lLen As Long

lLen = GetTempPath(260, sPath)

Text1 = Left(sPath, lLen)

lLen = GetWindowsDirectory(sPath, 260)

Text2 = Left(sPath, lLen)

lLen = GetSystemDirectory(sPath, 260)

Text3 = Left(sPath, lLen)

End Sub

 

  我的sPath是讓API去賦值的,因此必須指定大小,以避免當緩存比API要填充的字符串還小時出現(xiàn)錯誤。它們的返回值都是API已經(jīng)填充了的字符個數(shù)。因為定長字符串長度是一定的,所以沒被填充的空間仍留著,所以要用left來取出有用的部分。

 

***

  我在現(xiàn)在講這個例子除了它實用簡單,還因為我想讓你知道定義長字符串在API中的應用,而且這里有個VB的知識要跟大家講。當我們定義一個變長的字符串變量時,VB并不會像其他變量一樣馬上為它分配內(nèi)存,而是當賦值給它時才分配合適大小的內(nèi)存來存放。

  但是API并不會像VB一樣為你的變量分配內(nèi)存并賦值,它只是知道你想要得到一個字符串,那么它就給你,至于你的變量裝不裝得下,那是你的事。定長的字符在定義時,由于已經(jīng)指定了大小,所以VB就同時分配了內(nèi)存給它,所以在使用API填充一個字符串變量時就要用定長字符串并指定字符的大小了。

 

  但是,是不是定義時是變長的字符串變量就無法用來讓API填充呢?其實是有辦法的,就是事先讓VB為它分配好足夠的內(nèi)存??聪旅?

Dim sPath As String

sPath=Space(260)

'或者

sPath=String(260,0)

 

  用這段代碼來代替前面定長字符串變量的聲明,得到的結果是一樣的。

 

  Space(260)把260個空格賦給了sPath變長字符串變量,因此VB此時為它分配了可容納260個空格的內(nèi)存,而String(260,0)則把260個NULL字符(ASCII碼為0的字符,在API中多數(shù)代表字符串的結尾)賦給sPath,它同樣因此而得到260個字節(jié)的內(nèi)存空間。當然你也可以用 String(260," "),讓空格來填充這個空間,效果是一樣的。

 

 

 

經(jīng)過前幾期的連載,我們學到了幾個有用的API,也許有的讀者會希望我盡快介紹更多的API,不過有許多簡單的API的用法是相似甚至相同的,所以為了讓讀者學到真正有用的知識,在連載的初期,我講的API將是比較簡單而又涉及到相關基礎知識的。至于那些用法極相似甚至相同的,我會在適當?shù)臅r候再介紹它們,只是詳細程度和側(cè)重點不同而已。這點希望引起讀者的注意。

 

 

 

第四話 使用自定義類型

 

  我在前面已經(jīng)提到過自定義類型,這次我用一個簡單的API來說明一個自定義類型在API中的使用。

 

  VB中規(guī)定了自定義類型的變量傳遞給函數(shù)或子程序時必須按引用來傳遞(關于按引用傳遞與按值傳遞,將在以后的文章中做詳細介紹),因此下面這個API的聲明,你會發(fā)現(xiàn)和前面所介紹的幾個有少許不同。

 

 

Public Declare Function GetCursorPos Lib "user32" Alias "GetCursorPos" (lpPoint As POINTAPI) As Long

 

相比上一話中的一個API:

 

 

Public Declare Function GetSystemDirectory Lib "kernel32" Alias "GetSystemDirectoryA" (ByVal lpBuffer As String, ByVal nSize As Long) As Long

 

  可發(fā)現(xiàn)參數(shù)前面少了個ByVal。如果不加ByVal,或者把ByVal換成ByRef,就是按引用傳遞。POINTAPI不是VB的標準數(shù)據(jù)類型,它是一個自定義類型。從API瀏覽器中我們得到它的定義原形是這樣的:

 

 

Public Type POINTAPI

 

x As Long

 

y As Long

 

End Type

 

  這里應該引起注意的是,你應該把POINTAPI的定義寫在使用它的函數(shù)聲明之前,否則VB會認為你的類型未定義。你也不可以把 x As Long 和 y As Long 的位置對調(diào),如果對調(diào)了,在這個API中最多只會使原本 x 的值變成 y 的值,y 的值變成 x 的值,但在更復雜的自定義類型中,結果就不可預知了。

 

  這個API的作用是得到鼠標指針在屏幕中的坐標(以像素為單位)。你可以在自己的程序中試驗它,比如:

Dim tCursor As POINTAPI

 

GetCursorPos tCursor

 

Debug.Print tCursor.x, tCursor.y

 

將從調(diào)試窗口打印鼠標指針的當前坐標

 

VB中的坐標系統(tǒng)比較豐富,有Twip、Point、Pixel、 Character、Inch、Millimeter、Centimeter和User。很復雜吧?在這里我要說的是Twip和Pixel,至于剩下的,由于和本文所說的應用無多大關系,請參考MSDN或相關書籍。

 

  VB中最常用的是Twip的坐標系統(tǒng),按照微軟的說法, Twip是一種與屏幕無關的測量單位,就是說,當我們使用Twip作為單位時,(在打印時)不需要擔心屏幕的分辨率??雌饋硎峭Ψ奖愕臏y量單位,但是在 API應用中,它卻顯得有點多余,因為在API中使用的坐標系統(tǒng)是Pixel。Pixel是以像素為單位的測量單位,像素是構成屏幕的最小元素,因此它也是常用的一種測量單位。

 

  下面讓我們來看看如何在API中應用這兩個常用的坐標系統(tǒng)。我把上一話的示例擴展了一下,將要用到一個新的 API:ScreenToClient。

 

 

Private Declare Function ScreenToClient Lib "user32" (ByVal hwnd As Long, lpPoint As POINTAPI) As Long

 

  ScreenToClient的作用是把屏幕中的坐標轉(zhuǎn)換為客戶區(qū)的坐標(關于什么是客戶區(qū),請參考前面的文章)。hwnd是客戶區(qū)對象的句柄,而 lpPoint則是已經(jīng)存放著屏幕坐標的 POINTAPI類型,執(zhí)行該函數(shù)后,lpPoint的內(nèi)容將被轉(zhuǎn)換為客戶區(qū)坐標值。

 

參考圖1,它顯示了當Form1的坐標系(ScaleMode)設置為Twip時:

 

1.鼠標在屏幕中的坐標

 

2.鼠標在Form1中的坐標(即由VB計算出來的客戶坐標)

 

3.把鼠標的屏幕坐標轉(zhuǎn)換為Form1的客戶坐標

 

4.把以Pixel為單位的客戶坐標轉(zhuǎn)換為以Twip為單位的客戶坐標

 

看看我是如何計算這4對坐標值的:

 

 

Private Sub Form_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)

 

Dim tC As POINTAPI

 

GetCursorPos tC

 

Label1 = "1. Cursor Position: " & tC.X & Space(5) & tC.Y '注意這里是在屏幕中的坐標

 

Label2 = "2. Cursor on Form Coordinate: " & X & Space(5) & Y

 

ScreenToClient Me.hwnd, tC

Label3 = "3. ScreenToClient: " & tC.X & Space(5) & tC.Y '這里把屏幕中的坐標轉(zhuǎn)換為在 Form1 中的坐標

Label4 = "4. Coordinate after transform: " & tC.X * Screen.TwipsPerPixelX & Space(5) & tC.Y * Screen.TwipsPerPixelY

 

End Sub

 

  然后對比圖2,和上面同樣的代碼,把Form1的ScaleMode設置為 Pixel 時計算出來的坐標值。

  在圖1中,F(xiàn)orm1的ScaleMode是Twip,當把鼠標的屏幕坐標轉(zhuǎn)換為客戶坐標時,我們發(fā)現(xiàn)它和Form1本身提供的X、Y值不同(2和3不同),這是因為此時VB程序給我們的坐標值是以Twip為單位的。所以這里我提供了一個方法來把以像素為單位的客戶坐標轉(zhuǎn)換為以Twip為單位,即把水平和豎直方向的坐標值分別乘以Screen.TwipsPerPixelX和Screen.TwipsPerPixelY(所以2和4相同)。

 

  Screen.TwipsPerPixelX和Screen.TwipsPerPixelY是由VB本身提供的,它們的作用是得到屏幕中在水平和豎直方向上每個像素各等于多少個Twip。你也可以使用另一個VB提供的方法:ScaleX()和ScaleY(),它們可以幫你把某一坐標系的值轉(zhuǎn)換成另一坐標系的值。然而,作為一種習慣,我還是建議選擇第一種方法,它顯得直觀一些,并且許多時候當看到這樣一段代碼時,我們可以馬上就理解它的作用。

 

  再看圖2,F(xiàn)orm1的ScaleMode是Pixel,因此Form1本身提供的X、Y和我們用API計算出來的值是相同的(2和3相同),而不是圖1中和被轉(zhuǎn)換為Twip的4相同。

 

  看了上面的示例,我想你應該知道如何在API中使用 Twip和Pixel了。另外我還想補充一句,在一般應用中,我們使用得最多的還是Twip,原因之一是VB默認是使用它的,之二是用它來控制長度比用Pixel更準確,特別是在涉及到打印時——1 Point等于1/72英寸,1 Twip等于1/20 Point即1/1440⒋紓坷迕子?67 Twips; 而Pixel卻因屏幕顯示范圍的不同而改變,這必將使得難以掌握打印長度。

 

  程序在Windows98/2000+VB6下調(diào)試通過。工程文件下載地址是:

  http://www.cfan.net.cn/qikan/cxg/0204gwv.zip。

 

窗體和風格

 

  在Windows中大部分東西都是一個窗口,窗體、菜單、工具欄、狀態(tài)欄、按鈕、文本框……不要覺得奇怪,它們都是窗口——Window(是否從一個側(cè)面說明了這個操作系統(tǒng)為何叫Windows,加了復數(shù)的Window)。

 

  從VB的IDE中你可以更改一個窗體的外觀,圖1是 IDE中各種外框風格的窗體。

 

  你可以看到它們有的有邊框,有的沒有;有的有標題欄,有的沒有;有的有最大最小化按鈕,有的沒有。這些窗體的邊框風格都是在窗體被創(chuàng)建時就定下來的。我們在建立VB程序的窗體時,不需要自己寫創(chuàng)建窗體的代碼,省去了許多重復的工作,但我們也因此失去了解其中秘密的機會。許多情況下窗體風格是在運行時就一直不變的,但有時我們要求在運行時改變,然而,類似BorderStyle等許多設置外觀的屬性只能在設計時才有效,在這種情況下,我們的這項工作就無法完成。所幸的是,實際上窗體的風格是能夠在運行時被改變的,用SetWindowLong,我們就能解決這個問題。

 

  以前我寫過子類的文章,用的也是SetWindowLong,但這次我們不是要用子類,它比子類簡單得多。下面給出SetWindowLong的聲明:

Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

 

  要改變窗體的風格,我們需要用一個常量來使 SetWindowLong知道我們對窗體進行風格設置:GWL_STYLE。

 

  從API瀏覽器得到GWL_STYLE的值后,調(diào)用時,它是作為第二個參數(shù)傳遞出去的。那么第三個參數(shù)呢?這里顯得有點復雜,因為它不是一個單一的參數(shù),而是一組參數(shù)的組合。

 

  就如上面我所說的,一個窗體可能有邊框,可能有最大最小化按鈕,可能有標題欄,但也有可能一部分或全部都沒有,如果我們在這里只用一個參數(shù)為其設置風格,那么這么多風格就需要一種特殊方法,使該API能夠知道我們包含了哪些風格在里面。這就是Or運算。Or運算是把兩個數(shù)值進行或運算,而微軟為了可以方便分離進行Or運算的值,對這些值都精心設計過,因此我們可以放心地將它們組合。如,把 1 和 2 進行Or運算,然后傳遞給函數(shù),函數(shù)會自己分離出 1 和 2,就知道我們傳遞了 1 和 2 兩個值。但有時我們不僅是要組合幾個值,而且要把一個組里的某個值去除,所以還需要用另一種方法: And Not(這里的And 不是布爾運算的And,而是位運算的And)。比如把 1 和 2 進行 Or 運算后的值中的 1 去掉,則將其 And Not 1。如果想知道是否含有一個值,可以用And,如 If 64 And 3 Then ……這里只是提供一種方法讓你可以使用,如果你想知道它們是如何工作的,我建議你參考位運算的相關書籍。

 

***

  我說過窗體、按鈕等許多東西都是一種窗口,那么這個函數(shù)也就理所當然的是針對所有窗口而設計的了,因此可供設置的風格非常多,并且新風格在新操作系統(tǒng)出現(xiàn)時也可能被增加,這里只能給出大部分最常用的,更多的風格請參考 MSDN的Window Styles部分。

 

WS_BORDER:窗口帶有一個薄邊框

 

WS_DLGFRAME:帶有一般對話框的風格,但沒有標題欄

 

WS_CAPTION:窗口帶有一個標題欄,經(jīng)測試,實際上等于 (WS_BORDER Or WS_DLGFRAME)

 

WS_SIZEBOX 和 WS_THICKFRAME:窗口帶有一個可以調(diào)整窗口大小的邊框(即VB里的Sizable,其他地方的邊框均指不具調(diào)整大小功能的邊框)

 

WS_HSCROLL:窗口帶有一個水平滾動條

 

WS_MAXIMIZEBOX:窗口帶有最大化按鈕,該窗口必須具有 WS_CAPTION 風格

 

WS_MINIMIZEBOX:窗口帶有最小化按鈕,該窗口必須具有 WS_CAPTION 風格

 

WS_SYSMENU:在窗口的標題欄上增加一個系統(tǒng)菜單,該窗口必須具有 WS_CAPTION 風格(即WS_BORDER和WS_DLGFRAME)

 

WS_OVERLAPPED 和 WS_TILED:窗口是一個交迭式窗口。交迭式窗口帶有一個標題欄和一個邊框

 

WS_OVERLAPPEDWINDOW 和 WS_TILEDWINDOW:窗口是一個交迭式窗口,并且組合了 WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU,

 

WS_THICKFRAME, WS_MINIMIZEBOX 以及 WS_MAXIMIZEBOX 這些風格

 

WS_VSCROLL:窗口帶有一個垂直滾動條

 

  好了,說了這么多,下面該動手了。在VB 里BorderStyle設置為NONE的窗體,我在上面加了8個CheckBox ,分別測試這些CheckBox上面所示的風格,當CheckBox按下時,表示具有該風格,彈起時表示不具有該風格。

 

***

 

我把該示例所需的常量聲明列在下面:

 

 

Private Const GWL_STYLE = (-16)

 

Private Const WS_BORDER = &H800000

 

Private Const WS_CAPTION = &HC00000 ' WS_BORDER Or WS_DLGFRAME

 

Private Const WS_DLGFRAME = &H400000

 

Private Const WS_SIZEBOX = &H40000

 

Private Const WS_MAXIMIZEBOX = &H10000

 

Private Const WS_MINIMIZEBOX = &H20000

 

Private Const WS_SYSMENU = &H80000

 

Private Const WS_HSCROLL = &H100000

 

Private Const WS_VSCROLL = &H200000

 

如果你要讓窗體具有WS_SIZEBOX風格,可以這樣寫:

SetWindowLong Me.hwnd, GWL_STYLE, WS_SIZEBOX

 

  但是這里仍有問題。這相當于只給窗體WS_SIZEBOX 風格,如果要其他風格我們就得一起加上,但如果我們想在保留窗體原有風格的基礎上增加一個風格,還需要另一個API:

Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long

 

  GetWindowLong的調(diào)用方法和SetWindowLong相似,只不過不需要第三個參數(shù),因為這里的返回值是得到它的風格的組合。你可以先這樣做:

Dim lStyle As Long

 

lStyle = GetWindowLong(Me.hwnd, GWL_STYLE)

 

然后你就可以放心地使用了。

SetWindowLong Me.hwnd, GWL_STYLE, lStyle Or WS_SIZEBOX

 

為窗體增加一個WS_SIZEBOX風格而無需擔心其他風格會丟失。如果想去掉WS_SIZEBOX,則使用:

SetWindowLong Me.hwnd, GWL_STYLE, lStyle And Not WS_SIZEBOX

 

  好了,到這里已為你講述了安全地為窗體更改風格的方法,你可以把你想要的風格(比如前面所列出的)應用于你的窗體。但是,它還是不夠完美,當你改了風格之后,你會發(fā)現(xiàn)——雖然風格實際上已經(jīng)改了,但外表完全沒變,就好像窗體忘了刷新一樣。

讓它刷新?或許你會這么認為,不過這個可憐的窗體,無論你用什么方法去刷新,它都無動于衷……很長一段時間以來我都使用了一個折衷的方法——改變窗體的大小,再改回去。當窗體大小被改變之后,它就會刷新一下,這樣就沒事了。但是這種方法顯得笨了一點,你也許希望就如發(fā)送消息一樣方便地讓它正常刷新,不過就如前面所說,它不領你的情。

 

  但是這種情況也并非無法解決,下一話,我將告訴你一個更好的辦法。

 

位置與常居頂端

 

  許多軟件,特別是占桌面面積不是很大的軟件(比如筆者的NaviEdit),通常都提供了一個常居頂端的功能(可能有的軟件不是這么叫法,但作用是相同的),它的作用是保持窗口一直在其他窗口的上面,可以省去頻繁切換窗口的動作。

 

如果你想這么做,有一個API可以實現(xiàn): SetWindowPos,聲明是這樣的:

Private Declare Function SetWindowPos Lib "user32" Alias "SetWindowPos" (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long

 

  雖然參數(shù)很多,但實際用起來很簡單。hwnd是窗口的句柄,x、y、cx、cy分別是窗口的x和y坐標、寬和高度。hWndInsertAfter用來指定窗口的Z位置(或稱Z順序)。如果你經(jīng)常接觸3D方面的軟件,你就知道Z代表深度。這個參數(shù)接受5種值:HWND_BOTTOM、 HWND_NOTOPMOST、HWND_TOP、HWND_TOPMOST或者另一個窗口的句柄。而wFlags用來指定附加的選項。

 

  你可以用它改變窗口的位置和大小,而且它允許你同時改變Z位置(當然,在VB中不用API你也可以改變窗體大小和位置)。比如讓窗口退到最下面,可以這么使用:

SetWindowPos Me.hWnd, HWND_BOTTOM, 10&, 10&, 80&, 120&, 0&

 

  想要常居頂端,只需把HWND_BOTTOM改為 HWND_TOPMOST,而HWND_NOTOPMOST則是取消常居頂端,HWND_TOP是把窗口的Z位置改為最前。如果這個參數(shù)傳遞的是另一個窗口的句柄,則是把該窗口的Z 位置更改為在另一個窗口的下面。

 

***

  非常簡單的事情。不過如果像上面一樣做,是不是單單改個Z位置也要計算窗口位置和大???最后一個參數(shù)又是干什么用的呢?wFlags可以讓SetWindowPos忽略或執(zhí)行某種行為。這里給出一部分:

 

SWP_DRAWFRAME和SWP_FRAMECHANGED:強制發(fā)送 WM_NCCALCSIZE消息給窗口

 

SWP_HIDEWINDOW:隱藏窗口

 

SWP_NOACTIVATE:不激活窗口

 

SWP_NOMOVE:保持當前位置(忽略x和y)

 

SWP_NOREDRAW:窗口不自動重畫

 

SWP_NOSIZE:保持當前大?。ê雎詂x和cy)

 

SWP_NOZORDER:保持窗口在列表的當前位置(忽略hWndInsertAfter)

 

SWP_SHOWWINDOW:顯示窗口

 

  這些參數(shù)可以使用Or運算組合,所以如果你不希望改變窗口位置和大小,你只需要給最后一個參數(shù)傳遞(SWP_NOMOVE Or SWP_NOSIZE)即可。如下:

SetWindowPos Me.hWnd, HWND_TOPMOST, 0&, 0&, 0&, 0&, SWP_NOMOVE Or SWP_NOSIZE

 

  這里的x、y、cx、cy的值將被忽略。其他值的組合,你可以自己去試試。

  好了,這個看起來好像有點復雜的API已經(jīng)變得很清晰,那么輪到上一話的收尾。

 

  WM_NCCALCSIZE消息是在計算窗口的客戶區(qū)大小時被發(fā)送的,它主要是讓程序可以收到該消息后重新計算客戶區(qū)的大小。我們先不管它是不是也能像許多以WM_開頭的消息一樣由我們發(fā)送給程序讓它產(chǎn)生作用,但它使用起來的復雜程度讓我寧可選擇改變窗體大小再改回去。當我們改變窗口的大小時,很明顯的就是它一定會重新計算客戶區(qū)大小以調(diào)整外觀。既然這個函數(shù)可以強制發(fā)送WM_NCCALCSIZE消息,那么我們就應該試一試。

SetWindowPos Me.hwnd, 0&, 0&, 0&, 0&, 0&, SWP_NOSIZE Or SWP_NOZORDER Or SWP_NOMOVE Or SWP_FRAMECHANGED

 

  為了不改變窗口大小、位置和Z順序(就是要窗口保持原狀),我使用SWP_NOSIZE Or SWP_NOZORDER Or SWP_NOMOVE,讓它忽略前面所有的參數(shù),最后加個 Or SWP_FRAMECHANGED 就是讓它重新計算客戶區(qū)大小。把上面的一行放到前一話中更改風格的那一句下面,再執(zhí)行程序,成功了! 它已經(jīng)能夠正常刷新! 就是這樣,問題馬上變得這么簡單。

 

父與子

 

  在開始這一話之前,不知各位讀者有沒有使用過MDI Form呢?看看圖1,這是一個標準的MDI Form和其中一個子窗體在標準和最大化情況下的外觀。不過別誤會,我不是想講MDI,你再看看圖2,我只是想讓你區(qū)別圖2的窗體不是MDI Form。圖2的兩個窗體都是一般的窗體,從最大化的外觀就可以看出區(qū)別了。是不是覺得很有意思?其實也沒有什么秘密。

 

  我說過 Windows中多數(shù)東西都是一種窗口,比如按鈕。一般情況下我們看到的按鈕都是在一個窗體的里面,這是因為窗體和按鈕有一種父與子的關系。當一個窗口成為另一個窗口的子窗口(Child),那么它的位置的變化就只發(fā)生在另一個窗口里,另一個窗口就是這個窗口的父窗口(Parent)。平時我們建立的窗體都是相互獨立的,與其他的窗體沒有關系,但我們可以通過API使它們建立起父與子的關系。這要用到SetParent:

 

 

Private Declare Function SetParent Lib "user32" (ByVal hWndChild As Long, ByVal hWndNewParent As Long) As Long

 

  SetParent接收兩個參數(shù),第一個是將成為子窗口的窗口句柄,第二個是將成為父窗口的窗口句柄。它的使用很簡單,比如想把Form2作為Form1的子窗口,只需這樣使用:

SetParent Form2.hWnd, Form1.hWnd

 

  Windows會自動把Form2在新的父窗口中的位置調(diào)整為原父窗口的位置(即使是桌面,也是一個父窗口)。即是說,假如原來在桌面的Form2,位置為10,10,則它在新的父窗口中的位置也為10,10。但這個新的10,10是以新父窗口為參照物的,無論怎么變化,都是在新父窗口中。

 

  不過應該注意,并不是所有東西都適合當父窗口。因為每一種窗口都有為自己設計的行為,比如當畫面重畫時要畫什么,如果我們?yōu)樗砑恿诵碌淖哟翱?,那么它們將可能產(chǎn)生沖突,因為父窗口在設計時并沒有考慮出現(xiàn)意外的子窗口的情況。為了說明這個問題,我做了一個示例。當我把按鈕作為ListBox的子窗口時,你會看到由于ListBox在選擇項目時進行了畫面的重畫,導致按 鈕顯示變得不正常,但當我按了一下按鈕時,又因為按鈕的重畫,顯示又正常了。

 

  值得一提的是,當我們把Form1中的一個子窗口(比如按鈕)放置到Form2中,而我們又在Form1中為這個子窗口的某個事件寫了執(zhí)行代碼,那么 夠岜恢蔥新穡?Form2又需不需要為這個新的子窗口做特別處理呢?假如我的處理代碼都是寫在Form1中的,而所有控件都被我放到Form2中時(如圖 4),它們的點擊事件的代碼仍然能被執(zhí)行。由于無法得知實際上VB內(nèi)部是如何處理控件的消息循環(huán)的,所以我也無法對此中秘密進行解釋,特別是一個應該注意的問題——當你把按鈕(這里以按鈕為例,但其實其他東西也一樣)放到 Form2中后,如果這個按鈕在Form2中獲得了焦點,那么你就無法從Form2切換回Form1,除非這時你可以讓Form1中某個控件重新獲得焦點 ——比如通過使某個控件從Form2中成為Form1的子窗口,或者使用 SetFocus讓Form1的某個控件獲得焦點。所以,實際應用中應該避免這種情況的發(fā)生。如果新的父窗口不是由VB所建立的窗體,那么這種事就不會發(fā)生,不過這已不是本話的內(nèi)容了。

 

  在我寫的示例源程序里,還有一個GetParent的API這里沒有講到,我用它判斷當前的子窗口是哪個窗體的子窗口。它的作用是返回指定子窗口的父窗口的句柄。

 

尋找子窗口

 

  這里又是一個特別的例子,圖像處理我還會兩下,不過這可不是處理來的,而是真實的抓圖。我把開始按鈕移到這里來了。再看看圖6,怎么樣?有意思吧?

 

這里我要介紹幾個API:

 

 

Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long

 

Private Declare Function GetWindow Lib "user32" (ByVal hwnd As Long, ByVal wCmd As Long) As Long

 

Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long

 

  首先是FindWindow。FindWindow可以根據(jù)所給的條件,從桌面上尋找一個窗口,lpClassName是窗口的類名,而lpWindowName是窗口的標題。我們可以傳遞lpClassName,讓它找符合的類名的窗口,或傳遞 lpWindowName,讓它找符合的標題的窗口,如果我們不需要兩個條件都符合,則另一個參數(shù)可以傳遞vbNullString,讓它忽略。它的返回值就是找到的窗口的句柄。

 

  那么什么是類名?避開C++的相關術語來說,其實Windows的窗口都是某種類中的一種,這個“類”可以是Textbox、 Combobox,也可以是由用戶來定義的,這個窗口是屬于哪一類的,它的類名就是什么。GetWindow也可以用來尋找某個窗口并返回其句柄,但它只限于在某個窗口中尋找子窗口,因此它需要傳遞hWnd以表示在哪個窗口里尋找。而 wCmd用來描述要找的子窗口與父窗口的關系。它的值如下:

 

GW_CHILD:尋找第一個子窗口

 

GW_HWNDFIRST:尋找第一個同級窗口,或?qū)ふ业谝粋€頂級窗口

 

GW_HWNDLAST:尋找最后一個同級窗口,或?qū)ふ易詈笠粋€頂級窗口

 

GW_HWNDNEXT:尋找下一個同級窗口

 

GW_HWNDdivV:尋找前一個同級窗口

 

GW_OWNER:尋找窗口的所有者(即父窗口)

 

  我們先來理解什么是同級窗口和頂級窗口。打個比方,如果一個窗口有三個子窗口,則這三個窗口都是同一級的,互為同級窗口。如果我們從沒尋找過一個子窗口,那么API 不知道我們要找的是和哪個窗口同級,那么此時它找的是頂級窗口,頂級窗口即是子窗口,但這個子的關系是直接的,而不會是子窗口的子窗口(即孫子,別笑,這里的術語不是我自己造的)。最后一個GetClassName和以前講過的幾個字符串相關的API用法差不多,hWnd是窗口句柄,lpClassName是用來接收窗口類名的緩沖區(qū),nMaxCount則是說明緩沖區(qū)的大小。

 

***

那么接下來我是如何用它們的呢?看這里:

 

 

Dim hTaskbar As Long, hStartbutton As Long

 

Dim sClass As String * 250

 

hTaskbar = FindWindow("Shell_traywnd", vbNullString)

 

hStartbutton = GetWindow(hTaskbar, GW_CHILD)

 

Do

 

GetClassName hStartbutton, sClass, 250

 

If LCase(Left$(sClass, 6)) = "button" Then Exit Do

 

hStartbutton = GetWindow(hStartbutton, GW_HWNDNEXT)

 

Loop

 

  我使用FindWindow從桌面上找到了一個類名為 “Shell_traywnd”的窗口,它就是任務欄(不要問我是怎么知道它的類名的)。然后我又用GetWindow函數(shù),從任務欄找到第一個子窗口。接下來,我用一個Do…Loop結構的循環(huán)為上一次找到的子窗口檢查其類名,如果類名是button,則說明是個按鈕,一般來說,任務欄上只有一個是button類的,所以一找到,它勢必就是“開始”按鈕了。如果沒找到,則仍使用GetWindow,但這次和第一次不同,我傳遞的不是任務欄的句柄,而是上一次找到的子窗口的句柄,為的是找下一個同級窗口,就這樣一次次循環(huán)直到找到開始按鈕。

 

  那么,開始按鈕就被我這么找到了,然后我就可以像對待其他窗口一樣對待它:比如將它移動。不要忘了上一期所講的內(nèi)容,SetWindowPos將在這里產(chǎn)生作用,你可以移動它,或者為最后一個參數(shù)組合上SWP_HIDEWINDOW,讓開始按鈕變得不可見,或者組合SWP_SHOWWINDOW重新顯示……

 

  接下來輪到任務欄了,你從圖6中可以看到在開始按鈕的位置有另一個“厲害”的按鈕取代它,這是上一話的內(nèi)容:SetParent。我用SetParent為原本在Form1上的按鈕指定了新的父窗口——任務欄。如果你查看我的示例源程序,你會發(fā)現(xiàn)在此按鈕的GotFocus事件中,我把焦點轉(zhuǎn)移給了另一個按鈕,原因在上一話已經(jīng)說了。

 

  在示例源程序中,我還演示了隱藏和顯示任務欄,仍然是SetWindowPos的功勞,提醒一下,為了不改變窗口的一些屬性,要在最后一個參數(shù)組合上合適的值。

 

  好了,這一期的內(nèi)容就這么多,我想這一次你應該好好研究我的源程序,里面的東西涉及到上一期和本期的內(nèi)容,把它消化下去吧。

 

本站僅提供存儲服務,所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Excel窗體API應用技巧
自定義VB系統(tǒng)控件
在Visual Basic中如何拖動窗體或控件_VB窗體文章_VB_編程開發(fā)_芯友網(wǎng)
新奇:(nodejs兄弟)用HTML + FLASH +JS 也可以寫桌面EXE。
在VB中使用API函數(shù)詳解
VB 聲明中alias的作用與含義
更多類似文章 >>
生活服務
熱點新聞
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服