从ContainerControl类继承的子类作为容器窗体,可以容纳除Form类对象外的其余窗体对象。

在所有容器窗体内,最基本的就是顶级容器Form类以及面板容器Panel类。这两者的主要区别为:前者具有Windows标准框架(标题栏,最大 化、最小化和关闭按钮,窗体边框,可调整尺寸),并且可以独立存在;后者只是一块区域,并且必须依附在某个容器窗体上,无法独立存在。除了它们的区别外, 他们都具有:Controls属性,可以在上面放置控件;控件放置的位置需要通过控件的Top和Left属性或者控制。

 

1 绝对布局

对于容器类型的控件 (包括Form类型),出了Size,Bounds属性外,还有ClientSizeClientRectangle 属性,前者表示客户区尺寸,Size类型;后者表示客户区的矩形,是一个Rectangle类型。所谓客户区,就是容器实际可以使用的空间,对于Form类型来说,客户区就是除过标题栏,四周的边框外剩余的部分。

客户区示意图 图1 客户区示意图

一般来说,直接在Form上或Panel上放置控件,控件的位置不会自动调整,完全依赖控件的Left, Top和Location等属性控制,控件的大小也不会自动改变,完全依靠控件的Width, Height和Size等属性控制。这种方式称为绝对位置布局

我们再来熟悉一下这些用于定位一个控件的属性们,它们可以用于获取或设置控件的位置和尺寸:

  • Left 属性:控件距离其容器左边界的距离,int类型;
  • Top 属性:控件距离其容器上边界的距离,int类型;
  • Location 属性:控件左上角坐标距离其容器的距离,Point类型。一般而言,可以把容器的左上角认为是坐标轴原点,则Location属性表示控件相对于其容器的坐标。所以其X属性等于控件的Left属性,Y属性等于控件的Top属性;
  • Width 属性:控件的宽度,int类型;
  • Height 属性:控件的高度,int类型;
  • Size 属性:控件的尺寸,包括宽度和高度,Size类型;

除了以上六个属性外,还可以使用Bounds 属性,这是一个Rectangle类型的属性,表示一个相对于容器左上角为坐标原点的矩形,即控件的位置和尺寸。利用SetBounds方法还可以使用X, Y, Width, Height四个分量设置Bounds属性。

 

2 锚定相对布局

如果进一步设置控件的Dock属性,则可以设定控件再容器内的相对位置 ,Dock属性可以设置控件按照其所在容器的“左右上下中 ”这五个方位来放置控件,此时控件只能设置Width、Height和Size属性,而无法设置Left、Top和Location属性,即控件只能调整大小,无法自由设置位置。这种控件依照容器的相对位置放置控件的方式称为“相对位置布局 ”。

Dock的属性值是一个DockType枚举,其称谓为“锚定 ”,就是将控件和其容器的左右上下中这五个位置绑定,无论容器移动到什么地方,也无论容器的尺寸如何变化,控件始终确定在这五个位置之一的地方。这个枚举其定义如下:

    ///   < summary >
    ///   定义锚定方位的枚举
    ///   < /summary >
    public   enum   DockStyle   {
        ///   < summary >
        ///   取消锚定
        ///   < /summary >
        None   =   0 ,
   
        ///   < summary >
        ///   锚定在顶部
        ///   < /summary >
        Top   =   1 ,
   
        ///   < summary >
        ///   锚定在底部
        ///   < /summary >
        Bottom   =   2 ,
       
        ///   < summary >
        ///   锚定在左边
        ///   < /summary >
        Left   =   3 ,
       
        ///   < summary >
        ///   锚定在右边
        ///   < /summary >
        Right   =   4 ,
       
        ///   < summary >
        ///   锚定在中央并充满容器
        ///   < /summary >
        Fill   =   5 ,
    }

锚定布局方位图 图2 锚定布局方位图

下面我们用一段代码来演示一下:

using   System;
using   System . Drawing;
using   System . Windows . Forms;
 
namespace   Edu . Study . Graphics . DockLayout   {
 
