图形界面编程(五) 布局容器类(4)

桃扇骨 2022-07-14 23:53 248阅读 0赞

点击打开链接

6 分隔容器类

分隔容器(SplitContainer)由分隔线和两个面板容器组成,可以通过分隔线将容器所在区域分为两个部分(左右或上下),每个部分里面有一个面板容器(Panel容器类),可以放置其它控件。

通过鼠标拖动分隔线,可以改变容器两个区域的尺寸。

分隔线可以通过SplitterWidth 属性控制,例如:

[c-sharp] view plain copy

  1. // 设定分隔线宽度为2单位
  2. this.splitPane.SplitterWidth = 2;


这里的splitPane是一个SplitContainer类型字段,表示一个分隔容器。上述代码将其分隔线宽度设置为2个单位。

SplitContainer容器的两个面板分别为Panel1属性Panel2属性,也成为1号面板和2号面板。正常情况下,1号面板在左边(分隔线垂直)或者1号面板在上方(分隔线水平),参看示意图:








垂直分隔示意图 水平分割示意图

图1 分隔容器分隔方向示意图

通过SplitContainer类对象的Orientation属性可以设置分隔容器分隔线的方向,例如:

[c-sharp] view plain copy

  1. // 设置分隔面板容器分隔线方向为垂直方向
  2. this.splitPane.Orientation = Orientation.Vertical;


通过SplitContainer类对象Panel1(或者Panel2)属性得到一个Panel类型对象引用,通过其Controls属性的Add方法可以将其它控件增加在这两个面板之一上。从而将控件放置在SplitContainer容器的左边(上边)或右边(下边),例如:

[c-sharp] view plain copy

  1. // 将标签控件加入splitPane容器1号面板容器内
  2. this.splitPane.Panel1.Controls.Add(this.textLabel);


这里的textLabel是一个Label类型字段,表示任意控件。

通过SplitContainer对象的SplitterDistance属性可以控制分隔线和其起始位置的距离(对于分隔线垂直,起始位置在容器最左边;对于分隔线水平,起始位置在容器最顶端),例如:

[c-sharp] view plain copy

  1. this.splitPane.SplitterDistance = 120;


上述代码设置分隔线距离起始位置120个单位。

通过SplitContainer对象的Panel1MinSize属性可以控制1号面板的最小宽度(对于分隔线垂直)或高度(对于分隔线水平),即拖动分隔线可以使1号面板成为德最小尺寸;同理Panel2MinSize用于设置2号面板,例如:

[c-sharp] view plain copy

  1. this.splitPane.Panel1MinSize = 50;


设置1号面板最小尺寸(宽度或高度,根据分隔线方向决定)为50个单位。

通过SplitContainer对象的Panel1Collapsed属性,可以将1号面板收起(属性值为true)或展开(属性值为false);同理,Panel2Collapsed属性用于2号面板,例如:

[c-sharp] view plain copy

  1. this.splitPane.Panel1Collapsed = !this.splitPane.Panel1Collapsed;


将1号面板收起或展开(根据面板的当前状态决定)。

一般来说,分隔容器用于显示侧边栏底边栏,就像我们使用的Visual Studio一样。在不需要的时候收起,并可以随时调整尺寸。

下面通过一个例子来展示SplitContainer的具体使用:

界面布局如下:

界面布局示意图
图3 界面布局示意图

为了让两个按钮放置的更为合理,使用了一个流式布局面板(flowPane字段),将按钮放在流式面板上,再把流式面板放在分隔容器的2号面板中。

代码如下:

Program.cs

