2. 表單

表單是我們在Microsoft Visual Basic中認識的第一個物件。雖然您也能夠只使用早期的使用者介面,像是命令列工具程式(command-line driven utilities)來撰寫有用的程式,但是大部份的Visual Basic應用程式都至少包含一或多個表單,因此需要熟悉其屬性及特性。

雖然表單及控制項在本質上不同,但卻有一個重要的相通點:它們都是Visual Basic的物件,所以它們顯現屬性、對方法的反應及事件的引發是相同的。Visual Basic可說是以物件為基礎(object-base)的程式設計語言,因為Visual Basic程式開發者的工作包含了讀取與修改物件的屬性、呼叫物件方法並對物件的事件回應。更進一步可將Visual Basic視為一視覺化程式設計環境,在其中,物件的外觀可以在設計階段(design-time)直接以視覺化互動工具來定義,,而不需要寫任何的程式碼。

表單和控制項顯露出眾多的屬性,當您在物件瀏覽器中檢測它們時可能會懷疑要如何了解所有屬性的意義。要了解這些其實需要花一段蠻長的時間,但是,一段時間之後,您可能會發覺,其實許多控制項與表單都擁有相同的屬性。

由於上述的原因,所以筆者用了一些特別的方法建構本章及後續各章。大部份的語言使用手冊都會先介紹表單,詳述每一個內建的控制項類別,並例示其特性等等。而依筆者的觀點而言,這種方式會強迫讀者把每一個物件都視為獨立案件,分別研究。這種支離破碎的資訊只會增加全盤了解的困難,更糟的是,花力氣記這些東西對了解實際運作的情形並無多大益處。舉例來說,為什麼有些控制項有TabIndex屬性而沒有TabStop屬性?又為什麼有些控制項支援hWnd屬性而有些則不支援?

幾經思考之後,筆者決定跳出傳統的說明方式,而將重點放在許多表單及內建控制項共通的屬性、方法及事件上。表單的特性在本章稍後也會提到,第三章則介紹Visual Basic的內建控制項。這表示您在本章前半段將不會看到任何完整的程式範例,但仍會有些程式碼片斷用以解釋屬性如何使用及對由大部份控制項型態所共享的事件的反應。總歸一句話,在Visual Basic環境中工作時所有物件的屬性、方法及事件的完整表列只需敲一個鍵:只要按 F2 顯示物件瀏覽器,或按 F1 取得完整的說明。我想您應該不想在此重複看那些相同的資訊吧!

還有其他的原因將通用的屬性放在一起解釋:發展到6.0版,Visual Basic已歷經了多次重大的改變。每一版都會新增新的特性,表單與控制項也會有更多更新的屬性、方法及事件。舊版相容性一直是Microsoft計劃的首要目標,因此也能支援舊有的特性。所以在Visual Basic環境下可以不需修改任何一行程式碼,也可叫出並執行Visual Basic 3的專案(唯一的例外是當程式碼參照到外部存取資料庫的程式庫或控制項)。舊版相容當然也有其缺點,第一個就是不斷增加的屬性、方法及事件。舉例來說,假設有一組複製的屬性與拖曳有關,也有二個不同的方式可設定字型屬性,結果對程式設計的初學者就會造成混淆,而程式設計老手則可能因不願再學新語法而使用舊版的(通常是較無效率的)特性。希望接下來對通用的屬性、方法及事件所做的敘述,能有助於所有讀者釐清觀念。

通用屬性(Common Properties)
 

Visual Basic 6乍看之下似乎支援各式各樣的物件屬性,不過還好,其實許多不同類別的物件都共享同一組屬性。現在來探討這些通用的屬性。

Left、Top、Width及Height屬性
 

物件(表單及控制項)的這些屬性會影響其位置和大小,而這些值常與物件收納器(container)相關,就像螢幕對表單或父表單對控制項的關係。一控制項亦可包含在其他控制項中,這樣我們可說那是它的收納器。在這個例子中,Top及Left屬性就與其收納器控制項有關。這些屬性的預設單位是twip(約1/1440英吋),這個單位能提供建立任何解析度的使用者介面,您也可以經由設定收納器的ScaleMode屬性來改變計算單位,例如像素(pixel)或英吋,不過無法變更表單已使用的單位,因為它們並無收納器:表單的Left、 Top、 Width及 Height屬性均是以twip為單位。如欲知twip這個計算單位更詳盡的資料,請參考本章的, ScaleMode 屬性一節。

在設計階段的屬性視窗中輸入數值資料時,通常會直接在其父表單中移動或重設控制項大小來做設定。請記住這點:Visual Basic已在 格式 功能表中提供許多互動式指令供您用一個動作即可調整大小、對齊及配置多個控制項位置。當然,您也可以在執行階段透過程式碼存取及修改這些屬性以調整大小:

'表單寬度加倍並移動到螢幕的左上角'
Form1.Width = Form1.Width * 2
Form1.Left = 0 
Form1.Top = 0

請注意雖然所有控制項(包括看不見的)在設計階段的屬性視窗中可顯示以上四種屬性,但看不見的控制項(例如計時器控制項)在執行階段則不支援這些屬性,因此也無法從程式碼中去讀取或修改。


注意

並非所有控制項一定要以一致的方式支援以上四種屬性,例如像下拉式組合清單控制項(ListBox)的Height屬性不論在設計或執行階段就只有讀取而不能寫入,就筆者所知,這是目前唯一屬性在設計階段的屬性視窗中看得到,但卻無法修改的例子。這是因為下拉式組合清單控制項的高度會依控制項的字型屬性而定,當您在表單中寫程式碼來修改所有控制項的Height屬性時,請記得以上這個例外。


ForeColor及BackColor屬性
 

大部份看得見的物件都有ForeColor及BackColor二個屬性,這二個屬性會影響文字本身及背景的顏色。部份控制項(例如:捲軸)的顏色是由Microsoft Windows控制,因此在屬性視窗就看不到ForeColor及BackColor二個屬性。另外有些狀況是,這二個屬性的效果會隨其他屬性改變,例如:如果設定文字標籤控制的BackStyle屬性值為0─透明,則該文字標籤控制項的BackColor就會沒有效果。指令按鈕控制項也非常特別,其有BackColor屬性但沒有ForeColor屬性,且其背景顏色僅在Style屬性值設定為1─圖形才有作用(而因為 Style 屬性預設值為0-標準,所以一般情況下,無論在BackColor中如何修改都不會有作用)。

在屬性視窗中設定以上其中一種屬性,都可選擇要標準視窗色彩、系統配色頁籤或調色盤頁籤來自訂色彩,請參考圖2-1。筆者的建議是除非另有原因一定要使用自訂色彩,否則最好使用標準色彩。系統配色在任何視窗版的機器都能良好顯示,且通常會符合您客戶的喜好,並讓您的應用程式看起來在系統中整合得相當好。第二個建議是如果想使用自訂色彩,則做一個固定的色彩架構應用在整個應用程式中。第三個建議則是:千萬不要在同一表單中同時使用這二種方式,對同一個控制項也不要在ForeColor屬性中使用標準配色而在BackColor中使用自訂色彩(反之亦然),因為這樣可能會改變系統調色盤而使控制項完全看不到。

有很多方法可在程式碼中指定配色。Visul Basic在設計階段的屬性視窗的系統配色頁籤中提供一組符號常式以對應所有的顏色:

'使Label1以所選的狀態出現。
Label1.ForeColor = vbHighlightText
Label1.BackColor = vbHighlight

表2-1列出了所有符號常數。也可以在瀏覽物件視窗中瀏覽,在最左邊的下拉式清單中按下SystemColor常式後,選擇 All libraries 或上端的下拉式組合清單控制項中的 VBRUN 。請注意這些常數的值都是負數。


 

圖2-1是在設計階段設定 ForeColor  BackColor 屬性二種不同的方法。
常數 十六進位數值 說明
vb3DDKShadow &H80000015 最深的陰影
vb3Dface &H8000000F 立體顯示項目的較深陰影
vb3Dhighlight &H80000014 立體顯示項目的反白色彩
vb3Dlight &H80000016 立體顯示項目的較淺反白色彩 (次於vb3DHilight)。
vb3Dshadow &H80000010 視窗的陰影色彩
vbActiveBorder &H8000000A 使用中視窗的框線色彩
vbActiveTitleBar &H80000002 使用中視窗的標題色彩
vbActiveTitleBarText &H80000009 現用標題、調整大小方塊以及捲軸箭號方塊中的文字
vbApplicationWorkspace &H8000000C 多重文件介面 (MDI) 應用程式的背景色彩
vbButtonFace &H8000000F 按鈕的表面色彩
vbButtonShadow &H80000010 按鈕的陰影色彩
vbButtonText &H80000012 按鈕的文字色彩
vbDesktop &H80000001 桌面色彩
vbGrayText &H80000011 灰色 (呈暫止狀態的) 文字
vbHighlight &H8000000D 控制項中選取項目的背景色彩
vbHighlightText &H8000000E 控制項中選取項目的文字色彩
vbInactiveBorder &H8000000B 非使用中視窗的框線色彩
vbInactiveCaptionText &H80000013 非使用中標題的文字色彩
vbInactiveTitleBar &H80000003 非使用中視窗的標題色彩
vbInactiveTitleBarText &H80000013 非現用視窗標題、調整大小方塊以及捲軸箭號方塊中的文字
vbInfoBackground &H80000018 工具提示的背景色彩
vbInfoText &H80000017 工具提示的文字色彩
vbMenuBar &H80000004 功能表背景色彩
vbMenuText &H80000007 功能表中的文字色彩
vbScrollBars &H80000000 捲軸灰色區域的色彩
vbTitleBarText &H80000009 現用標題、調整大小方塊以及捲軸箭號方塊中的文字色彩
vbWindowBackground &H80000005 視窗背景色彩
vbWindowFrame &H80000006 視窗外框色彩
vbWindowText &H80000008 視窗中的文字色彩
表2-1是Visual Basic所用的系統顏色之常數。