    ///   < summary >
    ///   继承Form类,   定义窗体类
    ///   < /summary >
10      class   MyForm   :   Form   {
11   
12          //   以不变模式设定WIN_TITLE,   表示窗体标题
13          private   const   string   WIN_TITLE   =   " 面板演示 " ;
14   
15          ///   < summary >
16          ///   面板类
17          ///   < /summary >
18          private   Panel   panel;
19   
20          ///   < summary >
21          ///   按钮
22          ///   < /summary >
23          private   Button   button;
24   
25          ///   < summary >
26          ///   单选按钮数组
27          ///   < /summary >
28          private   RadioButton [ ]   radioButtons;
29   
30          ///   < summary >
31          ///   构造器
32          ///   < /summary >
33          public   MyForm ( )   {
34              //   设定窗体标题
35              this . Text   =   WIN_TITLE;
36              //   设定窗体最大化
37              this . WindowState   =   FormWindowState . Maximized;
38   
39              //   实例化面板类对象
40              this . panel   =   new   Panel ( ) ;
41              //   设置面板的边框为三维效果
42              this . panel . BorderStyle   =   BorderStyle . Fixed3D;
43              //   设定面板的锚定方式为无
44              this . panel . Dock   =   DockStyle . None;
45              //   设定面板的背景色为白色
46              this . panel . BackColor   =   Color . White;
47              //   为面板更改尺寸事件绑定委托方法
48              this . panel . Resize   + =   new   EventHandler ( OnPanelResized ) ;
49   
50              //   实例化按钮对象
51              this . button   =   new   Button ( ) ;
52              //   因为button是panel的子窗体,   所以背景色默认也会变为白色
53              //   这里为按钮重新指定颜色,   SystemColors.Control是操作系统默认的控件背景色
54              this . button . BackColor   =   SystemColors . Control;
55              //   设置按钮文本
56              button . Text   =   " 点下我! " ;
57              //   为按钮点击事件绑定委托方法
58              button . Click   + =   new   EventHandler ( OnButtonClick ) ;
59   
60              //   将按钮加入面板
61              this . panel . Controls . Add ( this . button ) ;
62   
63              //   实例化radioButtons数组,   数组长度和锚定样式数量相同
64              this . radioButtons   =   new   RadioButton [ ( int ) DockStyle . Fill   +   1 ] ;
65              for   ( int   i   =   0 ;   i   <   this . radioButtons . Length;   i + + )   {
66                  RadioButton   rb   =   new   RadioButton ( ) ;
67                  //   设定RadioButton的显示文本
68                  rb . Text   =   ( ( DockStyle ) i ) . ToString ( ) ;
69             
70                  //   将RadioButton对象加入面板
71                  this . panel . Controls . Add ( rb ) ;
72                  //   为数组元素赋值RadioButton对象引用
73                  this . radioButtons [ i ]   =   rb;
74              }
75   
76              //   将面板加入窗体
77              this . Controls . Add ( this . panel ) ;
78          }
79   
80          ///   < summary >
81          ///   处理按钮点击事件的委托方法
82          ///   < /summary >
83          private   void   OnButtonClick ( object   sender,   EventArgs   e )   {
84   
85              //   设置面板的宽度和高度为父窗体的1/3
86              this . panel . Width   =   this . ClientSize . Width   /   3 ;
87              this . panel . Height   =   this . ClientSize . Height   /   3 ;
88   
89              //   每点击一次按钮,   更改面板的锚定方式
90              if   ( this . panel . Dock   = =   DockStyle . Fill )   {
91                  this . panel . Dock   =   DockStyle . None;
92              }   else   {
93                  this . panel . Dock   =   ( DockStyle ) this . panel . Dock   +   1 ;
94              }
95   
96              //   更改主窗体标题
97              this . Text   =   String . Format ( " {0}   面板的锚定方式目前为:{1} " ,   WIN_TITLE,   this . panel . Dock ) ;
98              this . radioButtons [ ( int ) this . panel . Dock ] . Checked   =   true ;
99          }
100   
101          ///   < summary >
102          ///   面板尺寸改变事件委托方法
103          ///   < /summary >
104          private   void   OnPanelResized ( object   sender,   EventArgs   e )   {
105              //   在面板改变尺寸后,   设置按钮位于面板中央
106              this . button . Left   =   ( this . panel . ClientSize . Width   -   this . button . Width )   /   2 ;
107              this . button . Top   =   ( this . panel . ClientSize . Height   -   this . button . Height )   /   2 ;
108   
109              //   间隔距离
110              int   marginPart   =   0 ;
111              //   起始位置
112              int   start   =   0 ;
113              //   遍历RadioButton数组,   设置每一个RadioButton的位置
114              foreach   ( RadioButton   rb   in   this . radioButtons )   {
115                  //   根据面板的锚定方式选择计算位置的分支
116                  switch   ( this . panel . Dock )   {
117                  case   DockStyle . Fill :
118                  case   DockStyle . Top :
119                  case   DockStyle . Bottom :
120                      //   对于填充,   置顶,   置底三种锚定方式,   采用横向摆放RadioButton控件
121                     
122                      //   设定每个RadioButton的上边距为面板高度的四分之一
123                      rb . Top   =   this . panel . ClientSize . Height   /   4 ;
124   
125                      //   计算每个RadioButton控件的间距
126                      if   ( marginPart   = =   0 )   {
127                          marginPart   =   this . ClientSize . Width   /   this . radioButtons . Length;
128                          start   =   marginPart;
129                      }
130                     
131                      //   按照RadioButton的间距等距设定其左边距
132                      rb . Left   =   start   -   rb . Width;
133                      break ;
134                  case   DockStyle . Left :
135                  case   DockStyle . Right :
136                      //   对于靠左、靠右两种种锚定方式,   采用纵向摆放RadioButton控件
137   
138                      //   设定RadioButton的左边距为面板宽度的四分之一
139                      rb . Left   =   this . panel . ClientSize . Width   /   4 ;
140                      if   ( this . panel . Dock   = =   DockStyle . Right )   {
141                          //   如果面板靠右锚定,   设置RadioButton的左边距为面板的四分之三
142                          rb . Left   =   this . panel . ClientSize . Width   -   rb . Width;
143                      }
144   
145                      //   计算每个RadioButton控件的间距
146                      if   ( marginPart   = =   0 )   {
147                          marginPart   =   this . ClientSize . Height   /   this . radioButtons . Length;
148                      }
149                      //   按照RadioButton的高度垂直等间距设置上边距
150                      rb . Top   =   start   +   rb . Height;
151                      break ;
152                  }
153                  start   + =   marginPart;
154              }
155          }
156      }
157   
158   
159      static   class   Program   {
160          static   void   Main ( )   {
161              Application . EnableVisualStyles ( ) ;
162              Application . SetCompatibleTextRenderingDefault ( false ) ;
163              Application . Run ( new   MyForm ( ) ) ;
164          }
165      }
166  }