[c-sharp] view plain copy

  1. using System;
  2. using System.Windows.Forms;
  3. namespace Edu.Study.Graphics.SplitLayout {
  4. ///
  5. /// SplitContainer测试窗体
  6. ///
  7. class MyForm : Form {
  8. // 分隔面板容器
  9. private SplitContainer splitPane;
  10. // 放置在分隔面板2号面板内的流式面板容器
  11. private FlowLayoutPanel flowPane;
  12. // 放置在分隔面板1号面板的标签控件
  13. private Label textLabel;
  14. /***** 放置在分隔面板2号面板的按钮 *****/
  15. // 用于切换分隔面板分隔方向的按钮
  16. private Button switchButton;
  17. // 用于合起分隔面板左边部分的按钮
  18. private Button collapsedButton;
  19. // 定时器组件
  20. private Timer colapsedTimer;
  21. // 分隔面板左边部分是否被合起的标志
  22. private bool isColapsed = false;
  23. ///
  24. /// 构造器
  25. ///
  26. public MyForm() {
  27. this.Text = “分隔容器测试”;
  28. /***** 初始splitPane化分隔面板容器 *****/
  29. this.splitPane = new SplitContainer();
  30. // 设置分隔面板容器分隔线方向为垂直方向
  31. this.splitPane.Orientation = Orientation.Vertical;
  32. // 设置分隔面板容器锚定在父容器中央
  33. this.splitPane.Dock = DockStyle.Fill;
  34. // 设定分隔线宽度为2单位
  35. this.splitPane.SplitterWidth = 2;
  36. // 设置分隔面板容器边框样式为实线边框, 这样分隔线可以看得更清楚
  37. this.splitPane.BorderStyle = BorderStyle.FixedSingle;
  38. /***** 初始化textLabel标签控件 *****/
  39. this.textLabel = new Label();
  40. // 设定标签控件内显示文本
  41. this.textLabel.Text = “分隔面板测试”;
  42. // 设定标签控件根据内容自动调整尺寸
  43. this.textLabel.AutoSize = true;
  44. // 将标签控件加入splitPane容器1号面板容器内
  45. this.splitPane.Panel1.Controls.Add(this.textLabel);
  46. /***** 初始化flowPane容器 *****/
  47. this.flowPane = new FlowLayoutPanel();
  48. // 设置流式面板容器布局方向为上下方向
  49. this.flowPane.FlowDirection = FlowDirection.TopDown;
  50. // 禁止容器内控件自动换行
  51. this.flowPane.WrapContents = false;
  52. // 禁止容器自动出现滚动条
  53. this.flowPane.AutoScroll = false;
  54. // flowPane容器锚定在父容器左边位置
  55. this.flowPane.Dock = DockStyle.Left;
  56. // flowPane容器加入splitPane容器2号面板容器内
  57. this.splitPane.Panel2.Controls.Add(this.flowPane);
  58. /***** 初始化switchButton按钮控件 *****/
  59. this.switchButton = new Button();
  60. // 设置按钮文本
  61. this.switchButton.Text = “切换分隔方向”;
  62. // 设置按钮自动调整尺寸
  63. this.switchButton.AutoSize = true;
  64. // 向按钮增加事件委托
  65. this.switchButton.Click += new EventHandler(SwitchButtonClick);
  66. // 设置按钮四周的空白
  67. this.switchButton.Margin = new Padding(0, 3, 3, 3);
  68. // 设置按钮的样式, 弹出式按钮
  69. this.switchButton.FlatStyle = FlatStyle.Popup;
  70. // 按钮增加在flowPane容器内
  71. this.flowPane.Controls.Add(this.switchButton);
  72. /***** 初始化collapsedButton按钮控件, 同上 *****/
  73. this.collapsedButton = new Button();
  74. this.collapsedButton.Text = “收起左边栏”;
  75. this.collapsedButton.AutoSize = true;
  76. this.collapsedButton.Click += new EventHandler(CollapsedButtonClicked);
  77. this.collapsedButton.Margin = new Padding(0, 3, 3, 3);
  78. this.collapsedButton.FlatStyle = FlatStyle.Popup;
  79. this.flowPane.Controls.Add(this.collapsedButton);
  80. /***** 初始化colapsedTimer定时器控件 *****/
  81. this.colapsedTimer = new Timer();
  82. // 定时器激发时间间隔20毫秒
  83. this.colapsedTimer.Interval = 20;
  84. // 设置事件委托, 定时器到达激发时间执行的方法
  85. this.colapsedTimer.Tick += new EventHandler(ColapsedTimerTick);
  86. // splitPane容器增加在主窗体上
  87. this.Controls.Add(this.splitPane);
  88. }
  89. ///
  90. /// 获取splitPane容器1号面板最小尺寸
  91. ///
  92. ///
  93. private int GetPanel1MiniSize() {
  94. int miniDistance = 0;
  95. // 根据splitPane容器的方向, 获取窗体高度或宽度的1/4作为1号面板尺寸
  96. if (this.splitPane.Orientation == Orientation.Vertical) {
  97. miniDistance = this.Width / 4;
  98. } else {
  99. miniDistance = this.Height / 4;
  100. }
  101. return miniDistance;
  102. }
  103. ///
  104. /// 将splitPane容器1号面板固定在某个最小尺寸上
  105. ///
  106. private void ChangeSpliterDistanceAndPanel1MiniSize() {
  107. // 设置SplitterDistance属性和Panel1MinSize属性,
  108. // 将分隔栏距离和1号面板最小尺寸设置为GetPanel1MiniSize方法返回值
  109. this.splitPane.SplitterDistance =
  110. this.splitPane.Panel1MinSize = this.GetPanel1MiniSize();
  111. }
  112. ///
  113. /// 窗体加载事件
  114. ///
  115. protected override void OnLoad(EventArgs e) {
  116. base.OnLoad(e);
  117. // 设置splitPane容器1号面板最小宽度
  118. this.ChangeSpliterDistanceAndPanel1MiniSize();
  119. // 重新计算flowPane容器的宽度
  120. // 由于flowPane中具有两个按钮, 所以设置flowPane宽度为两个按钮
  121. // 各自占据横向空间的最大值.
  122. this.flowPane.Width = Math.Max(
  123. this.switchButton.Width + this.switchButton.Margin.Horizontal,
  124. this.collapsedButton.Width + this.collapsedButton.Margin.Horizontal
  125. );
  126. }
  127. ///
  128. /// 窗体尺寸改变事件
  129. ///
  130. protected override void OnResize(EventArgs e) {
  131. base.OnResize(e);
  132. // 窗体改变尺寸时重新设置splitPane容器1号面板最小宽度
  133. this.ChangeSpliterDistanceAndPanel1MiniSize();
  134. }
  135. ///
  136. /// switchButton按钮控件点击事件
  137. /// 转换splitPane容器的分隔方向
  138. ///
  139. private void SwitchButtonClick(object sender, EventArgs e) {
  140. if (this.splitPane.Orientation == Orientation.Vertical) {
  141. // 如果splitPane容器分隔线原本为垂直方向, 则改为水平方向
  142. this.splitPane.Orientation = Orientation.Horizontal;
  143. // 指定flowPane容器锚定为锚地在父容器顶端
  144. // splitPane容器垂直分隔后, 分隔线呈水平处于splitPane容器2号面板的顶部
  145. this.flowPane.Dock = DockStyle.Top;
  146. // 更改flowPane的布局方向为水平布局
  147. // 此时flowPane中的两个按钮变为横向排列
  148. this.flowPane.FlowDirection = FlowDirection.LeftToRight;
  149. // 重新设置两个按钮周围的空白
  150. this.collapsedButton.Margin =
  151. this.switchButton.Margin = new Padding(3, 0, 3, 3);
  152. } else {
  153. // 如果splitPane容器分隔线原本水平方向, 则改为垂直方向
  154. this.splitPane.Orientation = Orientation.Vertical;
  155. // 指定flowPane容器锚定为锚地在父容器左边
  156. // splitPane容器水平分隔后, 分隔线呈垂直处于splitPane容器2号面板的左侧
  157. this.flowPane.Dock = DockStyle.Left;
  158. // 更改flowPane的布局方向为垂直布局
  159. // 此时flowPane中的两个按钮变为纵向排列
  160. this.flowPane.FlowDirection = FlowDirection.TopDown;
  161. // 重新设置两个按钮周围的空白
  162. this.collapsedButton.Margin =
  163. this.switchButton.Margin = new Padding(0, 3, 3, 3);
  164. }
  165. // 重新设置splitPane容器1号面板最小宽度
  166. this.ChangeSpliterDistanceAndPanel1MiniSize();
  167. }
  168. ///
  169. /// “收起左边栏”按钮点击事件
  170. ///
  171. private void CollapsedButtonClicked(object sender, EventArgs e) {
  172. // 显示一个消息框, 具有”是”和”否”两个按钮.
  173. // 返回DialogResult枚举值
  174. DialogResult dr = MessageBox.Show(
  175. “是否显示动画方式?”, // 消息框显示内容
  176. “提问”, // 消息框标题
  177. MessageBoxButtons.YesNo, // 消息框显示的按钮类型
  178. MessageBoxIcon.Question, // 消息框图标类型
  179. MessageBoxDefaultButton.Button1 // 消息框默认按钮(可以用回车键激活)
  180. );
  181. if (dr == DialogResult.Yes) { // 选择了”是”按钮
  182. if (this.isColapsed) {
  183. // 如果splitPane容器的1号面板已经收起
  184. // 则设置splitPane容器分隔线距离起始位置距离为0
  185. this.splitPane.SplitterDistance = 0;
  186. // 设置splitPane容器1号面板不再收起
  187. // 由于此时splitPane容器的SplitterDistance属性值为0
  188. // 所以1号面板也并没有显示出来
  189. this.splitPane.Panel1Collapsed = false;
  190. } else {
  191. // 如果splitPane容器的1号面板已经展开
  192. // 则设置1号面板的最小尺寸为0, 使其可以改变尺寸
  193. this.splitPane.Panel1MinSize = 0;
  194. }
  195. // 启动定时器
  196. this.colapsedTimer.Start();
  197. } else { // 选择了”否”按钮, 表示无需动画效果
  198. if (this.splitPane.Panel1MinSize == 0) {
  199. // 如果splitPane容器的1号面板最小尺寸为0,
  200. // 表示它通过动画方式收起过, 这里将最小尺寸恢复正常值
  201. this.splitPane.Panel1MinSize = this.GetPanel1MiniSize();
  202. }
  203. // 改变1号面板的收起状态
  204. this.splitPane.Panel1Collapsed = !this.splitPane.Panel1Collapsed;
  205. // 根据1号面板的收起状态设置isColapsed标志
  206. this.isColapsed = this.splitPane.Panel1Collapsed;
  207. }
  208. }
  209. ///
  210. /// 定时器到到达事件处理方法
  211. ///
  212. private void ColapsedTimerTick(object sender, EventArgs e) {
  213. if (this.isColapsed) { // 如果面板之前是合上的
  214. // 计算1号面板最小尺寸
  215. int miniSize = this.GetPanel1MiniSize();
  216. if (this.splitPane.SplitterDistance < miniSize) {
  217. /***** 如果分隔线还未到达指定位置, 则将分隔线距离增加最多30个单位 *****/
  218. // 求分隔线移动的最大距离
  219. miniSize = Math.Min(miniSize - this.splitPane.SplitterDistance, 30);
  220. // 设置分隔线与起始位置的距离
  221. this.splitPane.SplitterDistance += miniSize;
  222. } else {
  223. // 如果分隔线距离起始位置已经到达要求距离
  224. // 停止定时器
  225. this.colapsedTimer.Stop();
  226. // 设定1号面板最小尺寸
  227. this.splitPane.Panel1MinSize = miniSize;
  228. // 设置isColapsed字段, 表示面板已经展开
  229. this.isColapsed = false;
  230. }
  231. } else {
  232. /***** 如果分隔线还未到达指定位置, 则将分隔线距离减少最多30个单位 *****/
  233. if (this.splitPane.SplitterDistance > 0) {
  234. // 将分隔线距离起始位置距离减少最多30个单位
  235. this.splitPane.SplitterDistance -= Math.Min(this.splitPane.SplitterDistance, 30);
  236. } else {
  237. // 如果分隔线距离起始位置已经到达要求距离
  238. // 停止定时器
  239. this.colapsedTimer.Stop();
  240. // 设定面板1完全收起
  241. this.splitPane.Panel1Collapsed = true;
  242. // 设置isColapsed字段, 表示面板已经收起
  243. this.isColapsed = true;
  244. }
  245. }
  246. }
  247. }
  248. ///
  249. /// 主方法类
  250. ///
  251. static class Program {
  252. ///
  253. /// 应用程序的主入口点。
  254. ///
  255. static void Main() {
  256. Application.EnableVisualStyles();
  257. Application.SetCompatibleTextRenderingDefault(false);
  258. Application.Run(new MyForm());
  259. }
  260. }
  261. }