指派自訂色彩時,可以用Visual Basic定義的顏色常數來定義一些常見的色彩(vbBlack、 vbBlue、 vbCyan、 vbGreen、 vbMagenta、vbRed、 vbWhite、 and vbYellow,或使用十進位及十二進位的常數:

' 以下二陳敘式是相同的。
Text1.BackColor = vbCyan
Text1.BackColor = 16776960 
Text1.BackColor = &HFFFF00

您也可以使用RGB函數中紅色、綠色、及藍色元件的組合來建立顏色的值。最後,為了能簡化移植既有的QuickBasic應用程式,Visual Basic也支援QBColor函數:

'  以下陳敘式與上式是相同的。
Text1.BackColor = RGB(0, 255, 255)    ' red, green, blue values
Text1.BackColor = QBColor(11)

Font屬性
 

表單及控制項所能顯示的字串都有Font屬性。在設計階段,您可使用一般的文字對話方塊來設定字型屬性,請參考圖2-2。然而在執行階段則沒那麼簡單,因為Font是一個複合式的物件,所以必須分別指派屬性。Font物件有Name、Size、Bold、Italic、Underline及Strikethrough屬性。

Text1.Font.Name = "Tahoma"
Text1.Font.Size = 12
Text1.Font.Bold = True
Text1.Font.Underline = True


 

圖2-2 設計階段中,Font對話方塊能立即讓您修改所有字型屬性並且直接預覽結果。

小秘訣

可以使用Set指令來指派控制項所有的Font物件(因此不需個別設定字型屬性),請參考如下程式碼:

' 指派Text2與Text1相同的字型。'
Set Text2.Font = Text1.Font

上面的例子很明顯地可以看出如何用程式碼來對二個控制項指派相同的Font物件(objec)。這表示如果之後要改變Text1的字型,Text2也會跟著改。此作用與Font物件的本質相當調合,將在 第六章 會有更詳盡的介紹。您可以好好利用這一個方式(例如在表單中所有控制項都要使用同一字型時)。


Visual Basic 6仍繼續保有舊版的一些Font屬性,例如: FontName、FontSize、 FontBold、FontItalic、FontUnderlin及FontStrikethru,不過因為他們不會出現在設計階段的屬性視窗中,所以只能透過程式碼來做修改,您儘可使用您喜歡的語法,因為這二個形式是完全可相互替換的,在本書中,大部份我都是依循新的物件導向(object-oriented)的語法。

Font.Size屬性(或FontSize屬性)是很特異的,因為在一般的情況下,無法確知Visual Basic能否建立特定的字型大小,特別是當不使用TrueType字型時,由以下的程式碼可見一般:

Text1.Font.Name = "Courier"
Text1.Font.Size = 22
Print Text1.Font.Size  ' Prints 19.5

請注意即使指定特定不可得的字型大小也不會出現錯誤。


注意

在一般的情況下,在指定無效的字型時,Visual Basic也不會出現錯誤。在此情況下,結果會變得有些不可預期,請試看以下的程式碼:

' 警告:在您的系統可能會出現不同的結果。
Print Font.Name       ' Displays "Ms Sans Serif"
Font.Name = "xyz"
Print Font.Name       ' Displays "Arial"

Caption及Text屬性
 

Caption屬性是在控制項內部(或在表單的標題列)中顯示的一組字串,而使用者不能直接修改。相反地,Text屬性則對應到控制項的「內容」而且可以由使用者直接編輯。因為內訂的控制項都不可能同時有Caption及Text二個屬性,所以在實務上必需由屬性視窗中來看看目前工作的對象是那一個:文字標籤、指令按鈕、核取方塊、OptionButton、Data、以及Frame控制項的Caption屬性或TextBox、ListBox、and ComboBox控制項的Text屬性。

Caption屬性特別的地方在於其把「&」字元納入做控制項的快速鍵。而Text屬性被當做是控制項的預設值,可在程式碼中刪改。

' 以下的陳敘式是相同的
Text2.Text = Text1.Text
Text2 = Text1

說明

在程式碼中要指定或是省略預設值屬性的名稱取決於個人的偏好,通常我會試著在程式碼中加上所有指定的屬性名稱的參照,如此一來,程式碼會顯得較清晰易懂。不過,如果程式碼很長,要指定所有預設屬性名稱則可能使程式碼變得不具可讀性,而且可能會需要水平捲動程式碼視窗。本書將會依循這個考量,所以大部份的時侯我會指定預設屬性名稱,而如果程式碼太長的話則會省略。

在討論這個課題時,筆者發現許多程式設計師誤以為如果使用預設的屬性可以加快程式碼的執行速度,其是這是Visual Basic 3時代殘留下來的觀念,而到Visual Basic 4時就已改變控制項的內部實作方式了。

大體上而言,如果一控制項顯示Text屬性,就會支援SelText、 SelStart、及SelLength屬性,這些屬性會傳回目前在控制項中選取的文字部份之相關資訊。


Parent及Container屬性
 

Parent屬性是僅在執行階段才有的屬性(即在屬性視窗將不會看到此一屬性),這個屬性會傳回包括此控制項之表單的參照。Container屬性也是在執行階段才有的屬性,而其傳回的是控制項收納器的參照。這二個屬性彼此相關 ,當控制項直接放置在表單表面上,這二個屬性將會傳回同一物件(父表單)。

如果想將一個控制項從一個表單移動到另一個表單上,可以試著使用Parent屬性(此屬性是唯讀的),指派一不同的值到Container屬性(此屬性是可讀寫的)中則可移動控制項到另一個收納器,而因為指派的不是單純的值而是物件所以必需使用Set關鍵字:

' 移動Text1到Picture1收納器: 
Set Text1.Container = Picture1
' Move it back on the form's surface.
Set Text1.Container = Form1

Enabled及Visible屬性
 

在執行階段,所有控制項及表單的預設值都是看得到而且是可作用的。但有時也會需要將其隱藏或設成不可作用的狀態。舉個例子,假設現在要使用隱藏的DriveListBox控制項來計算系統中有多少磁碟機,因此需在設計階段的屬性視窗中設定DriveListBox控制項的Visible屬性為False,不過通常也可以在執行階段來變更這些屬性:

' 當使用者點選Check1 CheckBox控制項時使Text1有作用或無作用 
Private Sub Check1_Click()
    Text1.Enabled = (Check1.Value = vbChecked)
End Sub

無作用的控制項不會對使用者的動作有回應,但其實他還是全功能地並且仍可透過程式碼來做調整。看不到的控制項就是設定為無作用,所以不需去設定這二個屬性值為False。所有的滑鼠事件對無作用或看不到的控制項都會轉到下層收納器或表單本身。

如果一物件是另一物件的收納器(例如:表單是其控制項的收納器,而框架控制項則可是一組OptionButton控制項的收納器),設定其Visible或Enabled屬性會間接影響其所收納物件的狀態。這個特性可減少原本得寫的一組有作用或無作用的相關控制項的程式碼數量。


小秘訣

大部份的控制項當為無作用狀態時會改變其外觀。一般說來,這是個很有用的功能,因為使用者可一目了然到底那一個控制項是可以作用的。如果您一定需要使控制項無作用,但又必需讓他看起來像是在可作用的狀態,就可以將此控制項放在一個收納器中(例:框架或圖片方塊),然後設定收納器的Enabled屬性為False。 Visual Basic會讓所有收納的控制項都無作用,但看起來像是有作用(Enabled)的狀態。這個小技巧在您同時也設定收納器的BorderStyle屬性為0-None時會更好用。


有些程式設計師會設定TextBox或ComboBox控制項的Enabled屬性值為False,限定其為唯讀模式,這也是Visual Basic 3或更早的版本中的習慣,而現在的控制項另有一Locked屬性,當值為True時,控制項就是可作用的但使用者無法修改他的Text屬性。

hWnd屬性
 

hWnd屬性並不在屬性視窗中,因為其值只在執行階段才可利用到。它也是一個唯讀的屬性,因此無法指派它的值。hWnd屬性會傳回Windows內部用來辨認控制項的32位元整數值,。這個值在標準的Visual Basic程式設計是無意義的,但在呼叫Windows API常數(請參考 附錄A )時卻又非常好用。雖然可能用不到此一屬性,不過也得了解並非所有控制項都支援這個屬性以及為什麼。

Visual Basic控制項(包括VB本身內含的及外部Microsoft ActiveX二種控制項),可以被分為二大類別:標準控制項及非視窗(或LightWeight)控制項。二者間的不同可透過PictureBox(標準控制項)及Image(無視窗控制項)二個控制項的比較得知。雖則它們乍看之下很像,但在其背後可是完全不同。

在表單中放置標準控制時,Visual Basic會要求操作系統建立控制項類別的執行個體,Windows也會對此控制項做內部處理後再回傳給Visual Basic,然後程式語言再透過hWnd屬性顯示出來給程式設計師。這一連串Visual Basic對此控制項的作用(大小、字型設定等等)才真正地授權給Windows處理。當應用程式啟動一事件(例如:重設大小),Visual Basic執行時會呼叫內部的Windows API函數並將傳給它一個識別碼,如此一來Windows就會知道那一個控制項會被影響到

而Lightweight控制項(例如:Image控制項)與Windows物件則全然無關,而是完全由Visual Basic管理。就某方面而言,其實Visual Basic只是模擬控制項的存在:它會追蹤lightweight控制項並且每當表單更新時就會重畫一次。也因為其沒有任何Windows相關的識別碼,甚至Windows根本不知道有個控制在那堙A所以lightweight控制項不會有hWnd屬性。

實務上的觀點而言,標準控制項及lightweight控制項間的區別應說是:前者會消耗系統資源及記憶體而後者則不會。所以應該常要利用lightweight的控制項來代替標準控制項。舉例來說,使用Image控制項而不要用PictureBox控制項,除非是需要用到PictureBox的特性。用100個PictureBox控制項的表單要比用100個Image控制項的表單,在載入時要多上10倍的時間,希望這個說明使您更了解在其實務上的意義。

要知道一個控制項是否為lightweight,只要看他是否支援hWnd屬性即可。如果支援,那它必然是一個標準的控制項。物件瀏覽器顯示的TextBox、CommandButton、OptionButton、CheckBox、Frame、ComboBox及OLE控制項,還有捲軸及ListBox控制項及其所有的變動都是標準的控制項。而Label、Shape、Line、Image及Timer控制項並無hWnd屬性則應該可考慮其為lightweight控制項,不過得注意在外部的ActiveX控制項中唯無hWnd屬性,但不能將之歸為非視窗的,因為這個控制項的設立就是不希望將視窗的識別碼顯現於外的原故。在本章後續講述關於Zorder方法時會對此二類屬性再做深入介紹。

TabStop及Tabindex屬性
 

可以接受輸入焦點(focus)的控制項都有TabStop屬性。大部份固有的控制項都支援此一屬性,包括TextBox、OptionButton、CheckBox、CommandButton、OLE、ComboBox、二個捲軸、ListBox控制項及其所有的變數。大致上而言,固有的lightweight控制項並不支援此一屬性,因為其永遠不會收到輸入焦點。這個屬性的預設值為True,不過不論是在執行或設計階段都可再設定為False。

支援TabStop屬性的控制項同時也會支援TabIndex屬性,這二個屬性會影響Tab的次序,意即當使用者重覆按下Tab鍵時,會影響控制項出現的順序(另請參考第一章中 〈設定Tab次序〉 的部份)。Label及Frame控制項雖然也支援TabIndex屬性,不過因為這二個控制項並不支援TabStop屬性,其所產生的變化是當使用者點選Label或Frame控制項(或按Label或Form中Caption屬性定的快速鍵),輸入焦點會跑到依Tab次序所出現的控制項。因此可利用此一特性來讓Label及Frame控制項提供一快速鍵到另一個控制項

' 讓使用者能按Alt+N快速鍵來移動Text1控制項的輸入焦點。
Label1.Caption = "&Name"
Text1.TabIndex = Label1.TabIndex + 1

MousePointer及MouseIcon屬性
 

這二個屬性影響滑鼠游標在控制項上移動的形狀,Windows提供非常有彈性的滑鼠游標管理,每一個表單或控制都可以有不同的游標,您可以使用全域的Screen物件來設定應用程式範圍內的滑鼠游標,雖然如此,但會影響實際滑鼠規則的將不被允許:

如欲使用者移動滑鼠時出現沙漏游標,可參考以下程式碼:

'長篇的常數
Screen.MousePointer = vbHourglass
...
' 在這堻B理您的工作
...
' 記得改回儲存預設的游標
Screen.MousePointer = vbDefault

請看另外一個例子:

' 當滑鼠在Picturee1上時顯示十字型游標
' 當滑鼠在父表單上其他控制時顯示沙漏游標
Picture1.MousePointer = vbCrosshair
MousePointer = vbHourglass

MouseIcon屬性是用以提供使用者自訂滑鼠。在這個例子中,需設定MousePointer值為99-vbCustom然後再指定一個圖示給MouseIcon屬性。

'顯示紅色的停止符號滑鼠游標。實際上的路徑有可能會因Visual Basic
 安裝的主目錄不同而不一樣。
MousePointer = vbCustom
MouseIcon = LoadPicture("d:\vb6\graphics\icons\computer\msgbox01.ico")

在執行時可以勿需以LoadPicture指令來載入自訂滑鼠,舉例來說,可以在設計階段的屬性視窗中將其指派到MouseIcon屬性中,請參看圖2-3,當需使用時再設定MousePointer值為99-vbCustom即可使用。如欲對同一控制項可選擇多種游標,又不想增加額外的檔案,則只需在隱藏的Image控制項中多載入ICO檔案,就可以在執行時予以切換。


 

圖2-3. Visual Basic 6提供許多現成的自訂滑鼠游標、圖示及點陣圖在\GRAPHICS子母錄下。

Tag屬性
 

所有的控制項亳無例外地都有Tag屬性,甚至是ActiveX控制項(包括任何他家廠商的控制項)都有。我之所以這麼肯定是因為這是Visual Basic本身所提供的屬性,而不是由控制項提供的。 Tag並非Visual Basic對控制項所提供的唯一屬性:像Index、Visible、TabStop、TabIndex、ToolTipText、HelpContextID及WhatsThisHelpID屬性都屬同一類。這些屬性都被當作是延伸的屬性。請注意一些延屬性需在某些條件下才有,例如TabStop就只在控制真正收到focus才會出現,而Tag屬性很特別,永遠都會有,您也可在程式碼中參照到他而不用擔心會執行時出現任何錯誤。

Tag屬性對Visual Basic沒有特別的意義:其只是要儲存到控制項的值的收納器。例如:您可用他來儲存控制項的初始值,如此當使用者需要取消變更就可以輕而易舉地回復到原來的值。

其他屬性
 

Value屬性是許多固有的控制項非常普遍的屬性,像在CheckBox、 OptionButton、CommandButton及捲軸控制項甚至是許多外部的ActiveX控制項中都有。此一屬性的意涵在不同的控制項就會不同,但他們一定都是數值或布林屬性。

Index屬性是建立控制項陣列的關鍵, Visual Basic精密的特性能幫您建立多種用途的程式( 第三章 將會進一步介紹控制項陣列)。如不需建立控制項陣列,只要在設計階段的屬性視窗中將此一屬性保持空白即可。此一屬性在執行階段對屬於控制項陣列的控制項而言是唯讀的。另外,如果參照到一非屬控制項陣列的控制項之Index屬性, Visual Basic就會產生錯誤。

大部份固有的控制都支援Appearance屬性,也只能在設計階段指定值,在執行階段就變成唯讀的了。Visual Basic會預設建立3-D外觀的控制項,除非修改了此一屬性值為0-Flat,對舊程式您可能會為了與其他程式看起來一致而去修改他,但對新的應用程式,您大可不必管他,只要將Appearance屬性設成預設值(1-3D)即可。

Visual Basic可以自動貼附一個控制項到其父視窗的周沿,只要設定他的Align屬性值為non-Null 。只有二個固有的控制項有此一屬性: PictureBox及Data控制項,另外, 多數外部的ActiveX controls也可以。此一屬性可能的值有: 0-None、1-Align Top、2-Align Bottom、3-Align Left、及4-Align Right。

支援BorderStyle屬性的控制項有: TextBox、Label、Frame、PictureBox、Image及OLE控制項。設定此屬性的值為0-None可壓縮控制器的邊框成1-Fixed Single。表單也支援此一屬性,但其允許各種不同的設定(本章後部將再作介紹)。

在大部份Windows應用程式中移動滑鼠到一控制項或圖示並停留一下,通常就會出現ToolTip的黃色視窗。(圖2-4是ToolTip顯示出的有用提示)。 到了Visual Basic 4,程式開發人員必需建立特別的常數或買其他家的工具來增加這程式的這個機能。而在Visual Basic 5及6,則僅需要指定給控制項中的ToolTipText屬性一組字串即可。可惜的是,表單物件並不支援此一屬性。請注意ToolTip的位置及大小是不受您控制的,只能在系統值的範圍內修改前景及背景顏色(開啟Control Panel視窗,雙按Display屬性對話框或圖示然後移動到Display屬性對話框中的Appearance,即可修改ToolTips的字型及背景顏色)。


 

圖2-4 ToolTip請您輸入姓名。

DragMode及DragIcon屬性(及Drag方法)是用來拖曳表單上的控制項,不過他們已被OLExxxx方法及屬性所替代。舊有的屬性雖然仍相容,不過為使程式與相符於Windows 95的標準請勿再使用。OLE Drag及Drop屬性、方法及事件將在 第九章 中的〈使用拖曳〉的部份再詳述。

可使用LinkMode、LinkTopic、LinkItem及LinkTimeout屬性(以及LinkPoke、 LinkExecute、LinkRequest及LinkSend方法)來驅使一控制項或表單透過DDE (Dynamic Data Exchange)協定與其他控制項及表單(甚至在不同的應用程式中)溝通。在OLE及COM問世前, DDE是二個Windows程式互通最好的方式。而時至今日,則不應再使用此技術,因為這些屬性僅在舊版的程式中可予以維護。所以本書將不討論DDE的部份。

一般的方法
 

如同許多物件共通的屬性一樣,也有很多共通的方法。接下來將一一檢視這些方法。

Move方法
 

支援Left、Top、Width及Height屬性的控制項就可支援Move方法。因此可以透過單一步驟即可同時改變這四個中任幾個或全部的屬性,下面的例子就改變了Left、Top及Width屬性:

' 雙按表單的寬度將其移動到螢幕的左上角
' 語法:Move, Left, Top, Width, Height.
Form1.Move 0, 0, Form1.Width * 2

請注意除了第一個以外的參數都是可自選的,不過在指令中間卻不能將其任何一項省略。舉例來說,如果省去Width參數就不能略過Height參數,請注意筆者曾在個別屬性中特別提到Height屬性在ComboBox控制項中是唯讀的。


小秘訣

Move方法通常只會指派到單一屬性,原因是:這樣作比指派到四個屬性要能增加二至三倍的速度,另外,如果需修改表單中Width及Height的屬性時,任一屬性的指派都會觸發別的Resize事件,而使程式碼的效能大幅減低。


Refresh方法
 

Refresh方法可重繪控制項。一般來說是不會呼叫這一方法,因為當控制項變更時(通常是在無使用者程式碼執行且Visual Basic處於閒置的狀態下),Visual Basic會自動更新其外觀,不過若要在修改控制項屬性且希望使用者介面也立即更新的話,仍可召喚Refresh方法:

For n = 1000 To 1 Step -1
    Label1.Caption = CStr(i)
    Label1.Refresh          ' 立即更新Label
Next

注意

也可使用DoEvents指令來更新表單,因為這個指令可釋放控制項到Visual Basic中, Visual Basic表單引擎就可以更新使用者介面。不過要注意DoEvents也會造成執行額外的程序(例如:他會檢查是否有任何按鈕已被點選,若是,會先執行Click程序)。因此,這二個方式其實不盡相同,一般說來,只對已被修改的控制項使用Refresh方法會比執行DoEvents指令效果更好。再者,這樣也可避免再度進入會引起錯誤,例如:當使用者在之前的Click程序還沒完成再次點選同一按鈕。若要新表單上所有控制項而不希望用戶端能與程式互動時,請在父表單中執行Refresh方法即可。


SetFocus方法
 

SetFocus方法可在特定的控制項移動輸入focus。在設計階段,欲修改以設定表單上控制項的TabIndex屬性之預設定位點的順序時(請參考 第一章 ),就需要呼叫這個方法。這時控制項的TabIndex屬性值應為0,當表單載入即可接受focus。

SetFocus方法有個潛在的問題就是:如果控制項目前是無作用或是看不到的,那麼這個方法就無效且會在執行時產生錯誤,因此,在Form_Load事件(當還無法看見所有控制項時)要避免使用此方法,且應以下列二種方式處理:第一個是確認控制項已準備好接受focus,第二個是在On Error除述式中先預防錯誤的發生,第一個方式請參考以下程式碼:

'移動focus到Text1. 
If Text1.Visible And Text1.Enabled Then
    Text1.SetFocus
End If

以下為第二個方式的程式碼,利用On Error陳述式:

'移動focus到Text1
On Error Resume Next
Text1.SetFocus

小秘訣

SetFocus方法通常在Form_Load事件程序中用以設定當表單第一次出現時,那一個控制項可接受focus。

Private Sub Form_Load()
    Show          ' 使表單成為可見的
    Text1.SetFocus
End Sub

以下提供另一個解決方案:

Private Sub Form_Load()
    Text1.TabIndex = 0  
End Sub

請注意如果Text1無法接受輸入駐點(例如:其TabStop屬性設定為False),Visual Basic會自動依定位點次序移動駐點到下一個控制項,而不產生任何錯誤。第二個方式的缺點在於其會影響表單上所有其他控制項的Tab次序。


Zorder方法
 

Zorder方法影響控制項與其它重疊的控制項誰可看得見。如要將一控制項蓋在另一個控制項上面,只要執行這個方法而不需任何參數,又或者也可以設1為參數移到後面:

' 移動表單上一控制項到另一控制項後面
Text1.ZOrder 1      
Text1.ZOrder        ' 搬到前面

請注意也可於設計階段使用 格式 清單 Order 子目錄的指令來設定控制項的相對z-order,也可以按 Ctrl+J 鍵將選取的控制項帶到前面,或按 Ctrl+K 鍵搬到後面。

Zorder方法實際的動作會因控制項為標準的或無視窗的而不同。事實上,無視窗的控制項根本不會出現在標準控項的前面,換句話說,這二種控制項分別在z-order二個不同的層次中,標準控制項的層次會在無視窗控制項的層次之前,這表示Zorder方法只在其所屬的層次中可以改變控制項相對的z-order 。舉例來說,一個能將一Label(無視窗的)控制項放在TextBox(標準的)之前,不過,若標準的控制項可作用成收納器控制項(例:PictureBox或Frame),舉例來說,如果將無視窗的控制項包含在收納器控制項中,就可以顯示在標準的控制項之前,請參考圖2-5。

Zorder方法也適用於表單,可以在相同的Visual Basic應用程式中將一表單送到另一表單的下面或前面,不過若為控制表單與其他應用程式所屬視窗的相對位置,則不要用這一個方法。


 

圖2-5 控制項的相對z-order。

通用的事件
 

除了通用的屬性及方法,Visual Basic 6表單及控制項也有通用的事件,以下本節將逐一介紹。

Click及DblClick事件
 

Click事件是當使用者在控制項上按滑鼠左鍵時會發生,DblClick事件是在控制項上雙按滑鼠左鍵發生。不過這麼簡單的分法並非絕對的,Click在其他情況下也可能發生。舉例來說: CheckBox或OptionButton控制項的Value屬性不論什麼時候經由程式碼變更,Visual Basic就會觸發Click事件,如果使用者點選它也是一樣會觸發該事件。這個動作可以讓我們用相同的方式處理二個不同的案例。每當ListBox及ComboBox控制項的ListIndex屬性變改,也會觸發Click事件。

Click與DblClick事件不會傳參數到程式中,因此不能依賴這二個事件來告知目前滑鼠的位置,要知道滑鼠的位置要另外用MouseDown事件,稍後會對此一事件另作介紹。另外請注意當雙按控制項時,其會同時接受Click及DblClick事件,因此很難去判斷是單點選二次還是雙按一次,因為當Visual Basic呼叫Click事件程序後是否會再呼叫DblClick程序,所以請避免對相同的控制項指派不同的函數到click及double-click動作中,以免令使用者混淆。


小秘訣

因為對同一控制項不能個別指派到click及double-click二個動作,以下是這個簡易的方法能找出使用者真正做了什麼動作:


 

' 模組層級變數
Dim isClick As Boolean

Private Sub Form_Click()
    Dim t As Single
    isClick = True
    ' 預留半秒鐘等待第二次點選
    t = Timer
    Do
        DoEvents
        ' 如果DblClick程序取消此事件,則跳出
        If Not isClick Then Exit Sub
        ' The next test accounts for clicks just before midnight.
    Loop Until Timer > t + .5 Or Timer < t
    ' 將你的單擊(single-click)程序寫在這裡
    ...
End Sub
Private Sub Form_DblClick()
    ' 取消任何的預備Click
    isClick = False
    ' 將你的雙擊(double-click)程序寫在這裡
    ...
End Sub

Change事件
 

Change事件是Visual Basic所提供最簡單的事件:只要控制項的內容改變,Visual Basic就會觸發Change事件。不過很可惜,這麼簡單的設計並不相符於Visual Basic的架構。如同我在上一節中所提到的,當點選CheckBox及OptionButton控制項時,即觸發Click事件(而不是Change事件),不過,這不相符的地方倒還不算嚴重。

當使用者在TextBox及ComboBox控制項可編輯的區域打一些東西,就會產生Change事件(不過請注意:當ComboBox產生的Click事件是在使用者選取自清單中選取項目時,而不是在框框中打了字)。捲軸控制項則是當使用者點選縮放或移動捲軸時會產生Change事件。PictureBox、DriveListBox及DirListBox等控制項也都支援Change事件。

當控制項的內容透過程式碼改變時也會觸發Change事件,此一行為通常會致使程式變得有些無效率。舉例來說:大部份的程式設計師會在表單的Load事件中初始化所有TextBox的Text屬性,因此觸發太多的Change事件而讓載入程序變慢。

GotFocus及LostFocus事件
 

這二個事件觀念上很簡單:當控制項接受輸入駐點時,GotFocus就會觸發,而當輸入駐點移開到另一控制項時,LostFocus就會觸發。乍看之下,這二個事件似乎是可理想地執行一組確認機制(就是指當輸入值並不正確而使用者又移到其他控制項時,檢查欄位及通知使用者的程式碼片斷)。有時,這二事件的結果受許多因素的影響:包括MsgBox及DoEvents陳述式的表現。還好Visual Basic 6已介紹了新的Validate事件能漂亮地解決確認欄位的問題(請另見第三章中的 CausesValidation屬性及Validate事件 )

最後,請注意表單同時支援GotFocus及LostFocus事件,不過這二事件都只在表單不含任何接受輸入駐點的控制項時發生,不論是因為所有控制項是看不到的或是其TabStop屬性設為Fales。

KeyPress、KeyDown及KeyUp事件
 

這三個事件是每當用戶端在有輸入駐點的控制項上按鍵時就會觸發。其發生的順序如下:KeyDown(使用者按下按鍵)、KeyPress(Visual Basic將按鍵譯成ANSI數字碼)及KeyUp (使用者放開按鍵)。只有與控制相關的按鍵(例如: Ctrl+x  BackSpace  Enter  Esc )及可印出的字元在KeyPress事件中有作用。至於其他的按鍵(包括方向鍵、功能鍵及 Alt+x 等等)則不會觸發這個事件但會產生KeyDown及KeyUp事件。

KeyPress事件是這三個事件中最簡單的事件了,他可傳出使用者按下的按鍵ANSI碼,因此通常需用Ch$() 函數將其轉換為字串:

Private Text1_KeyPress(KeyAscii As Integer)
    MsgBox "User pressed " & Chr$(KeyAscii)
End Sub

如修改KeyAscii的參數,則會影響程式解譯按鍵的方式,也可以設定參數值為0來「吃掉」一個按鍵,請看看以下程式碼:

Private Sub Text1_KeyPress(KeyAscii As Integer)
    ' 將所有按鍵轉換成大寫並去除空白.
    KeyAscii = Asc(UCase$(Chr$(KeyAscii)
    If KeyAscii = Asc(" ") Then KeyAscii = 0 
End Sub

KeyDown及KeyUp事件可接受二個參數的設定:KeyCode及Shif。KeyCode是按下按鍵的程式碼,而Shift則是回報Ctrl、Shift及Alt按鍵狀態的一整數值,因為這個值是位元程式碼,所以必需使用AND運算子來擷取相關資訊:

Private Sub Text1_KeyDown(KeyCode As Integer, Shift As Integer)
    If Shift And vbShiftMask Then
        ' Shift key pressed
    End If
    If Shift And vbCtrlMask Then
        ' Ctrl key pressed
    End If
    If Shift And vbAltMask Then
        ' Alt key pressed
    End If
    ' ... 
End Sub

KeyCode參數可說明那一個按鍵被按下,因此與KeyPress事件所接受的KeyAscii參數是不同的。通常可以用符號常數來測試此一值,請參看以下程式碼:

Private Sub Text1_KeyDown(KeyCode As Integer, Shift As Integer)
    ' 若使用者按[Ctrl]+[F2],則替換控制項的內容為目前的日期 
    If KeyCode = vbKeyF2 And Shift = vbCtrlMask Then
        Text1.Text = Date$
    End If
End Sub

相對於KeyPress事件,如果指派不同的值到KeyCode參數中,則無法變更程式的行為。

要特別注意KeyPress、KeyDown及KeyUp事件在偵錯的階段可能會出現特別的問題。事實上,如果在KeyDown事件程序中放置一個中斷點,則目標控制項將永遠不會接受按鍵的通知,並且KeyPress及KeyUp 事件也不會觸發。相同的,如果當Visual Basic在執行KeyPress事件程序時進入中斷模式,目標控制項則可以接受按鍵,不過KeyUp事件仍不會觸發。


小秘訣

當無法編輯KeyCode參數也不能以修改值來影響程式時,這奡ㄗ悀@個小技巧:大部份的時候,可以在TextBox控制項中釋放多餘的按鍵:

Private Sub Text1_KeyDown(KeyCode As Integer, Shift As Integer)
    If KeyCode = vbKeyDelete Then
        '使控制項為唯讀,則可釋放按鍵
        Text1.Locked = True
    End If
End Sub
Private Sub Text1_KeyUp(KeyCode As Integer, Shift As Integer)
    ' 儲存正常的運算子
    Text1.Locked = False
End Sub

KeyDown、KeyPress及KeyUp事件僅當按鍵按在有輸入駐點的控制項上才可被接受,這個情形使得建立表單層級的按鍵識別碼(即:程式碼監視在表單上任何控制項按下的按鍵之程式片斷)時顯得困難,舉例來說,假設現需讓使用者可按[F7]來清除目前欄位,又不想在KeyDown事件程序中對每一個表單上每一個控制項寫同樣片斷的程式碼,則只要設定表單的KeyPreview屬性為True(例:在設計階段或執行階段都可以在Form_Load程序中做),然後寫下以下的程式碼:

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
    If KeyCode = vbKeyF7 Then
        ' An error handler is necessary because we can't be sure
        ' that the active control actually supports the Text
        ' property.
        On Error Resume Next
        ActiveControl.Text = ""
    End If
End Sub

若表單的KeyPreview屬性已設為True,則表單物件接受所有在其被送到目前有輸入駐點的控制項之前與鍵盤相關的事件,如欲在有輸入駐點的控制項上動作,則可使用上例程式碼片斷中表單的ActiveControl屬性。

MouseDown、MouseUp及MouseMove事件
 

這三個事件是在滑鼠在控制項上被點選、釋放或移動時發生,三者都接受相同的參數組:滑鼠按鈕的狀態、Shift/Ctrl/Alt按鍵的狀態以及滑鼠游標的x及y座標,此座標是相對於控制項或表單的左上角而定。圖2-6是顯示在Label控制項上的滑鼠狀態及位置,以及建立在即時視窗中的日誌之程式碼範例。也可於圖2-6中看到程式碼執行的結果。


 

圖2-6 以MouseDown、MouseMove以及MouseUp事件來監看滑鼠狀態。請注意當游標跑出表單的範圍時值為負。
Private Sub Form_MouseDown(Button As Integer, _
    Shift As Integer, X As Single, Y As Single)
    ShowMouseState Button, Shift, X, Y
End Sub
Private Sub Form_MouseMove(Button As Integer, _
    Shift As Integer, X As Single, Y As Single)
    ShowMouseState Button, Shift, X, Y
End Sub
Private Sub Form_MouseUp(Button As Integer, _
    Shift As Integer, X As Single, Y As Single)
    ShowMouseState Button, Shift, X, Y
End Sub
Private Sub ShowMouseState (Button As Integer, _
    Shift As Integer, X As Single, Y As Single)
    Dim descr As String
    descr = Space$(20)
    If Button And vbLeftButton Then Mid$(descr, 1, 1) = "L"
    If Button And vbRightButton Then Mid$(descr, 3, 1) = "R"
    If Button And vbMiddleButton Then Mid$(descr, 2, 1) = "M"
    If Shift And vbShiftMask Then Mid$(descr, 5, 5) = "Shift"
    If Shift And vbCtrlMask Then Mid$(descr, 11, 4) = "Ctrl"
    If Shift And vbAltMask Then Mid$(descr, 16, 3) = "Alt"
    descr = "(" & X & ", " & Y & ") " & descr
    Label1.Caption = descr
    Debug.Print descr
End Sub

在寫滑鼠事件的程式碼時,必需注意在用這些事件時一些執行的細節及易犯的錯誤,請記得以下幾點:

接下來看看MouseDown、MouseUp及MouseMove事件與Click及DblClick事件的關係:

表單物件
 

在詳細介紹大部份Visual Basic物件通用的屬性、方法及事件後,現在來個別看看其特點。最重要看得到的物件首推表單物件了,因為如果沒有父表單則無法顯示任何控制項,反之,只靠表單而沒有任何控制項也可以寫一些好用的應用程式。在本節中將以一些範例來介紹表單奇特的性質。

在設計階段,可從 專案 清單中的 新增表單 指令或在標準工具列中點選同一圖示來建立新的表單。可以用劃線來建立表單也可以利用許多Visual Basic6所提供的表單模板。若無法看到圖2-7所顯示的對話方塊,請從 工具 清單中召喚 選項 指令,圖選 環境 頁籤,再選右邊最上面的核取方塊即可。

每當需要建立新表單時,大可儘量使用表單模板。表單模板並不一定是會有很多控制項的複雜表單,即使只是有一組已小心設定屬性的空白表單也能節省一些寶貴的時間。例如:Visual Basic所提供的對話表單模板。如欲產生自訂的表單模板,則只需建立一個表單,再加上一些必需的控制項,然後將其儲存於\Template\Forms目錄下(Visual Basic模板的完整路徑可於 選項 對話框中的 環境 頁籤中讀取並修改。


 

圖2-7 Visual Basic 6所提供的表單模板

表單的基本屬性
 

建立了所需求大小的表單後,您可會想再設定一些重要的屬性。BorderStyle可算是影響表單行為最大的屬性了,其預設值為2-大小可調整,即能建立可調整大小的視窗。如欲建立的是不可調整大小的表單,則其值應設為1-單線固定或3-雙線固定對話方塊:二種設定的不同在於後者不會出現 縮到最小  放到最大 的按鈕。如欲建立浮動的、 像工具箱的表單,則值需設為4-單線固定工具視窗或5-可調整工具視窗。理論上,也可以使用0-沒有框線來移除任何一種框線及標題,不過鮮少會用到沒有框線的表單。

接下來該決定在標題列要出現的東西了。除了在表單的Caption屬性中指派適當的字串外,也應決定是否要讓表單支援系統清單及 關閉 按鈕(ControlBox屬性,預設值為True)、 縮到最小  放到最大 按鈕(分別是MinButton及MaxButton屬性)。為這些屬性選擇正確的值非常重要,因為在執行階段是無法以程式碼再來修改的。如希望表單一開始就是放到最大的狀態,則可設WindowState屬性值為2-最大化。


小秘訣

如欲建立無標題、大小可調整的視窗,則需設定ControlBox、MinButton及 MaxButton屬性值為False,並設定Caption屬性為空字串。如果在執行階段指派Caption為非空字串,Visual Basic就會建立表單的標題在其上,無標題列的表單無法像其他型態的視窗一樣可以滑鼠移動。


Visual Basic 5新增了三個重要的表單屬性,且在Visual Basic 6仍繼續保留。可以設定StartupPosition屬性值為2-螢幕中央,來讓表單在螢幕中央出現。也可以設定Moveable屬性值為False,使視窗無法被移動。只有在設計階段可設定以上二個屬性。第三個則是ShowInTaskbar;如設定屬性值為False,則表單不會出現在視窗系統的工具列上,。無標題的表單在工具列上則看起來像一個「空的」表單一樣,因此對這種表單最好設定ShowInTaskbar屬性值為True。

表單的效能調整
 

有些表單的屬性非常明顯地影響其效能。第一名首推AutoRedraw屬性,其可命令表單是否要由一固定的點陣圖來備份,如此一來,當其被其他表單覆蓋後仍能顯露出來,因為Visual Basic能快速地以內部的點陣圖來重建其內容。AutoRedraw的預設值為False:設定為True可加速更新運算子不過也會造成大量記憶體安排到這個固定的點陣圖。為讓您了解實務上其意義:對1024*768螢幕解析度及256色的系統而言,一個在可調整大小表單上固定的點陣圖會佔去768KB;而在800*600像素,全彩系統上則佔1406KB。若有更多的表單在同時間執行,更能證明不可將AutoRedraw屬性值設為True,至少不要是所有的表單都設為True。AutoRedraw還會用別種方式影響效能:每次執行繪圖方法(包括印出文字及畫圖形),Visual Basic在隱藏的固定點陣圖上建立輸出,然後複製整個點陣圖到表單上看得到的區域。不用說,這一定比在表單上直接建立輸出要慢得多了。


小秘訣

如果AutoRedraw設為True,Visual Basic將在表單上建立最大可能的固定點陣圖,也就是指整個可調大小視窗的螢幕。因此,若是建立較小的表單並設定其BorderStyle屬性為1-單線固定或3-雙線固定對話方塊,就可以限制記憶體因固定點陣圖所引起的消耗。


ClipControls屬性也會影響效能,如果執行許多繪圖方法(例如:Line、Circle、Point及Print),則應設定屬性值為False,因為所有用到的繪圖方法都會執行二次。當設定屬性值為False,Visual Basic就不需要建立剪取區,然而,如果所用的繪圖方法與表單的控制項重疊,則會產生如圖2-8中很差的效果,所以要特別注意。(與圖2-9比較,其所顯示的雖是同一應用程式,但是ClipControls值設為True是更適合的)。如不執行繪圖方法,因為其不會讓應用程式變慢,則可將其值設為True(預設值)。


 

圖2-8 當繪圖方法與存在的控制項重疊,設定ClipControls的巢狀效果。


 

圖2-9 以更適合的ClipControls屬性值執行圖2-8中的應用程式

HasDC屬性是Visual Basic 6的新成員,預設值為True,可引發Visual Basic建立表單常置的device Context,device Context容將和表單載入記憶體後共存亡(device Context是Windows用以在視窗表面繪圖所使用的結構)o若設定這個值為False,則Visual Basic僅會在確實需要時為表單建立device context並於不再使用時即予消滅。此種設定就系統資源而言會減少應用程式的需求要件,因而可增進效能。在另一方面,每當Visual Basic建立及消滅暫存的device context也會增加一些額外的負擔,這種情形在Visual Basic於程式碼中觸發事件時會發生。


注意

您在設定HasDC屬性為False時也仍能亳無問題地執行任何已存在的Visual Basic應用程式。如果現在要用進階繪圖技巧,越過Visual Basic直接寫入表單的device context,則不可以將表單的hDC屬性存到模組或廣域變數中,因為如此一來Visual Basic會於二事件間消滅並重建表單的device context,所以應該在每一事件程序一開始就先詢問hDC屬性的值。


表單的生命週期
 

要了解表單物實際上是如何運作,最好的途徑就是來看看事件發生的順序。請參考以下圖解說明,我也會依序講解各個事件。


 

Initialize事件
 

任何表單生命周期的第一個事件就是初始化事件。一但參照到程式碼中表格的名字,即使是在Visual Basic在其表面建立實體的視窗及控制項之前,就會觸發此事件,通常在事件中會用程式碼來正確初始化表單的變數:

Public CustomerName As String
Public NewCustomer As Boolean

Private Sub Form_Initialize()
    CustomerName = ""      ' 這一行並不真的需要
    NewCustomer = True     ' 這一行為必要
End Sub

當表單初始化之後,其所有的模組級變數(例如在以上程式中的CustomerName及NewCustomer)值會被指派成他們的預設值。所以當其值為0或空字串時,並不很需要去指派給變數一個值。例如在以上的程式碼中,則無需指派CustomerName變數一個值,不過您可能會希望保留其值以便有較好的可信度。

Load事件
 

在初始化之後所發生的事件會因程式碼如何參照到表單而有不同,如果只參照到其某一公用變數(或更正確地說是Public屬性),請參考下面這行程式碼:

frmCustomer.CustomerName = "John Smith"

如無其他事件發生則執行流程會回到呼叫處程式(caller)。現在程式碼設定了CustomerName變數/屬性一個新值,因為在此時, Visual Basic已建立了frmCustomer物件一個新的執行個體。在另一方面,如果程式碼在表單本身參照到自己的屬性或控制項,則Visual Basic無法完成運作,直到其建立真正的視窗及其子控制項。當此步驟已完成,便觸發Load事件:

Private Sub Form_Load()
    ' 初始化子控制項 
    txtName.Text = CustomerName
    If NewCustomer Then chkNewCustomer.Value = vbChecked
End Sub

在此時表單仍然看不到,意思就是如果在這個事件程序中執行繪圖指令(包括Print指令,因為Print指令在Visual Basic中被認為是繪圖指令)也看不到任何東西。同樣的,當可自由修改大部份控制項屬性的時候,也應避免任何無法於看不見的控制項上執行的指令。例如,不去召喚SetFocus方法在特殊控制項來移動駐點。

載入表單並不一定表示表單就會變成看得見的。只有在召換表單的Show方法或該表單是應用程式的啟動表單,才會成為看得見的表單。如下,可以決定要載入表單還是隱藏到設定其某些屬性為止:

'Load方法是可自由選擇的: 
'Visual Basic在參照到表單或其控制項時會載入表單
Load frmCustomer
' 直接指派控制項屬性Directly assign a control's property
' (不建議這麼做,不過仍供參考)
frmCustomer.txtNotes.Text = gloCustomerNotes
frmCustomer.Show

如上範例中的程式碼直接從表單外部參照表單的控制項,其實是很差勁的程式技巧。在 第九章 會另外介紹如何正確地初始化控制項屬性。

Resize事件
 

一當表單變成看得見時,Visual Basic即會觸發Resize事件。通當這個事件是用來重新安置表單上的控制項,如此一來其可配合空間而有較佳的版面配置。例如txtCustomer控制項延長右邊界及多線的txtNotes控制項延長右邊界及下邊界:

Private Sub Form_Resize()
    txtCustomer.Width = ScaleWidth _ txtCustomer.Left
    txtNotes.Width = ScaleWidth _ txtNotes.Left
    txtNotes.Height = ScaleHeight _ txtNotes.Top
End Sub

當使用者手動調整表單大小及當程式提示表單的大小時,也會觸發Resize事件 。

Activate事件
 

緊接在第一次的Resize事件之後的就是Activate事件了。無論何時表單在目前的應用程式中變成作用中表單,也會觸發這個事件,不過當表單件失去駐點(focus)後即使又再取得也不會觸發這個事件,因為使用者已跳到另一個應用程式中了。Activate事件在需要更新表單的內容之資料已在其他應用程式中被修改過時最好用了。當駐點回到目前的表單時,即同時更新其欄位:

Private Sub Form_Activate()
    ' 更新顯示廣域變數的資訊
    txtTotalOrders.Text = gloTotalOrders
    ...
End Sub

Paint事件
 

在表單能完全作用前可能會觸發的另一事件可能是Paint事件。這個事件在設定表單的AutoRedraw屬性為True時則不會觸發。在Paint事件程序中,將預期會使用繪圖方法(例如:Print、Line、Cicle、Point、Cls等等)來重畫表單的內容,以下是畫一彩色圓靶的範例:

Private Sub Form_Paint()
    Dim r As Single, initR As Single
    Dim x As Single, y As Single, qbc As Integer
    
    ' 以清除畫面開始
    Cls                                 
    ' 所有圓的圓心
    x = ScaleWidth / 2: y = ScaleHeight / 2 
    ' 原始半徑會比這二個值小
    If x < y Then initR = x Else initR = y
    FillStyle = vbFSSolid            ' Circles are filled.
    ' 由外向內畫圓 
    For r = initR To 1 Step -(initR / 16)
        ' 每一個圓使用不同的顏色
        FillColor = QBColor(qbc)
        qbc = qbc + 1
        Circle (x, y), r
    Next
    ' 重建一般填滿型態
    FillStyle = vbFSTransparent           
End Sub

Paint事件程序每當表單需要更新時就會執行,例如:當使用者關閉或移動一部份或完全覆蓋表單視窗的時候。當使用者調整表單的大小,或移開顯示新區域時,也會觸發Paint事件。不過當使用者縮小表單時則不會發生。為使以上範例完整,可能還會需要在Resize事件中手動來觸發Paint事件,這樣一來,同心圓就會永遠都在表單的正中間:

Private Sub Form_Resize()
    Refresh
End Sub

注意

千萬不可以手動呼叫Form_Paint來強迫使Paint事件程序發生,真正正確而最有效率的方式應是重畫一個視窗來執行其Refresh方法,然後讓Visual Basic決定最適當的時機來做。再者,如果直接以呼叫Form_Paint程序來取代Refresh方法,在其些狀況下結果會變成Paint事件程序執行二次。


在第一次的Paint事件之後(或是當AutoRedraw屬性為True時,緊跟在Active事件之後),表單即準備好接受使用者輸入資料。而如表單並不包含任何控制項或其控制項沒有一個是可接受輸入駐點的話,表單本身會接受GotFocus事件。其實很少需在表單的GotFocus寫程式碼,因為通常會以Activate事件來代替。

Deactivate事件
 

如同之前曾提到的,當跳到應用程式中另一個表單時,這個表單即接受Deactivate事件而另一個Activate事件則會保留輸入駐點。如果召喚Hide方法來設定Visible屬性為False,而暫時隱藏表單,也會有同樣的結果。

QueryUnload事件
 

當表單將被移除,則表單物件會接受QueryUnload事件,藉由檢驗UnloadMode參數來知道表單為什麼會被移除。筆者已建立在筆者的應用程式中可供利用的程式碼架構如下:

Private Sub Form_QueryUnload(Cancel As Integer, _
    UnloadMode As Integer)
    Select Case UnloadMode
        Case vbFormControlMenu   ' = 0
            ' 表單將被使用者關閉
        Case vbFormCode          ' = 1
            ' 表單將被程式碼關閉
        Case vbAppWindows        ' = 2
            ' 目前Windows session結束
        Case vbAppTaskManager    ' = 3
            ' Task Manager關閉應用程式
        Case vbFormMDIForm       ' = 4
            ' 父MDI關閉表單
        Case vbFormOwner         ' = 5
            ' 擁有表單者關閉
    End Select
End Sub

可以設定Cancel參數為True來拒絕移除,請看以下程式碼:

Private Sub Form_QueryUnload(Cancel As Integer, _
    UnloadMode As Integer)
    ' 不讓使用者關閉表單
    Select Case UnloadMode
        Case vbFormControlMenu, vbAppTaskManager
            Cancel = True
    End Select
End Sub

Unload事件
 

若不想取消移除運作, Visual Basic最後會產生Unload事件,並會給最後一次機會來確保表單不被關閉。大部份的情形下,可利用此機會來警示使用者需去儲存資料:

' 模組級的變數
Dim Saved As Boolean

Private Sub Form_Unload(Cancel As Integer)
    If Not Saved Then
        MsgBox "Please save data first!"
        Cancel = True
    End If
End Sub

除非取消要求,否則當退出Unload事件程序後,Visual Basic會消滅所有控制項並移除表單,釋放所有於載入階段分配的Windows資源。召喚表單的方式,也可能也會觸發表單的Terminate事件,即放置清除程式碼的地方、關閉檔案等等。此事件觸發(或不發生)的種種原因將於第九章中介紹。


注意

當觸發Terminate事件,表單物件其實已經不在,所以無需於程式碼中參照到他或其控制項。如果碰巧這麼做了,也不會產生錯誤。而是Visual Basic會建立另一個新的表單物件執行個體(instant),並將默默隱藏在記憶體中而不會消耗大量系統資源。


控制項的集合物件
 

表單顯示一個很特別的屬性就是控制項的集合體,其包含了所有目前被載入的表單之控制項。此集合物件可使表單模組的程式碼更有效率,且是一些程式設計不可或缺的關鍵技巧。舉個例子來看,只要四行程式碼即可清除表單上所有TextBox及ComboBox控制項,很簡單吧:

On Error Resume Next
For i = 0 To Controls.Count - 1
    Controls(i).Text = ""
Next

在此錯誤處理是必要的,因為必需說明在表單上所有的控制項並不支援Text屬性。例如,控制項子集合(Controls collection)亦包含了所有在表單上的功能表項目,而功能表項目並無Text屬性。所以,當重覆子集合時就必需予以解釋這些情況。以下是另一個方式,用通用的Control物件及For Each...Next陳敘式來循環集合中的所有控制項:

Dim ctrl As Control
On Error Resume Next
For Each ctrl In Controls
    ctrl.Text = ""
Next

上述二個程式片斷對表單上任何控制項都可以使用,而且即使將其剪貼到其他表單模組上也能作用。控制項子集合優點就是可建立這類原無法以其他方式來寫的通用副程式。本書在之後也會介紹許多其他控制項子集合的程式設計。

Screen物件
 

Visual Basic表單存在於電腦螢幕上。即使應用程式僅會使用到螢幕的部份區域,仍需要好好來認識這一物件。Visual Basic對看得到的桌面提供了相關的全域的螢幕物件。

表單的Left、Top、Width及Height屬性是以TWIP計算,Twip是螢幕及印表機裝置所使用的計算單位。對印表機而言,一英吋相當於1400個Twip;而在螢幕上,則要依監視器的大小及視訊卡目前的解析度而定。您可從螢幕物件的Width及Height屬性來找出目前螢幕的大小(多少twip),然後再來使用這些值,例如:可以用以下程式碼來移動目前的表單到螢幕右下角:

Move Screen.Width - Width, Screen.Height - Height

雖然螢幕物件無法傳回其他單位的值,仍可以輕而易舉地以螢幕物件的TwipsPerPixelX與TwipsPerPixelY屬性來轉換其值到像素:

' 以像素計量螢幕的寬度及高度
scrWidth = Screen.Width / Screen.TwipsPerPixelX 
scrHeight = Screen.Height / Screen.TwipsPerPixelY

'縮小目前的表單:x軸縮小10個像素,y軸縮小20個像素 
Move Left, Top, Width - 10 * Screen.TwipPerPixelX, _
  Height - 20 * Screen.TwipsPerPixelY

螢幕物件可以用Font及FontCount屬性來列舉所有螢幕可用的字:

' 在下拉式清單中載入所有螢幕字形的名字
Dim i As Integer 
For i = 0 To Screen.FontCount - 1
    lstFonts.AddItem Screen.Fonts(i)
Next

螢幕物件僅有二個屬性是可寫入的:MousePointer及MouseIcon(在之前已介紹過了),可使用以下陳敘式來修改滑鼠指標:

Screen.MousePointer = vbHourglass

指派到此屬性的值僅能影響目前的應用程式。如果移動滑鼠游標到另一應用程式的桌面或視窗上,原來的滑鼠游標就會重設。因此,這不算真正的螢幕屬性,這個概念也適用於Screen屬性、ActiveForm及ActiveControl。ActiveForm是唯讀的屬性,其ActiveControl則會傳回給使用中之表單的輸入駐點(input focus)的控制項一個參照。通常會一起使用這二個屬性:

' 如果目前的表單是frmCustomer,則清除有焦點的控制項 
' On Error是必要的,因為無法確認其是否支援Text屬性,或甚至實際上為作用
中控制項
On Error Resume Next
If Screen.ActiveForm.Name = "frmCustomer" Then
    Screen.ActiveControl.Text = ""
End If

表單即使沒有輸入焦點也可作用。若已跳到另一應用程式中,,Screen物件仍繼續傳回參照到目前在應用程式中的最後一個表單,來當做作用中的表單。請記得,Screen物件無法看到超出目前應用程式以外的東西。而目前的應用程式(current application)是指唯一在系統上執行的程式。以下可說是Win32程式設計上算是一種常規:任何應用程式都不應該知道其他應用程式的狀態或以任何方式來影響其他在系統上執行的應用程式。

印出文字
 

大部份Visual Basic的應用程式並不會直接在表單上顯示文字,而通常是以Label控制項或PictureBox控制項來顯示文字,不過認識如何在表單上顯示文仍有助於了解Visual Basic通常是如何處理文字的。再來,這堜珒ㄓ峊籉韟傢鰝穖磌號洈澈令及屬性也同樣適用PictureBox控制項。

最重要用來顯示文字的繪圖方法就是Print方法。因為其早期的樣子,經過多年依然無任何修改,仍繼續被保留下來,這個指令其實早已是Basic語言的一部份了。因為早期的MS-DOS程式是以Basic來寫,而會依其使用者介面的指令而變得很大,所以Visual Basic能支援這個方法是很重要的。既然現代Visual Basic程式設計並不再仰賴這個指令,不過我仍將介紹其基本的特性:


說明

在物件瀏覽器中是找不到Print方法的,這個指令的本質與其控制項之語法(試想一下有很多陳敘式可用於Print,像逗點、分號及Tab ()函數)來避免Microsoft工程師在此將其包括進來,而是在執行階段,Print方法可直接在程式語言中執行,而將所有連續及複雜的物件導向語言執行的消耗視為整體。


通常會使用Print方法來做表單用戶端區域上的快捷輸出(quick-and-dirty output),例如:可以用以下這個簡單的程式碼來顯示表單目前的大小及位置:

Private Sub Form_Resize()
    Cls                ' 重設印出的位置到 (0,0)
    Print "Left = " & Left & vbTab & "Top = " & Top
    Print "Width = " & Width & vbTab & "Height = " & Height
End Sub

小秘訣

可使用分號來代替「&」運算子,用逗號來代替vbTab常數。 如故意要用標準語法且保持Print方法特性明晰,則可很簡單地循環使用其引數並傳到自訂的方法,或指派到Label中的Caption屬性。這樣一來如將快捷(quick-and-dirty)樣版轉為一般的應用程式後,可以節省時間。


如同之前我曾說明過的,Print方法的產出受目前Font及ForeColor屬性值的影響,預設的情況下,BackColor屬性並不會影響Print指令,因為文字通常當其背景為透明時才會印出。大部份的時候,這種情況並不會發生問題,因為通常會印在乾淨的表單上,而這也會有更好的執行結果,因為僅需傳輸文字的像素而不用傳背景的顏色,不過如欲於清除之前同一位置上的訊息,同時也印出訊息,則設定FontTransparent屬性值為False就可做到。否則,則需結束在上層的訊息來讓二則訊息都看不到。

一般來說,每次Print指令重設目前繪圖位置的x-軸為0並提高y-軸,如此一來,下一個字串就會立即顯示在前一個之下。查詢表單的CurrentX及CurrentY屬性也可知道下一個Print指令顯示其結果的位置。正常狀況下,點(0,0)表示用戶端區域的左上角(即表單邊線內其標題列下方的部份)。x-軸的值是從左向右遞增,而y-軸是從上到下遞增。可指派新的值到這些屬性中來在表單上任何地方印出文字:

' 在表單約正中央的地方顯示訊息
CurrentX = ScaleWidth / 2
CurrentY = ScaleHeight / 2
Print "I'm here!"

不過這個程式碼其實不會讓訊息正好出現在表單的中央,因為字串的其他部份會向右出現所以只有一開始的印出點是在正中央。要精確地將整個訊息置中,則必需從表單的TextHeight及TextWidth方法來定高度及寬度:

msg = "I'm here, in the center of the form."
CurrentX = (ScaleWidth _ TextWidth(msg)) / 2
CurrentY = (ScaleHeight - TextHeight(msg)) / 2
Print msg

TextWidth及TextHeight方法常用以了解訊息是否可於限定範圍內出現,而因為Print方法對較長的字串並無法自動斷行,這個步驟在需印出表單時就顯得特別好用。要彌補此一不足的地方,可以在空白的表單加上以下程式碼,執行程式後則可任意調整表單大小。圖2-10可見表單重調大小的情形。


 

Figure 2-10. 對較長的文字自動斷行
' 格式化Print輸出的副程式 
Private Sub Form_Paint()
    Dim msg As String, pos As Long, spacePos As Long
    msg = "You often use the TextWidth and TextHeight methods" _
        & " to check if a message can fit within a given area. " _
        & vbCrLf & " This is especially necessary when you" _
        & " print to a form, because the Print method doesn't" _
        & " support automatic wrapping for long lines, and you" _
        & " need to solve the problem through code."

    Cls
    Do While pos < Len(msg)
        pos = pos + 1
        If Mid$(msg, pos, 2) = vbCrLf Then
            ' A CR-LF pair, print the string so far and reset variables.
            Print Left$(msg, pos - 1)
            msg = LTrim$(Mid$(msg, pos + 2))
            pos = 0: spacePos = 0
        ElseIf Mid$(msg, pos, 1) = " " Then
            ' A space, remember its position for later.
            spacePos = pos
        End If
        ' 檢查目前為止訊息長度
        If TextWidth(Left$(msg, pos)) > ScaleWidth Then
            ' The message is too long, so let's split it.
            ' If we just parsed a space, split it there.
            If spacePos Then pos = spacePos
            ' Print the message up to the split point.
            Print Left$(msg, pos - 1)
            ' Discard printed characters, and reset variables.
            msg = LTrim$(Mid$(msg, pos))
            pos = 0: spacePos = 0
        End If
    Loop
    ' Print residual characters, if any.
    If Len(msg) Then Print msg
End Sub
Private Sub Form_Resize()
    Refresh
End Sub

上面的程式碼對任何字形都能使用,您可以建立一個可接受任何字串及任一表單參照的通用副程式來當作練習,如此一來就能於之後的應用程式都可再使用到。

另一個待解決的問題是需限定訊息最適合的字型大小,這樣才能在既定的範圍內出現。因為不知道目前使用字型可支援的大小,所以通常得使用For..Next迴圈來找最合適的大小。以下這個簡單的程式範例中,建立了一個數位的時鐘,且可隨意放大或縮小,時間則是透過隱藏的Timer控制項來更新:

Private Sub Form_Resize()
    Dim msg As String, size As Integer
    msg = Time$
    For size = 200 To 8 Step -2
        Font.Size = size
        If TextWidth(msg) <= ScaleWidth And _
            TextHeight(msg) <= ScaleHeight Then
                ' 已找到適合的字體大小了
                Exit For
            End If
    Next
    ' 啟動計時器
    Timer1.Enabled = True
    Timer1.Interval = 1000
End Sub
Private Sub Timer1_Timer()
    ' 以目前的字型設定印出目前的時間
    Cls
    Print Time$
End Sub

Graphic方法
 

Visual Basic提供程式開發多種繪圖方法。可以畫點或線也可以畫更複雜的幾何圖形,例如:長方形、圓形及橢圓形。並可完全掌控線條顏色、寬度及型態,也可以用固定的色彩或花紋來填滿所有圖形。

無疑地,最簡單的繪圖方法就是Cls。其可清除表單的表面,再以BackColor屬性所定義的背景顏色填滿他,再將其移動目前繪圖的位置到座標(0,0)。如指派新值給BackColor屬性,則Visual Basic會清除本身的背景,如此一來則永遠無需於改變表單背景顏色後再引發Cls方法。

畫點
 

Pset是更好用的方法,可用以修改表單上單一像素的顏色。基本的語法,只要指定x和y軸即可,而第三個選擇性參數若不同於ForeColor的值則可指定像素的顏色:

ForeColor = vbRed
PSet (0, 0)                  ' 紅色像素
PSet (10, 0), vbCyan         ' 向右移的藍綠色像素

如同許多其他繪圖的方法,Pset可用Step這個關鍵字支援相對位置,當使用這個關鍵字時,在括弧中的二個值將當作是從目前繪圖點(即目前螢幕上CurrentX及CurrentY所指的地方)開始計算的地方。以Pset方法畫的點則會變成目前的繪圖位置;

' 設定開始位置
CurrentX = 1000: CurrentY = 500
' 延水平線畫10個點
For i = 1 To 10
    PSet Step (8, 0)
Next

使用Pset方法的結果也受其他表單屬性的影響,例如DrawWidth,將此屬性設為大於1(預設值)的值則可畫出較大的點。請注意當所有繪圖單位為twip時,其值是以像素計算。當使用大於1的值,座標將傳給Pset方法會被視為比較大的點(其實該說是一個圓了)的中央。請試試看以下幾行程式碼來做彩色的效果:

For i = 1 To 1000
    ' 亂數設定點的寬
    DrawWidth = Rnd * 10 + 1
    ' 以亂數取座標值及顏色來畫一個點
    PSet (Rnd * ScaleWidth, Rnd * ScaleHeight), _
        RGB(Rnd * 255, Rnd * 255, Rnd * 255)
Next
' 好習慣:還原為預設值.
DrawWidth = 1

Point方法和Pset方法很像,是用來傳回既定像素的RGB顏色的值。要了解這個方法如何使用,首先可建立一個有Label1控制項的表單,畫上一些圖(可用之前的一些程式),然後再加上以下的副程式:

Private Sub Form_MouseMove (Button As Integer, _
    Shift As Integer, X As Single, Y As Single)
    Label1.Caption = "(" & X & "," & Y & ") = " _
        & Hex$(Point(X, Y))
End Sub

執行此程式,並移動滑鼠游標到有顏色的點來顯示座標及顏色的值。

畫線及長方形
 

接下來介紹Line方法。這是個很好用的指令,其語法的多樣性,只需提供開始及結束的座標就可以畫出直線,另外再加上顏色的值就大功告成了(如省略顏色的值,則會使用目前ForeColor的值):

' 以表單二對角畫密斜紋及紅色的"X"
' Line方法會受目前DrawWidth之設定影響
DrawWidth = 5
Line (0, 0) _ (ScaleWidth, ScaleHeight), vbRed
Line (ScaleWidth, 0) _ (0, ScaleHeight), vbRed

如同Pset方法一樣,Line方法也支援Step這個關鍵字來指定相對位置。Step關鍵字可放在任一組座標之前,所以可任意混合絕對位置和相對位置。如省略第一個引數,則會以目前的繪圖位置開始畫線:

' 畫三角形
Line (1000, 2000)- Step (1000, 0)  ' 水平線
Line -Step (0, 1000)               ' 垂直線
Line -(1000, 2000)                 ' 畫上三角形最後一條線

Line方法的結果也受表單的另一屬性影響-DrawStyle,預設值為0 (實線),不過也可畫虛線,請看圖2-11。表2-2列出所有的線條型態:


 

Figure 2-11 DrawStyle屬性不同設定的結果
表2-2 DrawStyle屬性的常數
常數 敘述
vbSolid 0 實線(預設值)
vbDash 1 由 ─ ─ 所形成的線
VbDot 2 由 - 所形成的線
VbDashDot 3 由 ─ 和 - 反覆出現所形成的線
VbDashDotDot 4 由 ─ 和 -- 反覆出現所形成的線
VbInvisible 5 透明
VbInsideSolid 6 內實線

請注意DrawStyle屬性僅會在其值為1-像素時可影響繪圖結果;如為其他的值,則DrawStyle屬性會被忽略且線條都會用實線。新增B為Line方法第四個參數來畫矩形;在這個案例中,這二個點是任何對角線的座標:

' 藍色矩形,寬2000 twip ,高1000 twip
Line (500, 500)- Step (2000, 1000), vbBlue, B

矩形的繪製方式會受到目前DrawWidth及FillStyle屬性所設定的值之影響。

最後,可使用BF引數來畫填滿的矩形,建立無填滿及填滿的矩形之功能可讓您建立一些有趣的效果,例如:可以繪製表單上3-D黃色浮動矩形,請看以下三行程式碼:

' 灰色的矩形可做陰影
Line (500, 500)-Step(2000, 1000), RGB(64, 64, 64), BF
' 白色矩形為表
Line (450, 450)-Step(2000, 1000), vbYellow, BF
' 加上黑色框線
Line (450, 450)-Step(2000, 1000), vbBlack, B

雖然可以用BF引數來繪製填滿的矩形,Visual Basic還是提供了更高階的填滿方式:可以設定FillStyle屬性來做填滿,結果請參看圖2-12。很有趣地,Visual Basic對個別顏色提供個別的顏色屬性供填滿區域使用,而FillColor則允許以一種顏色畫矩形的輪廓,並用其他顏色來畫其內部。以下即利用一特性,因此只需二行就可重寫先前程式碼的例子:

Line (500, 500)-Step(2000, 1000), RGB(64, 64, 64), BF
FillStyle = vbFSSolid   ' 作填滿的矩形
FillColor = vbYellow    ' 上色
Line (450, 450)-Step(2000, 1000), vbBlack, B


 

圖2-12 FillStyle屬性所提供的8種樣式

FillStyle屬性可被指定的值,請參看表2-3。

表2-3 FillStyle屬性的常數
常數 敘述
vbFSSolid 0 實心
vbFSTransparent 1 透明
vbHorizontalLine 2 水平線
vbVerticalLine 3 垂直線
vbUpwardDiagonal 4 左上到右下的斜線
vbDownwardDiagonal 5 左下到右上的斜線
vbCross 6 垂直交叉線
vbDiagonalCross 7 對角交叉線

畫圓、橢圓、及弧
 

最後要來介紹最複雜的Circle方法,其可以畫圓、橢圓、弧、甚至是PIE圖。畫圓是這個方法最簡單的動作了,因為幾乎不需指定圓心及半徑:

' 半徑為1000像素,接近表單的右上角:
Circle (1200, 1200), 1000

Circle方法會受DrawWidth、DrawStyle、FillStyle及FillColor目前的屬性值影響,這表示可用畫較細邊線的圓並用花紋填滿圖形。圓的邊線通常是以目前的ForeColor的值來畫,不過也可以第四個引數來改變它:

' 3像素寬線色邊線的圓,並用黃色填滿 
DrawWidth = 3
FillStyle = vbFSSolid
FillColor = vbYellow
Circle (1200, 1200), 1000, vbGreen

上述的例子任何螢幕及任何解析度下都可畫一個很完美的圓,因為Visual Basic會自動沿x及y軸來計算不同的像素密度。至於畫橢圓,則必需跳過另外二個可自由選擇的引數(稍後會再作解釋),並在指令結束新增一個斜率。斜率就是橢圓的y軸除以x軸。至於更複雜的部份,此方法的第三個引數值應大於這二個的半徑值。如果希望在矩形的範圍內畫一個橢圓,則必需特別注意。此一可重覆使用的副程式可簡化畫橢圓的語法:

Sub Ellipse(X As Single, Y As Single, RadiusX As Single, _
    RadiusY As Single)
    Dim ratio As Single, radius As Single
    ratio = RadiusY / RadiusX
    If ratio < 1 Then
        radius = RadiusX
    Else
        radius = RadiusY
    End If
    Circle (X, Y), radius, , , , ratio
End Sub

Circle方法只要第一個及最後一個的引數(之前跳過的那二個引數)即可畫圓及橢圓的弧形。這些引數的值就是虛線構成的角的開始及結束的地方,可將弧二端點與圖形中點連起來。這個角的弧度是以逆時針的方向計算。舉個例子來說,可以依此來畫一個四分圓:

Const PI = 3.14159265358979
Circle (ScaleWidth / 2, ScaleHeight / 2), 1500, vbBlack, 0, PI / 2

當然如欲畫的是橢圓的弧也可以加上斜率的引數。Circle方法甚至還可以繪製扇形圖,也就是弧形以半徑與圓形或橢圓的圓心相連的圖形。畫這個圖形需指定第一及最後一個引數值為負。圖2-13顯示之「 有缺口的」扇形圖是以如下程式碼所做的:

' 畫有缺口的扇形圖
' 請注意不能指定Null負的值
'      but you can express it as -(PI * 2).
Const PI = 3.14159265358979
FillStyle = vbFSSolid
FillColor = vbBlue
Circle (ScaleWidth / 2 + 200, ScaleHeight / 2 - 200), _
    1500, vbBlack, -(PI * 2), -(PI / 2)
FillColor = vbCyan
Circle (ScaleWidth / 2, ScaleHeight / 2), _
  1500, vbBlack, -(PI / 2), -(PI * 2)


 

圖2-13 畫扇形圖

DrawMode屬性
 

因為到目前為止所介紹的屬性及方法並不足夠,所以當寫圖形密集的應用程式仍需來看看表單的另一個屬性─DrawMode,這個屬性用以指定圖形與表單上像素的互動關係。雖然表單上所有線條、圓及弧所有的圖之預設值都可以很簡單被更改,不過應該不會常這麼做。事實上DrawMode屬性於混合使用這些原就在表單上的圖形時就可增添許多變化。(表2-4列出各種效果的值)

表2-4 DrawMode屬性的常數
常數 敘述 位元運算子(S=螢幕,P=畫筆)
vbBlackness 1 螢幕顏色值都設成0(畫筆顏色未使用) S= 0
VbNotMergePen 2 OR運算子用於畫筆顏色及螢幕顏色,結果會相反(因為用了NOT運算子) S= Not(S Or P)
vbMaskNotPen 3 與畫筆顏色相反(用了NOT運算子),因用AND運算子所以結果是螢幕顏色 S= S And Not P
vbNotCopyPen 4 與畫筆顏色相反。 S= Not P
vbMaskPenNot 5 與螢幕顏色相反(因使用NOT運算子),因AND運算子,使用畫筆顏色 S= Not S And P
vbInvert 6 螢幕顏色設相反(畫筆顏色不使用) S= Not S
vbXorPen 7 XOR運算子用於螢幕及畫筆顏色 S= S Xor P
vbNotMaskPen 8 AND運算子用於螢幕及畫筆顏色,而結果是相反的(因使用NOT運算子) S= Not (S And P)
vbMaskPen 9 AND運算子用於螢幕及畫筆顏色 S= S And P
vbNotXorPen 10 XOR運算子用於螢幕及畫筆顏色,而結果是相反的(因使用NOT運算子) S= Not (S Xor P)
vbNop 11 無任何運算子(事實上是關閉繪圖) S=S
vbMergeNotPen 12 畫筆顏色相反(因使用NOT運算子),使用OR運算子於結果與螢幕顏色 S= S Or Not P
vbCopyPen 13 ForeColor屬性值所指定的值來畫圖 S= P
vbMergePenNot 14 螢幕顏色相反(因使用NOT運算子)之後NOT應算子再用於畫筆顏色 S = Not S Or P
vbMergePen 15 OR運算子用於螢幕顏色及畫筆顏色 S= S Or P
vbWhiteness 16 螢幕顏色均為1(畫筆顏色不使用) S=-1

要了解每一種圖究竟是怎麼做的,需先記得顏色是最後經由位元出現的,所以除了0與1的位元型運算子並無可結合畫筆顏色及表單原本顏色之運算子。如果以此觀點再來看看表2-4,就會比較清楚最右邊那一行的意思了,也可以用來設定繪圖指令的預期結果。例如,如果在藍色的背景顏色(十六進位的值應為&HFFFF00)上要畫一個黃色的點(十六進位的值應為&HFFFF),可看見以下的結果:

vbCopyPen 黃色(忽略螢幕顏色)
vbXorPen 紫色 (&HFF00FF)
vbMergePen 白色 (&HFFFFFF)
vbMaskPen 綠色(&H00FF00)
vbNotMaskPen 紫色 (&HFF00FF)

不同的畫圖模式也可以產生相同的結果,特別是在使用實心顏色的時候(請看上面及vbNotMaskPen的範例),如果問到說:「是否對所有的模式都要費盡心思?」答案是「是的」及「不」。如果只是寫簡單無圖形產生的應用程式通常是無需擔心他們,而如果是作一些高階的圖形處理則至少需知道Visual Basic提供那些方式。

DrawMode屬性最好用的地方就是能消去邊緣,用滑鼠再畫或調整新圖形而不影響之下的其他圖。每當在Microsoft小畫家或任何Windows繪圖程式中畫圖,即使不甚了解也都能使用橡皮擦的功能。如果問到:當以滑鼠拖曳矩形的一角時究竟發生了那些事?Microsoft小畫家是否擦去這個矩形然後於另一個位置再畫一個矩形?在Visual Basic應用程式中要如何使用相同的特性?這些答案都很簡單,而且是以DrawMode屬性為基礎。

訣竅在於對螢幕及同樣的畫筆的值用XOR運算子二次,第二次的XOR會命令螢幕回覆原本的顏色(如非常熟悉使用邏輯運算子,這應該不會太難,如果不熟,則請多作幾次試驗)。因此,只要設定DrawMode值為7-vbXorPen;然後第一次可畫出矩形(或線條、圓、弧等等),第二次則可以將其消去。當使用者最後釋放滑鼠游標時,設定DrawMode屬性為13-vbCopyPen在表單上畫最後一個矩形。下面的程式可用來試驗一下橡皮擦的功能:可以拖曳滑鼠左鍵畫一個空的矩形(以亂數產生的線條寬度及色彩),再拖曳滑鼠右鍵來填滿色彩。

' Form-level變數
Dim X1 As Single, X2 As Single
Dim Y1 As Single, Y2 As Single
' 如果我們正拖曳一個長方形值會是True
Dim dragging As Boolean

Private Sub Form_Load()
    ' 在一個黑的背景中繪圖的效果較好
    BackColor = vbBlack
End Sub
Private Sub Form_MouseDown(Button As Integer, Shift As Integer, _
    X As Single, Y As Single)
    If Button And 3 Then
        dragging = True
        ' Remember starting coordinates.
        X1 = X: Y1 = Y: X2 = X: Y2 = Y
        ' 用亂數產生顏色及寬度
        ForeColor = RGB(Rnd * 255, Rnd * 255, Rnd * 255)
        DrawWidth = Rnd * 3 + 1
        ' 畫長方形用Xor模式
        DrawMode = vbXorPen
        Line (X1, Y1)-(X2, Y2), , B
        If Button = 2 Then
            ' 填滿色的長方形
            FillStyle = vbFSSolid
            FillColor = ForeColor
        End If
    End If
End Sub
Private Sub Form_MouseMove(Button As Integer, Shift As Integer, _
    X As Single, Y As Single)
    If dragging Then
        ' 刪除舊的長方形(用Xor模式重複同一個命令)
        Line (X1, Y1)-(X2, Y2), , B
        ' 畫出新的座標
        X2 = X: Y2 = Y
        Line (X1, Y1)-(X2, Y2), , B
    End If
End Sub
Private Sub Form_MouseUp(Button As Integer, Shift As Integer, _
    X As Single, Y As Single)
    If dragging Then
        dragging = False
        ' 決定完成畫出長方形
        DrawMode = vbCopyPen
        Line (X1, Y1)-(X, Y), , B
        FillStyle = vbFSTransparent
    End If
End Sub

ScaleMode屬性
 

雖然twip是Visual Basic在螢幕上放置物件及調整大小的預設測量單位,但不是唯一的單位。事實上,表單及一些其他可當收納器的控制項(例:PictureBox控制項),都有ScaleMode屬性可供於設計或執行階段中設定,可設定的值詳列於表2-5。

表2-5 ScaleMode屬性的常數
常數 敘述
VbUser 0 使用者自訂
VbTwips 1 Twip,為五百六十七分之一公分
VbPoints 2 點(每一英吋72點)
VbPixels 3 像素
VbCharacters 4 字元 (水平=120單位twip,垂直=240 twips
VbInches 5 英吋
VbMillimeters 6 公厘
VbCentimeters 7 公分
VbHimetric 8 Himetric(1000單位=1公分)

表單物件有二種方法可輕易轉換任何不同的測量單位,ScaleX方法可供水平測量而ScaleY方法可供垂直測量,其語法是相同的:傳遞一個需被轉換的值(來源值),用表2-5中任一常數來指定來源值的使用單位(fromscale參數),再以另一個常數(toscale參數)指定轉換後的單位。如省略了fromscale參數,則會使用vbHimetric,而如省略了toscale參數,則會以目前ScaleMode屬性值為其值。

' 沿著X座標一個pixel是多少twip?
Print ScaleX(1, vbPixels, vbTwips)

' 畫出一個50x80 pixel長方形
' 在表單的左上角不管指定的ScaleMode
Line (0, 0)-(ScaleX(50, vbPixels), ScaleY(80, vbPixels)), _
vbBlack, B

注意

ScaleX及ScaleY方法提供相同的功用,如同螢幕物件中TwipsPerPixelX及TwipsPerPixelY屬性一樣,是為其他測量單位工作,而無需去寫轉換的程式碼。唯一需繼續使用Screen的屬性是當在BAS模組中寫程式碼(如:一般的函數或程序)又沒有任何表單可參照時。


ScaleMode屬性與其他四個屬性非常有相關性。ScaleLeft及ScaleTop相當於表單的用戶區域左上方像素的(x,y)值,通常設定為0。ScaleWidth及ScaleHeight則相較於用戶區域右下角的像素值。如設定不同的ScaleMode,則這二個屬性立刻會反應出新的設定值。例如:假設設定ScaleMode值為3-像素,那麼去查詢ScaleWidth及ScaleHeight值就可知道用戶區域以像素為單位的大小了。請記得,即使目前的ScaleMode為1-Twips,ScaleWidth及ScaleHeight屬性傳回的值也會不同於表單的Width與Height屬性,因為後者是視窗的邊框及標題列的座標,是在用戶區的外部,如果了解了這些數量間的關係,就可以導出一些很有用的表單資訊。

'在表單模組內部執行以下程式碼
' 確認ScaleWidth及and ScaleHeight傳回twip
' (若使用預設值則下一行無用) 
ScaleMode = vbTwips
' 計算邊框的寬度是多少pixels
BorderWidth = (Width _ ScaleWidth) / Screen.TwipsPerPixelX / 2

' 計算標題列的高度是多少pixels
' (Assumes that the form has no menu bar)
CaptionHeight = (Height _ ScaleHeight) / _
    Screen.TwipsPerPixelY _ BorderWidth * 2

ScaleMode屬性值的指派可以是任意值,不過最常使用的是 Twip  像素  像素 在取回子控制項的像素座標時就很有用處,可用來執行一些含於Windows API呼叫函式的高階繪圖指令。

 使用者自訂 設定非常特別,通常是不用指派到ScaleMode屬性,而是以設定ScaleLeft、ScaleTop、ScaleWidth及ScaleHeight屬性來定義自訂的座標系統。當使用他時,ScaleMode屬性會自動由Visual Basic設為0-使用者自訂。如需要建立自訂座標來簡化應用程式的程式碼,並讓Visual Basic執行所有自訂的轉換。以下的程式會在表單上使用自訂座標繪出一個函數(詳見圖2-14的結果)


 

圖2-14 使用自訂座標系統畫出3-D多項式函數。
'  X-Y平面部分被劃出
Const XMIN = -5, XMAX = 5, YMIN = -100, YMAX = 100
Const XSTEP = 0.01

Private Sub Form_Resize()
	' 設定一個自訂的座標給可見範圍符合常數
    ScaleLeft = XMIN
    ScaleTop = YMAX
    ScaleWidth = XMAX - XMIN
    ScaleHeight = -(YMAX - YMIN)
    ' Force a Paint event.
    Refresh
End Sub
Private Sub Form_Paint()
    Dim x As Single, y As Single
    ' 開始空白的畫布.
    Cls
    ForeColor = vbBlack
    ' 開始顯示說明
    CurrentX = ScaleLeft
    CurrentY = ScaleTop
    Print "f(x) = x ^ 3 - 2 * x ^ 2 + 10 * x + 5"
    CurrentX = ScaleLeft
    Print "X-interval: [" & XMIN & "," & XMAX & "]"
    CurrentX = ScaleLeft
    Print "Y-range: [" & YMIN & "," & YMAX & "]"
    ' 畫x-及y-軸
    Line (XMIN, 0)-(XMAX, 0)
    Line (0, YMIN)-(0, YMAX)
    ' 畫出數學函數
    ForeColor = vbRed
    For x = XMIN To XMAX Step XSTEP
        y = x ^ 3 - 2 * x ^ 2 + 10 * x + 5
        PSet (x, y)
    Next
End Sub

請多留心上面程式碼中注意的地方:

  • ScaleHeight屬性雖是負的,但卻是完全合法的值;事實上,當出現y-軸時,通常都需將此屬性值設為負數,因為預設的座標會從其頂端到往下增加其值,而通常我們所需要的正好相反。
     
  • 您可隨意調整表單的大小,且其都會顯示在x-y平面上相同的部份,如有必要也可予以變更。
     
  • 一旦已在Resize事件程序中設好了自訂系統,就可以說有了自己的x-y座標系統,因此可以簡化繪圖的程式碼。
     
  • Cls繪圖方法會重設CurrentX及CurrentY座標為 (0,0),即螢幕的正中央,因此,必需手動設定CurrentX與CurrentY屬性才能在左上角印出。
     
  • Print方法通常會重設CurrentX屬性為0,所以必需每次印出一行文字就設定一次。
     

使用調色盤
 

首先來談些理論的東西。大部份視訊卡都能顯示1600萬色(之所以說是理論,是因為我們人類其實無法分辨大部份相近的顏色),而相對地只有少數全彩(true-color)的視訊卡是可在螢幕上既定的距離內顯示許多色彩的,特別是在較高解析度的螢幕上(為了要簡化說明,所以我略過敘述high-color卡可同時顯示65,536色)。其他的,Windows則必需依賴調色盤。

調色盤是所有理論上視訊卡支援的色彩其中的256色之集合。如果視訊卡在調色盤模式中可運作,他則需給螢幕上每一像素提供一個位元(而不是1600萬色所需的3個位元),因此節省了很大的記憶體空間並增進大部份圖型的運作速度。256色中每一個值都指向一個視訊卡可找到其RGB值的表格。每一像素也有定義好的顏色,不過在螢幕上同時出現的決不會超過256色。

Windows本身雖保留了16個顏色,但仍有些留給應用程式使用,當前景應用程式要顯示圖像時,即可使用調色盤來找到相符於圖像的顏色。而若圖像所含的色彩大於可用的顏色時,應用程式就得去找折衷的方法(例如:用相同的調色盤登錄二個相似的色彩)。在實務應用上,與以下的問題相較這其實不是大問題:當應用程式必需在同時顯示多個圖像時,必然會在各組不同的色彩中調和,而那一個優先呢?那麼其他圖像又會怎樣呢?

一直到Visual Basic 5,Visual Basic程式開發者才算真正有解決方案,Visual Basic 4甚至更早的版本都只給在z-order中的第一個圖像有優先權,也就是指有駐點的表單或控制項,螢幕上其他所有的圖像都用調色盤來顯示,而實際上卻常常與其色彩組不相符,結果不盡如人意。從Visual Basic 5開始,終於有了另一種選擇。

此一功能的關鍵在於表單的PaletteMode屬性,其於設計或執行階段中都可被指派三種不同的值:0-系統調色盤、1-最上層控制項的調色盤、及2-自訂調色盤。系統調色盤是固定的,包涵完整「標準的」色彩 ,可提供許多圖像合宜的立體效果,而且,應可允許以不同的調色盤的多個圖像和協地出現在表單上。(此為Visual Basic5及6表單的預設模式)。最上層控制項的調色盤模式是此語言之前版本唯一可設定的模式。有駐點之Form或PictureBox會影響視訊卡所使用的調色盤,其將會以可能最好的方式呈現,而其他控制項則不一定可發揮此特點。第三種設定即自訂調色盤模式可建立自訂的調色盤,只要在設計或執行階段用LoadPicture函數指派一個點陣圖給Palette屬性就可以了。在這個案例中,與所提供的圖型關聯的調色盤就變成此表單所使用的調色盤了。

以上介紹了表單物件所包函的屬性、方法及事件。知道了這些將有助於了解下一章將討論的所有Visual Basic固有的控制項之特性。