本节代码下载

可以看到,在主窗体上,我们放置了一个面板(Panel),面板上有一个按钮(Button)和五个单项按钮(RadioButton),第 40-77行代码是Form类构造器,实例化并创建了上述控件,并将它们都加入各自的容器(这里将Panel的容器设置为from)。对于面板,我们绑定 了它的Resize事件,当面板尺寸发生改变时引发,执行OnPanelResized方法(第104-156行);对于按钮,我们绑定了它的Click事件,当按钮点击时引发,执行OnButtonClick 方法(第83-99行)。

OnButtonClick 方 法中,重新定义了面板的尺寸,然后将面板的Dock属性切换为另一个值。所以我们可以看到,一旦点击按钮,面板的位置就会发生变化,分别会位于Form容 器的上(值为1)、下(值为2)、左(值为3)、右(值为4)、中(值为5)几个位置,而面板的Dock属性为None(值为0)时,面板绝对定位位置, 不在按照锚定位置布局。如下图:

Dock布局运行效果图 Dock布局运行效果图 Dock布局运行效果图
Dock布局运行效果图 Dock布局运行效果图 Dock布局运行效果图

图3 锚定方位示意图

可以看到,当Dock属性为DockStyle.Top或DocStyle.Bottom时,只能设置控件的Height 属 性,其它位置和尺寸属性均无效;当设置为DockType.Left或DocType.Right时,只能设置控件的Width属性,其它属性无效。当控 件的Dock属性为DockType.None时,则控件的位置按照其Top,Left和Location属性定位,控件的尺寸按照其 Width,Height和Size属性来设定。

在OnPanelResized 方法中,由于按钮和单选按钮都没有设定 其Dock属性(即Dock属性为DockType.None),所以我们采用绝对定位,通过一个简单的算法,根据面板当前的的锚定方式,计算按钮和五个 单选按钮的位置。如果是面板按照DockType.Top, DockType.Buttom, DockType.Fill布局,则单选按钮按照横向平均间距布局,否则按照纵向平均间距布局,对于DockType.None不作处理。

通过上面的比较可以看出,绝对布局存在的问题是:除非不允许容器改变大小,否则容器一旦改变尺寸,将会破坏布局结构,而相对布局则不存在这个问题。

 

from http://www.ej38.com/showinfo/csharp-190971.html

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