本节代码下载

在代码中,通过“切换分隔方向”按钮来切换分隔线的方向(164-200行),切换分隔线方向很简单,设置其Orientation属性即可,但要注意,切换分隔线方向后,要对两个面板内控件的布局方向做必要的调整,因为两个面板的放置方向改变了;

代码中使用了两种方式来收起面板,普通方式和动画方式,普通方式很简单,直接设置Panel1Collapsed(或Panel2Collapsed)属性即可(第243行);动画方式是通过一个定时器控件(即定时呼叫事件委托方法的控件)不断定时改变面板的SplitterDistance属性(收起为减小属性值,展开为增加属性值),直到SplitterDistance属性值达到预定大小(253-288行);注意,SplitterDistance属性不能为负数,代码261行和277行保证了这一点。

定时器在固定时间间隔后呼叫某个委托函数一次,并一直重复直到定时器被关闭。其Interval属性用于设定时间间隔(即多久呼叫委托函数一次,第99行),Tick事件用于指定呼叫的委托函数(第100行),使用Start方法可以启动定时器(第234行),Stop方法可以停止(也可以说是关闭)定时器(第267行)。定时器必须可以在适当的时候被关闭,本例中,如果分隔线达到指定距离后,定时器被关闭。

发表评论

表情:
评论列表 (有 0 条评论,248人围观)

还没有评论,来说两句吧...

相关阅读