C# 布局容器类
C# 布局
从ContainerControl类继承的子类作为容器窗体,可以容纳除Form类对象外的其余窗体对象。
在所有容器窗体内,最基本的就是顶级容器Form类以及面板容器Panel类。这两者的主要区别为:前者具有Windows标准框架(标题栏,最大 化、最小化和关闭按钮,窗体边框,可调整尺寸),并且可以独立存在;后者只是一块区域,并且必须依附在某个容器窗体上,无法独立存在。除了它们的区别外, 他们都具有:Controls属性,可以在上面放置控件;控件放置的位置需要通过控件的Top和Left属性或者控制。
1 绝对布局
对于容器类型的控件 (包括Form类型),出了Size,Bounds属性外,还有ClientSize 和ClientRectangle 属性,前者表示客户区尺寸,Size类型;后者表示客户区的矩形,是一个Rectangle类型。所谓客户区,就是容器实际可以使用的空间,对于Form类型来说,客户区就是除过标题栏,四周的边框外剩余的部分。
一般来说,直接在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 , |
} |
下面我们用一段代码来演示一下:
1 | using System; |
2 | using System . Drawing; |
3 | using System . Windows . Forms; |
4 | |
5 | namespace Edu . Study . Graphics . DockLayout { |
6 | |
7 | /// < summary > |
8 | /// 继承Form类, 定义窗体类 |
9 | /// < /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)时,面板绝对定位位置, 不在按照锚定位置布局。如下图:
图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
更多推荐
所有评论(0)