用戶界面(User Interface,UI)是一個軟件技術的專用術語。通俗地說,用戶界面就是程序的使用者可以看到的程序外觀,對于運行于個人電腦上的程序而言,用戶界面指的就是程序的窗體(Form)。圖2-1是著名的MP3播放軟件WINAMP的用戶界面。
在.NET下,運行在個人電腦上的應用程序由“Windows Form”類型的項目生成。Windows Form類似于以前使用VB 6、Delphi、VC 6開發(fā)出來的窗體,其特點是擁有豐富的用戶交互功能,比如可以最大化、最小化,可以通過拖動標題欄而拖動整個窗體,單擊窗體左上角圖標會彈出系統(tǒng)菜單,按Alt+F4組合鍵可以關閉當前窗體……Windows Form通過調用.NET Framework和Windows操作系統(tǒng)的API(Application Programming Interface,應用程序編程接口),還可以方便地使用操作系統(tǒng)所提供的任意功能。
在.NET下,還有另外一種用戶界面,稱之為“Web Form”。一看見“Web”,讀者一定就會猜出這是運行在因特網上的窗體。其實Web Form本身就是Web網頁,它不能像Windows Form一樣獨立存在,而只能運行在瀏覽器(比如微軟的Internet Explorer)中。
為什么要把 “網頁”叫做“Web Form”呢?Web Form與傳統(tǒng)的使用HTML設計的網頁有何不同?
經常上網的人都知道網頁頁面和標準的Windows窗體還是有許多差別的。Web頁面由于必須顯示在瀏覽器中,所以其功能受到瀏覽器的限制,無法做到像標準的Windows窗體那樣功能強大而且使用方便。例如,按照目前的技術水平,要開發(fā)一個全部運行在瀏覽器中的圖像處理軟件(如Photoshop)是很困難的,不管是程序運行速度還是用戶操作友好性,都受到HTML及瀏覽器的天然限制。Windows Form主要運行在單機上,而Web Form主要運行在因特網環(huán)境中,這兩種差異極大的環(huán)境決定了兩者的不同。
但微軟公司一直在努力彌合開發(fā)Web應用程序和桌面應用程序之間的差別,Web Form就是把Windows Form的事件驅動原理應用于開發(fā)因特網應用程序的一種嘗試。在.NET下,開發(fā)因特網應用程序(即Web網站)的技術稱為ASP.NET。在ASP.NET中,把Web頁面看成是一個窗體,可以像開發(fā)Windows Form應用程序一樣,直接用控件在網頁上“畫”出網頁布局,并可以針對各種控件的事件進行編碼。正是由于在ASP.NET中設計Web網頁與設計Windows Form幾乎一樣,所以,ASP.NET中把Web網頁稱為“Web Form”。
提示
初學.NET的人不適合一上來就直接學習ASP.NET。由于Web Form是在Windows Form技術的基礎上并結合了現(xiàn)有的Web網站開發(fā)技術出現(xiàn)的更為復雜的新技術,所以,學習掌握Windows Form的基本原理,掌握一種.NET語言(推薦C#和VB.NET兩者之一)編程技能是學習ASP.NET技術的基礎。初學者切不可一開始就抱著本《C# Web Form編程》之類的書狂啃不休,不掌握必要的基礎,您會發(fā)現(xiàn)學到一定的時候就學不下去了。
學習ASP.NET的另一個基礎是掌握現(xiàn)有的Web開發(fā)技術,主要是HTML和JavaScript,以及Web應用程序的基本原理。
本書所有內容只涉及Windows Form技術,讀者如果對開發(fā)因特網應用程序感興趣,可以在學習本書之后,再去學習ASP.NET。
本章介紹Windows Form的基本原理,并學習使用VS .NET這一功能強大的集成開發(fā)環(huán)境高效地設計用戶界面。
2.1.1 窗體的使用
Windows Form應用程序的核心就是窗體(Form),先看一個示例。
1.類和命名空間
打開VS .NET,從“文件”菜單中選“新建/項目…”,如圖2-2所示。
圖2-2 新建一個Windows應用程序
注意選中“Visual Basic項目”下的“Windows應用程序”模板,在名稱處輸入“UseForm”。單擊【確定】按鈕,VS .NET將會自動創(chuàng)建一個窗體,顯示在屏幕中央。請切換到代碼視圖(參見第1章中的相關內容),觀察VS .NET自動生成的代碼框架。
Public Class Form1
Inherits System.Windows.Forms.Form
'此處略去Windows窗體生成器生成的代碼
End Class
從上述代碼中可以看到窗體其實是一個類,名字叫做“Form1”。在.NET中編程用到的所有東西都放在特定的類中,類是所有面向對象程序中最基本的具有獨立性的構成元素(在以C語言為代表的計算機語言開發(fā)出來的結構化程序中,最基本的構成元素是函數(shù))。2.2節(jié)將介紹面向對象編程的更多知識。
注意上述代碼中的第2句,“Inherits”是VB.NET中的一個關鍵字(Key Word,即編程語言中表達特殊意義的單詞),它表明類Form1從另一個類System.Windows.Forms.Form繼承而來,這個類由.NET Framework提供,它其中有許多已寫好的程序代碼,這些代碼能完成前面提到的標準Windows窗體應該具有的功能(比如讓窗體最大化和最小化)。通過繼承這些現(xiàn)有代碼,軟件工程師就不再需要自己實現(xiàn)這些功能,而只需使用現(xiàn)成的類,這就是面向對象編程所帶來的高開發(fā)效率。把許許多多完成各種功能的類集中放在一起,就構成了類庫(Class Library)。.NET就提供了一個龐大的類庫,參見圖2-3。
.NET Framework基本類庫
讀者可能會覺得奇怪,類名字“Form1”只有一個單詞,為什么它繼承的類有那么長的以英文句點分隔的字符串——“System.Windows.Forms.Form”?這就涉及.NET類庫中類的組織方式問題。
在.NET Framework中,類的存放不是雜亂無章的,而是分門別類的,比如開發(fā)Web網頁的類就放在System.Web這個大類別下。相應地,用于開發(fā)Windows Form的類就放在System. Windows.Forms這個大類別下。
諸如System.Windows.Forms這樣的大類別稱為名字空間(Name Space),可以把名字空間看成是功能彼此類似或相關的類的集合。
名字空間是可以嵌套的,大的可以裝下幾個小的,比如System這個頂級的名字空間下就可以有Web、Data、XML等幾個小的名字空間,而Runtime這個名字空間下還有Remoting、Serialization等好幾個子名字空間。嵌套的名字空間形成一個復雜的分層結構,最底層是具體的類。
當需要明確地表達一個類時,應采用以下格式:
最大的名字空間.子名字空間.孫名字空間.….類
最右邊的是類名,所以,“System.Windows.Forms.Form”這個字符串表明Form是一個類,它的最頂級名字空間是System,次級名字空間為Windows,三級名字空間為Forms。通過使用這樣的完整字符串,就可以在龐大的類庫中快速定位一個類。
為了不在編程時輸入一個長長的字符串,VB.NET引用了一種“提前”導入名字空間的Imports語句。
在代碼窗口的最上方輸入
Imports System.Windows.Forms
然后,就可以把代碼中的
Inherits System.Windows.Forms.Form
改為
Inherits Form
則程序仍然會正確運行。
這種在代碼開頭使用Imports語句導入特定名字空間的方法在編寫代碼時是非常常見的。在后面的章節(jié)中,許多實例都采用了提前導入名字空間的方法。讀者在手動敲入代碼時如果發(fā)現(xiàn)VS .NET提示“未定義類型XXX”(參見圖2-4),則往往是由于沒有導入名字空間的緣故。
未定義名字空間引發(fā)的錯誤
解決方法是在類代碼之外使用Imports語句導入名字空間,如圖2-5所示。
可以在隨機文檔MSDN中查找.NET Framework中所有類各自所屬的名字空間。
圖2-5 導入名字空間解決“類型未定義”錯誤
試一試
在MSDN中查找StreamReader這個類屬于哪個名字空間。
2.類和由類創(chuàng)建的對象
前面說過,F(xiàn)orm1其實是一個類,但我們看到的窗體并不是類本身,而是由Form1類生成的對象。
類和對象兩者是一種什么關系?為理解這一點,我們進行一個練習。
把一個按鈕拖動到窗體(窗體名Form1)上,在其Click事件代碼中書寫以下代碼:
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Dim frm As New Form1
frm.ShowDialog()
End Sub
運行之后,多次單擊按鈕,看看發(fā)生了什么(參見圖2-6)!
可以看到,每次單擊按鈕都會在屏幕上多出一個窗體,并且這些窗體都“長得”一模一樣。這說明了什么?
類Form1定義了窗體的外觀,但真正看到的是以Form1作為模板創(chuàng)建的Form1對象。一個類可以創(chuàng)建多個以它為模板的對象,類和對象之間是一對多的關系。為了便于理解,可以把類看成是一個印,它可以在紙上蓋出多個章,蓋出的章就是“對象”,不管蓋多少次,印本身是不會變的,蓋出來的章樣子也是類似的。
理解類和由類創(chuàng)建的對象這兩個概念非常重要,2.2節(jié)將學習類的更多知識。
上述代碼中使用New這個關鍵字創(chuàng)建類Form1的對象frm,再調用frm對象的ShowDialog()方法讓它顯示出來。
一個對象可以擁有屬性和方法,屬性往往代表了對象的性質,而方法往往代表了對象的功能。Form1對象的ShowDialog()方法完成的功能是把窗體顯示出來,并等待用戶關閉。
一個對象擁有多少種屬性和方法是由類來決定的。讀者可能會奇怪,在類Form1的代碼中,沒有看見有ShowDialog()這樣單詞啊?它從哪兒冒出來的?
別忘了,F(xiàn)orm1繼承自Form類,而ShowDialog()這個方法的功能由Form類所實現(xiàn),具體的代碼放在.NET Framework中,在VS .NET中不能直接看到。
3.Form類的常用屬性
熟悉Windows操作的人都知道,一個標準的窗口有很多特性,而System.Windows.Forms.Form作為所有可見窗體的父類,實現(xiàn)了標準的Windows窗體的所有功能,這主要是通過設置Form類的特定屬性值實現(xiàn)的,許多屬性可以用屬性窗口進行設置。以下是使用代碼來設置這些屬性的方法(假設所有的代碼都位于同一窗體,用關鍵字Me代表窗體):
(1)修改窗體標題
Me.Text="新窗體標題"
試一試
在Form_Load()過程中加上以上代碼,然后編譯運行。
(2)隱藏窗體
Me.Visible=False
將其改為True則會再顯示窗體。
(3)設置窗體背景顏色
Me.BackColor = Color.Yellow
以上代碼把窗體背景改為黃色。
請注意在.NET中顏色用System.Drawing名字空間中的Color類型表示,其完整的聲明為“System.Drawing.Color”。
.NET提供了許多種預先定義的顏色,如上面的代碼所示,可以使用以下的方式指定一種預定義顏色:
Color.顏色名稱
如果預定義顏色不能滿足需要,則可以通過指定RGB值來定義一種特殊的顏色。RGB分別代表Red(紅色)、Green(綠色)、Blue(藍色)三個分量,每個分量的取值范圍是0~255。從色彩學可以知道,紅、綠、藍是三原色,這三種顏色按不同的比例混合,可以生成人肉眼能看到的幾乎所有顏色。在計算機上,每個顏色有256種取值,所以,一共能表示“256′256′256”種顏色。
對于純色有以下公式:
(R,G,B)=(255,0,0) 純紅色
(R,G,B)=(0,255,0) 純綠色
(R,G,B)=(0,0,255) 純藍色
(R,G,B)=(255,255,255) 純白色
(R,G,B)=(0,0,0) 純黑色
如果使用RGB的方式來選定顏色,可以使用Color類型的FromARGB()方法:
Me.BackColor = Color.FromArgb(255, 0, 0)
以上代碼把窗體背景顏色改為紅色。
試一試
在Form_Load()過程中加上以上代碼,然后編譯運行,改改RGB的值,看看效果。
(4)讓窗體不能動態(tài)改變大小
Me.FormBorderStyle = FormBorderStyle.FixedSingle
試一試
(1)在Form_Load()過程中加上以上代碼,然后編譯運行;
(2)將FormBorderStyle設為其他值,再運行看看結果;
(3)在代碼窗口中選中FormBorderStyle,然后按F1鍵,調出MSDN,了解MSDN對這一屬性的描述;
(4)讓窗體在啟動時在屏幕上自動居中。
只需設置窗體的StartPosition屬性即可。
Me.StartPosition = FormStartPosition.CenterScreen
StartPosition屬性還有其他的取值,請讀者自行查閱MSDN。
Form類擁有很多屬性,本節(jié)僅介紹了幾個最常用的屬性,請您通過MSDN了解其他屬性。
4.Form類的常用方法
Form類提供了許多方法,其中最常用的有以下幾個:
(1)關閉與隱藏窗體
Me.Close() '關閉窗體
Me.Hide() '隱藏窗體
技術內幕
關閉一個窗體是指不再使用這個窗體,.NET虛擬機把這個窗體對象標記為不再使用,所占用的內存可以被回收。一個窗體被關閉之后,就不能再使用它了。
隱藏一個窗體是指這個窗體暫時從屏幕上消失,在需要的時候可以通過將其Visible屬性設置為True而重新顯示在屏幕上。
窗體隱藏的特點是窗體對象仍然存在于內存中,當再次顯示時,其中所有控件的屬性值仍然保存隱藏之前的原始狀態(tài)。創(chuàng)建一個窗體需要花費許多時間,而重新顯示一個窗體則速度非???。所以,為了加快程序的響應時間,可以預先創(chuàng)建一個窗體對象但不顯示(Visible=False),需要時設置Visible=True顯示它,這會給用戶此程序運行速度很快的感覺。
技術探索
使用Timer控件進行計數(shù),讓窗體顯示1分鐘,再隱藏1分鐘。
(2)顯示窗體
可以使用窗體對象的Show()、ShowDialog()方法顯示窗體,但這兩者是有區(qū)別的,請做以下實驗。
從“項目”菜單中選擇“添加Windows窗體”命令(參見圖2-7)。
圖2-7 新建一個窗體
單擊【打開】按鈕,將會在項目中添加一個新窗體Form2,可以在項目管理器中看到這個新窗體。
打開第一個窗體Form1,確定窗體上有一個按鈕Button1,雙擊Button1切換到代碼視圖,給其Click事件編碼:
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Dim frm As New Form2
frm.ShowDialog()
End Sub
編譯運行之后,單擊按鈕Button1,F(xiàn)orm2窗體將會顯示在屏幕上。
注意,這時可以試著再單擊Form1,您會發(fā)現(xiàn)Form1的標題欄始終是灰的,無法被激活。只有先關閉Form2,才能再次單擊Form1上的Button1。兩個窗體之間如果存在這種關系,則稱Form2 按“模式”方式顯示。
現(xiàn)在把代碼中的frm.ShowDialog()改為frm.Show(),再運行看看,現(xiàn)在,可以隨意地用鼠標單擊激活Form1和Form2了。這種情況稱Form2按“非模式”方式顯示。
何時用模式,何時用非模式,取決于程序的具體要求。一個實例是Microsoft Word的“插入圖片”窗體就必須是模式的,而“查找”窗體就必須是非模式的,讀者可以打開Word試一試。
讀者可能會奇怪,在解決方案資源管理器中Form1、Form2看上去都是地位平等的,為什么程序運行時會自動顯示Form1,而不會自動顯示Form2,要顯示Form2居然還要編碼?
這涉及到“程序主窗體”的概念。使用VB的早期版本創(chuàng)建一個新項目時,默認情況下第一個創(chuàng)建的窗體稱為主窗體,后創(chuàng)建的窗體是輔助窗體,關閉主窗體會導致程序的結束,關閉輔助窗體則不會結束整個程序。VB.NET延續(xù)了這個概念。當程序運行時,會首先顯示主窗體(當然可以通過給Sub Main()編碼手動改變VB.NET的默認行為),所以,主窗體往往又稱為“啟動窗體”,但這種稱呼的含義是模糊的,定義并不嚴格。
可以定義項目中的任何一個窗體為啟動窗體,方法是在“解決方案資源管理器”中的項目節(jié)點上單擊鼠標右鍵,選擇“屬性”(參見圖2-8)。
設定啟動對象
如圖2-8所示,從“啟動對象”下拉列表中選擇一個窗體對象,則此窗體將會在程序運行時自動顯示,關閉此窗體會導致程序關閉,而項目中的其他窗體對象必須先被創(chuàng)建(New)之后,再調用其Show()或ShowDialog()方法顯示。
技術內幕
主窗體的真實含義
由于VB.NET在后面悄悄地進行了一些工作,所以主窗體的真實含義不好理解。C#就明確得多了。
打開VS.NET,創(chuàng)建一個C#的Windows應用程序,切換到代碼視圖,可以看到以下代碼:
/// <summary>
/// 應用程序的主入口點
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
在Application.Run()中指定的窗體就是主窗體。當主窗體關閉以后,Application.Run()函數(shù)執(zhí)行完畢,Main()函數(shù)結束,整個程序也就終止了。
5.Form類的事件
事件(Event)是面向對象編程中的一個很重要的概念。可以這樣通俗地理解,事件用于表明在某個對象內部某些事情發(fā)生了,其他對象可以決定是否對這些事情進行響應。
舉一個簡單的例子,當鼠標在一個窗體上單擊時,會激發(fā)窗體類的Click事件,如果程序員為這個Click事件寫了一些代碼,那么,程序運行時只要鼠標單擊了這個窗體,Click事件就會發(fā)生,程序員寫的代碼就會被運行。
下面看兩個實例。
實例1:跟蹤鼠標的移動
新建一個“Windows應用程序”項目,取名為“MouseLocation”,往窗體上拖入一個Label控件,設置其Text屬性為“(0,0)”,如圖2-9所示。
雙擊圖2-9所示主窗體內部,將會切換到代碼視圖,光標默認位于Form_Load()過程中,如圖2-10所示。
主窗體圖2-10 給特定的事件編碼
在代碼視圖左上角的組合框中選中“(Form1 Events)”,在右上角的組合框中選擇“MouseMove”(如圖2-10所示),則VS .NET會自動生成處理MouseMove事件的框架代碼,并且光標會自動定位于Sub Form1_MouseMove()過程內部:
Private Sub Form1_MouseMove(ByVal sender As Object, ByVal e As _
System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseMove
End Sub
在Sub Form1_MouseMove()過程內部輸入以下代碼:
Label1.Text="(" & CStr(e.X) & "," & CStr(e.Y) & ")"
現(xiàn)在編譯并運行程序(參見圖2-11)。
可以看到隨著鼠標的移動,標簽的數(shù)字也跟著變動,能及時地顯示當前鼠標的位置坐標。
理解這個示例的關鍵有兩點。
(1)當鼠標在窗體上移動時,MouseMove事件會被激發(fā),會調用我們給MouseMove事件寫的代碼:
Label1.Text = "(" & CStr(e.X) & "," & CStr(e.Y) & ")"
MouseMove事件激發(fā)多少次,這段代碼就會執(zhí)行多少次。
(2)當MouseMove事件激發(fā)時,當前鼠標的位置坐標X和Y被封裝到了MouseMove事件的參數(shù)e中,可以用e.X和e.Y取出這個值。
為了在標簽上顯示(0,0)這樣格式的信息,需要把整數(shù)e.X和e.Y用VB.NET提供的內部函數(shù)CStr()轉為字符串變量,然后再連在一起,這就是我們所寫的代碼的作用。
提示
有關函數(shù)的概念將在2.2節(jié)介紹。
技術內幕
窗體坐標和屏幕坐標
在計算機中使用直角坐標系來確定一個點(參見圖2-12)。
與數(shù)學中使用的直角坐標系不同,計算機中使用的直角坐標系Y軸正向是向下的。
窗體坐標是以窗體為依據的,窗體內部區(qū)域左上角為原點。而屏幕坐標是以顯示器所顯示的區(qū)域為標準的,屏幕上左上角為原點,如圖2-13所示。
實例MouseLocation中輸出的是窗體坐標值。
第10章10.1.4節(jié)中對坐標系有更多的介紹。
實例2:在窗體上輸出文字
在實例MouseLocation中是使用Label控件輸出結果的,能不能不使用控件而直接在窗體上輸出文字?完全可以。
新建一個“Windows應用程序”項目,往窗體上增加一個按鈕,設置其Name屬性為“btnOutputStr”,將Text屬性改為“輸出字串”,如圖2-14所示。
給按鈕btnOutputStr的Click事件編碼如下:
Private Sub btnOutputStr_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnOutputStr.Click
Dim g As Graphics
'獲取繪圖對象
g = Me.CreateGraphics
'以紅色輸出字串
g.DrawString("您好!世界!", New Font("宋體", 16), Brushes.Red, 10, 10)
'釋放繪圖對象
g.Dispose()
End Sub
運行結果如圖2-15所示。
圖2-14 設計好的程序界面 圖2-15 在窗體上輸出文字
簡要解釋一下所寫的代碼:
要在窗體上輸出,必須獲取代表窗體可輸出區(qū)域的Graphics對象??梢园堰@個對象看成是一塊畫布,有了畫布,就可以用紅色畫刷(Brushes.Red),以16點大小的宋體字,在窗體坐標(10,10)處輸出字符串“您好!世界!”。
使用完繪圖對象g之后,使用Dispose()方法回收對象占用的資源。
程序運行時,單擊按鈕可以正確地輸出字符串。但現(xiàn)在試一試,把窗體最小化,再還原,發(fā)生了什么?原來看得見的“您好!世界!”不見了,為什么會這樣呢?
原來Windows中所有的程序窗體都是“畫出來的”,當一個窗體最小化時,Windows負責把此窗體擋住的其他窗體重繪出來;當窗體還原時,Windows就負責在原來的位置重新把此窗體畫出來。為了正確地完成這個重繪過程,Windows必須知道它需要重繪窗體上的哪些東西。
Windows知道應該重繪窗體和窗體上的控件,但在運行中使用繪圖方法輸出的內容(如上面的代碼所示)就不會自動重繪了,這就是字符串“神秘失蹤”的原理。
怎么解決這個問題呢?
Windows在每次重繪窗體的過程中,窗體對象都會引發(fā)一個Paint事件,因此可以在此事件中再次調用繪圖代碼。將原來的代碼移入到Form_Paint()事件中,如下所示。
Private Sub Form1_Paint(ByVal sender As Object, _
ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
Dim g As Graphics
'獲取繪圖對象
g = e.Graphics
'以紅色輸出結果
g.DrawString("您好!世界!", New Font("宋體", 16), Brushes.Red, 10, 10)
'釋放繪圖對象
g.Dispose()
End Sub
編譯運行之后,發(fā)現(xiàn)窗體先最小化再還原之后,文字會正確地顯示。結合前面所講的內容,您一定會明白其中的道理。
提示
10.1.2節(jié)中詳細介紹了圖形重繪原理。
現(xiàn)在運行修改過的程序,細心的讀者會發(fā)現(xiàn)程序一運行就有文字輸出,不再需要單擊按鈕了,這是因為窗體第一次顯示在屏幕上時,Paint事件也會發(fā)生。所以,現(xiàn)在可以從代碼中刪除Sub btnOutputStr_Click()過程,然后到窗體視圖中把按鈕刪除。
提示
僅刪除按鈕本身并不會同步刪除其事件響應代碼,這部分代碼需要手動刪除。
新代碼的另一個改變是獲取Graphics繪圖對象的語句變了,它可以直接從參數(shù)e中獲取。這是因為Paint事件主要用于繪圖,所以,為了方便,其參數(shù)e中就直接包含了繪圖對象g,不再需要調用窗體對象的CreateGraphics()方法。
技術探索
繪圖對象Graphics擁有相當豐富的功能,請查閱MSDN,試著在窗體上輸出一幅圖片。
除了已介紹的MouseMove事件和Paint事件,常用的Form類的事件還有以下幾個。
(1)Load事件:當窗體第一次創(chuàng)建時發(fā)生。
(2)Click/DoubleClick事件:鼠標在窗體上單擊/雙擊時發(fā)生。
(3)KeyPress/KeyDown/KeyUp事件:從鍵盤輸入時發(fā)生,通過事件提供的參數(shù),可以知道用戶按下了鍵盤的哪個鍵,其中KeyDown和KeyUp事件可以判斷組合鍵(如Ctrl+C)。
(4)MouseDown/MouseUp事件:按下鼠標鍵時發(fā)生,可以知道按下的是左鍵還是右鍵,以及按下時是否同時還按了鍵盤的組合鍵(如Ctrl、Alt、Shift)。
(5)Closing與Closed事件:當窗體關閉時發(fā)生。特別是Closing事件,可以通過設置其參數(shù)e.Cancel=True來禁止用戶單擊窗體右上角的關閉按鈕來關閉窗體。
技術探索
在MSDN中查看Form類的各種事件,了解其含義和用途。
2.1.2 常見控件使用
上一節(jié)介紹了窗體Form類的常用屬性、事件和方法。一個空白的Form并沒有多大用,F(xiàn)orm最常見的是作為一個“容器”,在其中可以加上各種各樣的控件,從而構成一個可視化的程序用戶界面。
本節(jié)將介紹三個常見界面控件:菜單、狀態(tài)條和工具欄的使用方法。在本書中不會對所有的控件做詳細介紹,而希望能通過若干典型的控件介紹一些重要的基礎知識。掌握了這些知識,讀者就可以通過其他資料來自行學會使用更多的控件了。
1.設計菜單(Menu)
菜單可謂是Windows應用程序中最重要的界面元素之一。有兩種主要的菜單形式:
(1)標準的下拉式菜單,這種菜單一般位于窗口的最上方,分為若干個菜單組,每個組下又有多個菜單項,有的菜單項還有子菜單項。
(2)彈出式菜單,這種菜單一般通過單擊鼠標右鍵彈出。
在VS .NET中,MainMenu組件用于創(chuàng)建下拉式菜單,ContextMenu組件用于創(chuàng)建彈出式菜單,如圖2-16所示。
在VS .NET中設計菜單非常簡單。以下拉式菜單為例,把一個MainMenu組件從工具箱中拖到窗體上,窗體上部就會出現(xiàn)一個菜單的雛形,并有一個選中的灰色方框可直接輸入菜單文本,如圖2-17所示。
每輸入完一項,可以按回車鍵確定。
菜單設計器
當需要修改某一項菜單時,直接用鼠標單擊選中,然后在屏幕右下方的屬性窗口設置菜單項的各種屬性。
VS .NET提供的菜單設計器非常易于使用,以下是一些使用技巧。
(1)菜單分隔線:可以直接在輸入區(qū)中輸入減號“-”生成,亦可以在菜單項屬性窗口中設置Text屬性為減號“-”。
(2)可以直接用鼠標拖動菜單項以調整相對位置。
(3)選中菜單項單擊鼠標右鍵,會彈出一個快捷菜單,其中提供了一些常用的命令(參見圖2-18)。
“剪切”一個菜單,然后在需要插入的位置選擇“粘貼”命令,可以快速地在多個菜單組之間移動子菜單項。
(3)菜單項有許多常用屬性,可以在菜單項屬性窗口中進行設置(參見圖2-19)。
菜單設計器的彈出式菜單 圖2-19 菜單項屬性窗口
這里簡單介紹一下常用屬性的用法(表2-1)。
表2-1 菜單項常用屬性
實現(xiàn)功能
說 明
設置屬性
禁用菜單項
讓選定的菜單項不可用(即灰掉)
Enabled=True
復選菜單項
選中后在菜單項前會加一個小對鉤,可以同時有多個菜單項打上此標記
Checked=True
單選菜單項
選中后會在菜單項前加一個小圓點,一個菜單組中一次只能有一個菜單項被選中
RadioCheck=True
隱藏菜單項
讓一個菜單項不顯示(仍是存在的,只不過程序運行時在菜單中看不到此項)
Visible=False
(4)彈出式菜單:使用ContextMenu組件設計,其設計方法與MainMenu基本是一樣的,但其特點是運行時并不會自動顯示此菜單,需要右擊特定的控件才能出現(xiàn)。
在VS .NET提供的許多控件中,都有一個ContextMenu屬性,只需在這些控件的屬性窗口中設計其ContextMenu屬性為指定的ContextMenu組件名字即可將控件與彈出式菜單相關聯(lián)。
試一試
(1)新建一個“Windows應用程序”項目,然后,試著建立本章實例MyEditor的菜單(參見圖2-20)。
圖2-20 “我的文本編輯器”菜單
(2)隨意設計一個彈出式菜單,設置右擊窗體彈出此菜單。
給菜單項編碼也非常簡單,只需在菜單設計器中雙擊某個菜單項,即可切換到代碼視圖。VS .NET會自動生成菜單項Click事件的框架代碼,如下所示:
Private Sub mnuOpen_Click(ByVal sender As System.Object, _
ByVale As System.EventArgs) Handles mnuOpen.Click
'在此輸入代碼
End Sub
提示
在VS .NET中,雙擊某個指定的控件會自動生成其默認事件的框架代碼,然后就可以從代碼編輯器右上方的下拉框中選取特定的事件,這是一個最基本、最常用的使用技巧,請注意掌握,以后本書不再重復介紹為某個事件編碼的基本步驟。
技術探索
(1)讀者可能會感到好奇,VS .NET是如何生成這些菜單的?
請隨意設計一個菜單,然后切換到代碼視圖,展開折疊的標有“Windows窗體設計器生成的代碼”這部分代碼,找到“Private Sub InitializeComponent()”這一過程,其中的代碼解釋了菜單生成的全部奧秘。要完全看懂這些代碼,您需要至少學完本書前4章的內容。
(2)您一定見過許多漂亮的菜單,并可能想在 VS.NET提供的MainMenu組件中給菜單加上小圖標,然而很遺憾,VS.NET 默認提供的 MainMenu 組件并沒有此功能,請您利用 Google(http://www.google.com)到因特網上去找到解決此問題的方法。在許多專業(yè)技術論壇中也有對此問題的解答。
2.使用狀態(tài)條(StatusBar)
狀態(tài)條大家并不陌生,圖2-21是Word 2003中的狀態(tài)條。
圖2-21 Word 2003提供的狀態(tài)條
可以看到狀態(tài)條主要用于顯示各種信息,信息被劃分為幾個區(qū)域。每個區(qū)域被稱為狀態(tài)條的面板(Panel)。
(1)狀態(tài)條示例介紹
在VS .NET中,提供了一個StatusBar控件用于在程序中顯示一個狀態(tài)條。請看圖2-22所示的狀態(tài)條使用實例的運行效果(示例項目UserStatusBar)。
圖2-22所示為最簡單的狀態(tài)條,在窗體下部僅有一個簡單的文本,此時狀態(tài)條處于簡單文本狀態(tài)(SimpleText)。單擊【切換狀態(tài)條顯示狀態(tài)】按鈕,可以看到如圖2-23所示的窗體。
僅有文本的狀態(tài)條圖2-23 顯示面板的狀態(tài)條
圖2-23所示為顯示了多個面板的狀態(tài)條。第一個面板僅顯示了一些文本,第二個面板加了一個圖標,第三個面板將顯示一個進度條,第四個面板是一個時鐘,顯示了當前的時間。
先單擊【將進度條放到狀態(tài)欄中】,再單擊【讓進度條開始走/?!堪粹o,可以看到進度條在狀態(tài)條上不停地“走”,如圖2-24所示。
圖2-24 不停地“走”的進度條
這個小例子雖然簡單,卻是本書所介紹的第一個復雜一些的程序。通過對這個例子的分析,相信讀者一定能大大增加對VS .NET編程的興趣。
(2)狀態(tài)欄示例分析
現(xiàn)在來揭開這個示例程序的面紗,看看怎么實現(xiàn)上面介紹過的實例功能。
① 訪問狀態(tài)條對象與面板對象
首先要明白,狀態(tài)條(StatusBar)中可以放置多個面板,每個面板都是一個StatusBarPanel對象,多個StatusBarPanel對象構成一個集合,作為狀態(tài)條對象的Panels屬性而存在。
當需要訪問一個面板對象時,要通過其所屬的集合對象訪問。假設狀態(tài)條對象StatusBar1中有三個面板,則以下代碼設置第2個面板的文本為“這是第2個面板”:
StatusBar1.Panels(1).Text = "這是第2個面板"
注意
.NET中集合的索引都是從0開始的,所以,Panels(0)代表第一個面板,而Panels(2)是第三個面板。
提示
.NET的許多控件中都有集合屬性,這些集合屬性一般其名字后面都以“s”結尾,集合中往往存放著許多類型一致的對象。訪問集合中對象的方法都是一致的,按以下代碼格式進行:
控件對象名.集合屬性名(要訪問的對象在集合中的位置)
在.NET中,集合的位置稱為索引,是從0開始的。
有關集合的更詳細介紹,請參見第3章。
可以通過狀態(tài)條對象的屬性窗口給狀態(tài)條增加面板,如圖2-25所示。
在圖2-25中,單擊Panels屬性右邊的標有省略號的按鈕,將會打開“StatusBarPanel集合編輯器”窗口,如圖2-26所示。
狀態(tài)條對象的面板屬性圖2-26 “StatusBarPanel集合編輯器”窗口
從圖2-26中可以看到,使用“StatusBarPanel集合編輯器”窗口,可以方便地在左邊列表框中添加和刪除面板對象,在右邊網格中則可以設置選中的面板對象的屬性。
表2-2是StatusBarPanel對象的一些重要屬性。
表2-2 StatusBarPanel的重要屬性
屬 性
用 途
Text
定義在面板上顯示的文字
BorderStyle
定義面板的顯示外觀(邊框是凸起還是凹下,或是根本沒有邊框)
Icon
定義圖標(示例中第2個面板顯示的小圖片就是通過此屬性提供的)
AutoSize
定義面板的大小是不是會隨著Text所定義的文字長度而變化
當把一個狀態(tài)條對象從工具箱拖到窗體上時,它默認是不顯示面板的。要顯示面板,請將其ShowPanels屬性置為True(參見圖2-25)。
狀態(tài)條不處于面板狀態(tài)時(即ShowPanels=False),要設置它上面的文字請使用其Text屬性。
以下是示例程序中切換狀態(tài)條顯示狀態(tài)的代碼:
01 Private Sub btnChangeStatus_Click(ByVal sender As System.Object, _
02 ByVal e As System.EventArgs) Handles btnChangeStatus.Click
03 '隱藏進度條
04 Me.ProgressBar1.Visible = False
05 With Me.StatusBar1
06 .ShowPanels = Not .ShowPanels
07 If .ShowPanels = False Then
08 .Text = "這是簡單文本狀態(tài)"
09 Else
10 .Panels(0).Text = "這是顯示面板狀態(tài)"
11 End If
12 End With
13 End Sub
注意第5行和第12行引入了一個新的關鍵字——With。With關鍵字主要用于簡化程序代碼輸入。其格式為:
With 對象名
'使用對象的各種代碼
End With
在With塊內部,對象的名字可以簡寫。
在前述的示例代碼中:
With Me.StatusBar1
.ShowPanels = Not .ShowPanels
'……
End With
其實相當于以下代碼:
Me.StatusBar1.ShowPanels = Not Me.StatusBar1.ShowPanels
讀這種語句時,只需要把With后對象名字搬到代碼中以英文句點“.”打頭的部分就能“湊”出完整的代碼。
另外一個是“Me”這個詞語,Me在VB.NET中是一個關鍵字,代表對象自身。在本例中,這些代碼是寫在窗體內部的,所以,Me就代表窗體對象frmStatus。又由于狀態(tài)條對象StatusBar1是放在窗體上的,所以,可以通過Me.StatusBar1來訪問這個對象。其實,也可以不寫Me,直接把“Me.StatusBar1”換成“StatusBar1”,程序仍然正確。那為什么多寫一個Me呢?
讀者只要在代碼編輯器中試一試就明白了,輸入一個Me,再加一個小句點,VS .NET會馬上下拉出一個方法和屬性清單。再輸入“stat”幾個字母,馬上就可找到StatusBar1這個對象,直接按空格或Tab鍵都會讓StatusBar1直接上屏。這是不是很方便?以后讀者還會看見其他示例中的代碼前也有一個Me,一般情況下去掉它對程序沒有任何影響,但對解放讀者的手指頭可大有影響(參見圖2-27)。
現(xiàn)在再來看看第6行代碼:
06 .ShowPanels = Not .ShowPanels
前面說過,當ShowPanels屬性為True時顯示狀態(tài)條對象會顯示面板,如果需要不斷地在“顯示”和“隱藏”兩種狀態(tài)之間切換,可以按照以下處理邏輯來寫代碼:
如果 ShowPanels=True 那么
ShowPanels=False
否則
ShowPanels=True
使用Me加快代碼輸入
換成VB.NET代碼之后,就是這樣:
If StatusBar1.ShowPanels = True Then
StatusBar1.ShowPanels = False
Else
StatusBar1.ShowPanels = True
End If
注意到True和False是互斥的,非此即彼,所以,可以使用一個Not運算符來把上面的4句變成一句(見示例中的第6行)。
Not運算符的作用就是取反:
Not False=True
Not True=False
示例中的第4個面板是一個時鐘,有關顯示時鐘的代碼相信讀者已經知道了,只需取出當前時間,然后設置對應面板對象的Text屬性即可。實際代碼不再列出。
② 在狀態(tài)欄上顯示進度條
進度條(ProgressBar)往往直觀地顯示某個工作完成了多少,多用數(shù)字表達,它的使用很簡單,只需要明了如表2-3所示的屬性的含義即可。
表2-3 進度條屬性含義
屬 性
含 義
Minimum
最小值
Maximum
最大值
Value
當前值
舉個實例:若Minimum=0,Maximum=100,則當Value=50時,表明當前工作完成了50%。
現(xiàn)在的問題是進度條與狀態(tài)條是兩個彼此獨立的控件,怎樣把它們兩個放在一塊?
答案是把進度條對象的Parent屬性設為狀態(tài)條對象,這樣進度條對象就成了狀態(tài)條對象的“兒子”,會乖乖地待在進度條對象內部。
試一試
新建一個Windows應用程序項目,往窗體上加一個狀態(tài)條和進度條,隨便往狀態(tài)條中加幾個面板,然后編寫代碼將進度條的Parent屬性設為狀態(tài)條對象,看看發(fā)生了什么?
讀者會發(fā)現(xiàn)進度條確實跑到狀態(tài)條內部去了,但卻把面板給擋住了,顯然這樣是不行的,還必須想辦法精確定位進度條到指定的位置。
仔細觀察一下示例程序的狀態(tài)條(圖2-28)。
組合了進度條的狀態(tài)條
讀者會看到,進度條的長短和高度剛好和第3個面板一樣,所以,如果能得到第3個面板的長度和高度,就知道該把進度條“縮小”并“平移”到多少才可以放進面板中。再看看圖2-25所示的StatusBarPanel對象屬性中,有一個Width屬性剛好符合要求。
下一個問題是進度條的左上角坐標。如果把進度條的左上角坐標設為0,則進度條會把第一個面板給蓋住,因此必須知道第3個面板的左上角坐標。很遺憾,StatusBarPanel對象沒有一個對應的屬性能直接提供這個數(shù)據,必須另想方法。
進度條要放到第3個面板上,而前兩個面板的寬度是知道的,那么,很容易就有以下公式:
進度條的左上角距狀態(tài)條左邊框的距離= 第1個面板的寬度 + 第2個面板的寬度
技術內幕
不是所有控件都能被設為其他控件的Parent的,可以這樣做的控件稱為“容器”控件(即可以容納其他控件的控件)。當一個控件被加入到一個容器控件中時,它的位置坐標是以容器為準的。比如Form就是一個容器控件,當設置一個標簽的Left屬性為100時,其含義是該標簽到窗體左邊緣的距離是100像素,而不是到屏幕左邊距的距離是100像素。因為窗體往往是可以在屏幕上移動的,因此標簽到屏幕左邊距的距離會不斷改變,而標簽相對于窗體的距離是不變的。因此,我們把標簽相對于窗體的坐標值稱為“相對坐標”(或“窗體坐標”),標簽到屏幕邊距的坐標稱為“絕對坐標”(或“屏幕坐標”)。
現(xiàn)在,所有的問題都解決了,讀者應該可以看得懂示例的代碼了:
Private Sub btnAddProgressBar_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnAddProgressBar.Click
With Me.ProgressBar1
'設置左邊距
.Left = Me.StatusBar1.Panels(0).Width + _
Me.StatusBar1.Panels(1).Width + 2
.Top = 2 '設置上邊距
.Width = Me.StatusBar1.Panels(2).Width '設置寬度
.Height = Me.StatusBar1.Height - 4 '設置高度
.Parent = Me.StatusBar1 '將其加入到狀態(tài)條中
.Visible = True '讓其可視
End With
End Sub
注意,為了美觀,應把進度條上下高度縮進2個像素,這樣,狀態(tài)條的邊框就可以完整地顯示出來,從而保持了面板外觀的一致性。
提示
如果能先弄明白程序中實現(xiàn)某個功能的思路是什么,再閱讀這段代碼就很容易理解了。解決某個功能的思路,一般是通過在程序代碼中加上注釋,或者是在軟件所配的文檔中說明。
3.使用工具條(ToolBar)
工具條是另一種常見的用戶界面控件,其上可以放置多個按鈕。圖2-29是本書示例程序MyEditor的工具條。
工具條
(1)給工具條添加按鈕
工具條上的每一個按鈕都是一個ToolBarButton對象,工具條上的所有按鈕組成一個集合,以工具條對象的Buttons屬性代表。
類似于訪問狀態(tài)條的面板對象,可以通過以下代碼來訪問工具條ToolBar1的第一個按鈕的Text屬性。
Me.ToolBar1.Buttons(0).Text = "New"
要給工具條添加按鈕,請在工具條對象的Buttons屬性上單擊標有省略號的按鈕,打開如圖2-30所示的按鈕編輯窗口。
給工具條增加按鈕
從圖2-30可以看到,給工具條增加按鈕,非常類似于給狀態(tài)欄增加面板,其操作也是類似的。
提示
在程序中訪問狀態(tài)條的面板與訪問工具條的按鈕,其方法都是一樣的,掌握這種編程模式,就可將其應用到所有需要訪問集合成員的場合,這將在后面的實例代碼中不斷看到。在學習中善于總結這種共性的東西,就可以舉一反三,提高學習的效率。
(2)給工具條按鈕增加圖標
從圖2-30中只看到ToolBarButton對象中有一個ImageIndex屬性,并沒有像狀態(tài)欄的面板對象那樣有一個Icon屬性可以指定一個圖標。那么,工具條按鈕上的小圖標從何而來?
這就用到了另一個控件ImageList。ImageList是一個專用于存放小圖標的控件。使用時將其從工具箱中拖到窗體上,然后,打開其Images屬性,會出現(xiàn)以下窗口(圖2-31)。
向ImageList控件增加圖標
給ImageList控件增加完圖標以后,再設置ToolBar控件的ImageList屬性為包含了多個圖標的ImageList控件,如圖2-32所示。
這時,就可以從ToolBarButton集合編輯器中給特定的按鈕添加圖標,如圖2-33所示。
將ImageList控件與 圖2-33 給工具條按鈕添加圖標
ToolBar控件聯(lián)系起來
試一試
(1)ToolBarButton的Style屬性有四個取值:PushButton,ToggleButton,Seperator和DropDownButton,請嘗試找出這四個值的作用。
(2)ToolBarButton有一個ToolTipText屬性,請弄清楚這個屬性的作用。
提示
ImageList這個控件常被用來向許多其他控件提供圖標,比如TreeView和ListView,在后面的內容中會看到這方面的介紹。
(3)確定單擊的按鈕
在窗體設計器上雙擊工具條對象,將會出現(xiàn)以下代碼框架:
'工具條按鈕事件
Private Sub ToolBar1_ButtonClick(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.ToolBarButtonClickEventArgs) _
Handles ToolBar1.ButtonClick
End Sub
那么如何編程確定用戶單擊的是哪個按鈕?
注意到工具條對象的單擊事件處理框架代碼中有一個類型為ToolBarButtonClickEventArgs的參數(shù)e,通過它的Button屬性就可以知道單擊的按鈕對象。
可以通過按鈕對象的Text屬性來具體區(qū)分是哪個按鈕,比如:
If e.Button.Text = "新建" Then
'單擊了“新建”按鈕
'……
End If
但由于Text屬性經常改變,所以,比較好的方式是通過控件的屬性窗口給每個ToolBar的Tag屬性賦一個惟一的字符串值,然后,通過此屬性來區(qū)別開各個按鈕。典型的代碼如下所示:
Private Sub ToolBar1_ButtonClick(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.ToolBarButtonClickEventArgs) _
Handles ToolBar1.ButtonClick
Select Case e.Button.Tag
Case "New" '單擊了"新建"按鈕
Me.NewFile()
Case "Open" '單擊了"打開"按鈕
Me.OpenFile()
'……
End Select
End Sub
上面代碼中的Select語句稱為多條件分支語句,其格式為:
Select Case 變量名
Case 值1
'處理代碼
Case 值2
'……
End Select
這個語句其實完全可以用條件語句來表達:
If 變量名=值1 then
'處理代碼
End If
If 變量名=值2 then
'處理代碼
End If
'……
顯然,Select Case語句更清晰和明確。
提示
2.2節(jié)將詳細介紹VB.NET語法。
2.1.3 界面布局
在實際的程序中,往往一個窗體上會有多個控件,而窗體的大小也可以用鼠標動態(tài)地調整。窗體大小改變時,窗體上的控件尺寸也必須做相應調整,才能讓用戶界面有一個比較滿意的效果??丶腄ock屬性與Anchor屬性就是起到這個作用的。
1.Dock屬性
通過一個示例來說明Dock屬性的作用。
請從工具箱上拖動一個RichTextBox控件到窗體上,然后編譯運行,試著改變窗體的大小,會發(fā)現(xiàn)RichTextBox控件始終保持原有的大小不變,如圖2-34所示。
選中RichTextBox控件,在其屬性窗口中設置其Dock屬性為Fill,參見圖2-35。
控件大小不隨窗體大小而變圖2-35 設置Dock屬性為Fill
再次編譯運行程序,可以發(fā)現(xiàn)RichTextBox控件自動布滿整個窗體區(qū)域,并可隨著窗體大小的改變而自動改變大小。
試一試
將Dock屬性改為其他值,再看看效果。
2.Anchor屬性
從工具箱中拖動一個按鈕到窗體上,然后編譯運行程序,改變窗體大小,可以看到按鈕本身是不會改變大小和位置的。
現(xiàn)在選中按鈕對象,在屬性窗口中將其Anchor屬性改為“Left,Right”,如圖2-36所示。
現(xiàn)在,編譯運行程序,改變窗體大小,可以看到按鈕寬度會隨著窗體的寬度變化而改變,如圖2-37所示。但改變高度時,按鈕寬度不受影響。
Anchor屬性 圖2-37 按鈕寬度與窗體寬度同步變化
試一試
將Author屬性設為其他值,編譯運行,改變窗體大小,看看結果。
從上面的嘗試中可以得出以下幾個有用的組合,如表2-4所示。
表2-4 Anchor屬性組合
Anchor屬性值
窗體尺寸變化時對控件尺寸的影響
Left,Right
控件寬度與窗體寬度同步變化
Right,Bottom
控件與窗體右下角的距離保持不變
Bottom
控件與窗體下邊距的距離保持不變
Left,Right,Top,Bottom
窗體尺寸大小的任何改變,都會使控件的尺寸做同步變化
表2-4中是以窗體為例的,事實上,Anchor屬性對任何容器控件內的控件都適用。所謂容器控件就是指能放置其他控件的控件,如窗體(Form)、面板(Panel)、頁控件(TabControl)等。當容器的尺寸改變時,容器內的控件按照其Anchor屬性值對這種改變做出相應的變化。
注意
(1)Dock與Anchor屬性對于設計用戶界面是非常重要的。
設計窗體時,應保證在800′600的屏幕分辨率下仍能顯示完整的窗體,而在1024′768或更高的分辨率上也能讓窗體顯示正常,這時,就要根據分辨率大小而適當改變窗體大小。合理設置窗體中控件的Dock與Anchor屬性值就顯得十分重要,否則,這個工作就必須手動編碼來完成,工作量不小。
(2)何時用Dock,何時用Anchor?
Dock屬性主要用于容器控件(窗體除外)和擁有較大尺寸的控件,比如RichTextBox控件、樹控件(TreeView)等;而Anchor則用在小尺寸控件中,如按鈕(Button)、標簽(Label)等。對容器控件很少需要用Anchor屬性,多用Dock屬性。
當然,何時用Dock,何時用Anchor并沒有絕對正確的答案,需要在開發(fā)實踐中靈活取舍,以達到滿意的界面效果。
3.同時設置多個控件的共同屬性
如果在窗體上同時有不止一個控件,則可以拖動鼠標畫出一個框同時選中這些控件,如圖2-38所示。
也可以先單擊選中第一個控件,然后按住Ctrl鍵,再次單擊其他控件。如果要從已選擇的控件集合中去掉某個控件,只需再次單擊它即可(注意要同時按下Ctrl鍵),如圖2-39所示。
圖2-38 用鼠標拖動選擇多個控件 圖2-39 選中的多個控件
一旦選中了多個控件,就可以使用“布局”工具條(參見圖2-40)對這些控件進行對齊、平均分布等操作。
圖2-40 VS .NET窗體設計器的布局工具條
針對選中的多個控件,VS .NET還會自動地抽取出所有這些控件都擁有的屬性,顯示在屬性窗口中,這時,對任何一個屬性所做的修改都會同時作用于所有選中的控件。
試一試
在窗體上放置多個文本框,然后選中它們,在屬性窗口中設置其Text屬性為空,觀察效果。
2.1.4 繪制圖標
在Windows Form應用程序開發(fā)中,有許多地方(比如工具條按鈕)需要用到圖標。在VS .NET中,可以直接創(chuàng)建圖標文件。
新建一個“Windows應用程序”,然后在“解決方案資源管理器”窗口中右擊項目節(jié)點,選擇“添加/添加新項”命令,出現(xiàn)如圖2-41所示的對話框。
添加圖標文件
注意選中“圖標文件”模板,單擊【打開】按鈕,可以看到圖2-42所示的畫面。
圖2-42的左側有一個調色板,在相應色塊上單擊鼠標左鍵可定義前景色,單擊鼠標右鍵可設置背景色。在上方的工具條上提供了繪制直線、文字等工具。注意,VS .NET的主菜單中增加了一個新菜單——“圖像”,其中提供了一些在工具條上沒提供的功能(參見圖2-43)。
VS .NET的圖標編輯器圖2-43 圖像菜單
圖標編輯器的使用很簡單,用提供的繪圖工具畫好圖標后,保存在磁盤上即可。
技術內幕
Windows使用的圖標最常用的有兩種:16′16(長與寬均為16像素)的,常用于工具欄按鈕圖標;32′32的,大都用在普通按鈕上。圖標文件的后綴名為“.ico”。一般來說,圖標顏色數(shù)不易過多,黑白兩色的對比明確,但略顯單調,256色的比較美觀,但當圖標尺寸為16′16時,人眼分辨不清,16色是較好的選擇。
有許多專用的圖標制作工具,可以到因特網上去搜索,也可以使用功能強大的繪圖軟件如CorelDraw、Photoshop等制作圖標,但要注意顏色及尺寸大小的問題。
試一試
到目前為止,讀者已學習了許多使用控件來設計用戶界面的知識,現(xiàn)在是實踐的時候了。請從本書配套光盤的示例源碼下找到MyEditor示例程序,這是一個文本編輯器,如圖2-44所示。
MyEditor用戶界面
請試著用所學到的知識把這個界面設計出來。
提示
(1)這個文本編輯器用到一個MainMenu控件繪制菜單,一個工具條(ToolBar),一個RichTextBox控件用于編輯文本,一個狀態(tài)條(StatusBar)用于顯示信息。
(2)當窗體大小改變時,RichTextBox會自動變換大小,占滿整個窗體的空白區(qū)域,如何實現(xiàn)?
(3)StatusBar有三個面板,處于顯示面板狀態(tài)。
(4)工具欄上的按鈕圖標部分是VS .NET直接提供的,部分可使用本節(jié)介紹的VS .NET圖標編輯器繪制。