WinForm二三事(四)界面佈局(上)

記得剛從Web轉向WinForm開發時有一段狂喜,沒有Session丟失、沒有瀏覽器不兼容,不用圍着HTML、CSS、Javascript、C#打轉,在Visual Studio裏控件擺成什麼樣子最後就是什麼樣子(雖然根據最終用戶的操作系統和分辨率設置有稍微的不同),這種感覺好久沒有過了,但隨着開發的界面越來越複雜卻有些無措。

接下來我會分兩篇來介紹WinForm界面佈局中需要注意的一些事情,上篇介紹的是一些簡單但也常見的,而下篇會接觸更爲複雜但靈活的內容。

Dock & Anchor

Dock和Anchor是水火不容的,同時給控件設置Dock和Anchor屬性時後設置的會覆蓋前面的設置。

Dock

我初接觸WinForm的時候發現控件的Width和Height只能是int,不能設置爲百分比,那個時候就想子控件如何跟着父控件而變化呢?父控件變大就變大,而且還填滿那個區域。甚至以爲這個需要通過代碼來解決,如是在SizeChanged事件裏……

後來才發現我要的就是Dock給的,Dock是停靠的意思。Dock屬性的類型是DockStyle枚舉:

   1: public enum DockStyle
   2: {
   3:     None = 0,
   4:     Top = 1,
   5:     Bottom = 2,
   6:     Left = 3,
   7:     Right = 4,
   8:     Fill = 5
   9: }

默認是None,當爲Left的時,就表示子控件停靠在父控件的左邊區域,並把左區域填充滿:  
image

上圖中的Panel總是會停靠在Form的左邊區域,不管如何調整Form的高度,它總是能把左邊區域填滿。Dock麻煩的地方在於多個控件碰到一起時,比如有兩個Panel都設置爲Left該怎麼辦?我們會發現向父控件的Controls集合中添加子控件,越晚添加具有更高的“優先級”(不知道有沒有這個說法,這是我杜撰的)。這裏的優先級指的是,子控件“優先級”越高,越靠近父控件邊緣,其他子控件就得避讓:

this.Controls.Add(this.panel1); 
this.Controls.Add(this.panel2);

image

Panel2後添加進去,所以它具有更高的優先級,更靠近Form的邊緣。這個規則不僅僅對於Dock都設爲Left的有效,對Dock屬性不同,但是碰到一起的子控件也適用:

   1: this.panel1.Dock = DockStyle.Left;
   2: this.panel2.Dock = DockStyle.Left;
   3: this.panel3.Dock = DockStyle.Top;
   4:  
   5: this.Controls.Add(this.panel1);
   6: this.Controls.Add(this.panel2);
   7: this.Controls.Add(this.panel3);

panel3的Dock設置爲Top,最後添加到Controls集合中,所以具有最高的優先級,其他兩個panel都得避讓: 
image

將Panel3調整成第二個添加進去呢?卻是這番景象: 
image

當然,這裏的“優先級”比較只在同一個“層次”起作用,將子控件與父控件同一層次的控件相比較是沒有意義的。

Anchor

Dock一般是劃分區域的,將一個窗體或大控件劃分幾個大區域以便佈局,就像Web中的div一樣。但有的時候我們卻想子控件在父控件裏的相對位置不要隨着父控件的變大縮小而變化,或者控件邊緣距離父控件邊緣的相對距離不要發生變化,但又不是停靠在父控件的邊緣,這就是Anchor該出場的時候了。

Anchor屬性的類型是AnchorStyles位標記(如果不理解位標記就將其理解爲可以使用位運算符進行操作,從而可以設置多個值的枚舉吧,深入理解請參見MSDN或《CLR via C#》中對位標記的描述):

   1: [Flags]
   2: public enum AnchorStyles
   3: {
   4:     None = 0,
   5:     Top = 1,
   6:     Bottom = 2,
   7:     Left = 3,
   8:     Right = 4
   9: }

Anchor的默認值是Anchor.Left | Anchor.Top,也就是子控件與父控件的左邊緣和上邊緣的相對位置不會變化,這也保證了在窗體最大化後子控件的位置不會發生變化:

窗體默認顯示時->

image

窗體變大後->

image
還是懸停在左上角不會發生變化。那如果設置爲Left和Right呢?當Anchor設置爲Left|Right的時候,爲了確保父控件(在這裏就是Form)變大時,控件的邊緣與父控件距離不變,子控件會自動的擴大:

image

總之,記着Anchor的中文意思:錨。當給控件設置Anchor的時候,就相當於用一個鐵釘將控件的邊緣給釘住。  

Padding & Margin

Padding和Margin沒有什麼好說的,和CSS的盒模型描述的一模一樣,Padding指的是控件內部空間,Margin指的是控件之外的:

image

padding和margin都可以指定四個值。

AutoSize

有的時候我們需要控件隨着裏面的內容的增長而增長,比如在做多語言的程序時,各國的語言描述同一個意思的時候長度會不同,這個時候就需要AutoSize爲true了,這樣當文字過長不會被截斷。關於AutoSize更詳細的內容請參見MSDN

如何面對複雜界面?出了問題咋辦?

設計時

當界面變得越來越複雜的時候,我們很期望瞭解控件之間的層次關係,這個按鈕是放在哪個Panel上?這個Panel的區域又是咋樣的。我剛接觸WinForm的時候,我非常期望WinForm上也有類似於IE Developer Toolbar的工具,點擊HTML,可以可視化的在界面上顯示區域,在界面上選中某區域,也可以定位到HTML元素。實際上在設計WinForm界面時也是可以的。這就是Visual Studio的Document Outline窗口(View->Other Windows->Document Outline):

image

不過如果你只想順着button1->panel4->panel3->……這條線導航控件樹,有更方便的方法:ESC鍵。選中一個控件,然後敲ESC鍵就會順着這個控件層次不斷的上溯。

選中一個控件,然後點擊右鍵,還會出現 Select …的菜單,可以選中該控件的某個父控件:

image

真是夠方便的~~~

運行時

不過有個問題是,上面的方法都是設計時的,有的時候我們的程序中如果動態的修改了某些涉及佈局的屬性最後發現界面亂套了,這可咋整。運行時的問題當然要運行時解決,給某個父控件附加Layout事件,當修改了涉及Layout的屬性時會觸發這個事件(也有特例,下一節介紹)。這個事件會有一個LayoutEventArgs參數,該參數有AffectedProperty屬性,該屬性指示的就是影響佈局的罪魁禍首,你就找到病症所在了。

SuspendLayout & ResumeLayout

我想大家對這兩個方法肯定不默認,幾乎在WinForm裏的InitializeComponent方法裏,在方法開始處有會調用SuspendLayout方法,然後在方法快結束處會調用ResumeLayout方法。有些的讀者也許還嘗試過刪除這兩個方法,發現程序表現行爲和以前也一樣。

瞭解這兩個方法對WinForm程序的性能還是挺有幫助的,在上一節提到修改涉及Layout的屬性時會觸發Layout事件,但是有特例,特例就是調用了SuspendLayout方法,關於修改哪些屬性會觸發Layout事件請查閱MSDN。在代碼中如果修改Size、Dock等屬性或向父控件添加子控件時,會執行佈局邏輯,有的時候甚至會重繪。當我們要修改一堆的這樣的屬性時,比如前面提到的InitializeComponent方法,我們當然不想修改一下就執行一次佈局邏輯,那太慢了。這個時候在修改之前你就可以調用SuspendLayout方法掛起佈局邏輯,等所有屬性都設置好後再調用ResumeLayout屬性,特別是在界面很複雜的時候性能有很大的提升。

Visual Studio默認將設置這些屬性的語句全部放在InitializeComponent方法裏,然後用SuspendLayout和ResumeLayout括住,所以我們一般不要自作主張的將這些屬性移出到外面設置,不過有的時候我們想在代碼裏動態生成一些界面,比如添加一些子控件什麼的,我們最好也像VS乾的那樣調用這兩個方法。

要注意的是,並不是調用了Form的SuspendLayout和ResumeLayout方法就一了百了了。如果你是向一個Panel添加子控件,你還得調用Panel的這兩個方法。

總結

本文介紹了WinForm界面佈局的初步知識,還介紹了通過臨時掛起佈局邏輯來優化程序性能。這些都很基礎也很簡單,應付簡單的佈局是夠了,不過對於更靈活更復雜的佈局就要涉及佈局引擎的內容了,這個我會在下一節裏介紹。

本系列其他文章

WinForm二三事(一)消息循環

WinForm二三事(一)補遺

WinForm二三事(二)異步操作

WinForm二三事(三)Control.Invoke&Control.BeginInvoke

WinForm二三事(四)界面佈局(上)

WinForm二三事(四)界面佈局(下)

WinForm二三事(五)實作

WinForm二三事(六)數據綁定

WinForm二三事(七)GDI+

WinForm二三事(八)開源項目

WinForm二三事(九)常用第三方控件庫

WinForm二三事(十)漫談

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章