这是 Gtk# 系列博文的第二篇。在上一篇博文《编写你的第一个 Gtk# 应用》中,我们提到“一个 Gtk.Window 只能直接包含一个部件”。这意味着,在不做其他额外操作的情况下,如果你向一个 GtkWindow 中添加了一个 GtkLabel (就像上一篇博文中的 Hello World一样)那么你将不能再添加一个按钮进去。

如过你尝试这么做,你会发现按钮并不会显示在窗体上,同时在控制台会输出一个警告:尝试将一个 GtkButton 类型的部件添加到 GtkWindow 中,但是 GtkWindow 作为 GtkBin 的子类型每次只能容纳一个部件,它现在已经容纳了一个 GtkLabel 类型的部件。

Attempting to add a widget with type GtkButton to a GtkWindow, but as a GtkBin subclass a GtkWindow can only contain one widget at a time; it already contains a widget of type GtkLabel 。

如果我们的 GUI 程序只能显示一个部件,那么就太尴尬了,只能包含一个部件的窗口也没有意义。其实我们有很多现成的部件可以解决这个尴尬的问题,常见的比如:VBox、HBox 和 Table 。

在 WinForm 开发中,我们使用坐标来定位控件。在 Gtk# 中当然也支持使用这种方式布局控件,但是首选的方式还是使用盒子(Box)。盒子是不可见的容器部件,他们有两种形式:水平(HBox)和垂直(VBox)。你可以把所有的部件(Widget)想象成一个又一个的盒子,然后他们整齐的排列好,塞满一个更大的盒子,而这个更大的盒子外面也可以有盒子。这种布局方式与屏幕无关且能更好的支持国际化。

1、准备项目

和上一篇博文类似,创建一个名为“Gtk.Layouts”的 .NET Core 控制台应用程序并引入 GtkSharp 组件后,在 Program.cs 文件中键入以下代码:

using System;namespace Gtk.Layouts{    class Program    {        static void Main(string[] args)        {            Application.Init();            var win = new Window("Gtk.Layouts");            win.SetDefaultSize(300, 300);            win.WindowPosition = WindowPosition.Center;            win.DeleteEvent += (s, e) =>            {                Application.Quit();            };            win.ShowAll();            Application.Run();        }    }}

以上代码在运行后会在屏幕中心展示一个标题为“Gtk.Layouts”大小是 300* 300 的空白 GtkWindow 。

2、HBox 和 VBox

HBox 容器中所有的控件都会水平排列在一行上,现在把 HBox 添加到窗体中,新建一个 InitializeWindow 方法,把窗体的初始化代码放到这个方法里:

using System;namespace Gtk.Layouts{    class Program    {        static void Main(string[] args)        {            Application.Init();            var win = new Window("Gtk.Layouts");            win.SetDefaultSize(300, 300);            win.WindowPosition = WindowPosition.Center;            win.DeleteEvent += (s, e) =>            {                Application.Quit();            };            InitializeWindow(win);            win.ShowAll();            Application.Run();        }        private static void InitializeWindow(Window window)        {            var hBox = new HBox();            window.Add(hBox);        }    }} 

程序运行起来之后,窗体似乎没什么变化?别紧张,前面说过盒子是不可见的容器,并不会被直接展示出来,现在添加一个按钮进去看看:

        private static void InitializeWindow(Window window)        {            var hBox = new HBox();            window.Add(hBox);            hBox.Add(new Button("我是按钮"));        } 

加入按钮以后:

按钮被展示了出来,而且充满了整个窗体。目前的效果和直接在 GtkWindows 下新增 GtkButton 差不多,再多加几个按钮看看:

        private static void InitializeWindow(Window window)        {            var hBox = new HBox();            window.Add(hBox);            for (int i = 0; i < 4; i++)            {                hBox.Add(new Button($"我是按钮{i + 1}号"));            }        }

我们使用 for 循环添加的 4 个按钮被整齐的展示在了窗体上,每个按钮的宽度和高度都相同,而且整个 GtkWindow 的宽度变长了。回想一下上面提到过的:整齐和塞满。GtkWindow 可以根据其内部控件的需要拓展自身的大小,最大宽度不能超过 32767 像素。如果尝试调整窗体的大小会怎么样?我帮你尝试了一下,调整到最小尺寸时是这个模样:

从 Widget 中派生的 Button 继承了 Widget 的 SetSizeRequest 方法,这个方法可以设置部件所需的布局大小。在实际实践中,通常也不会使用 HBox 的 Add 方法来添加子部件,而是采用功能更加强大的 PackStart 或 PackEnd 方法。与 Add 方法相比,这两个方法提供了更丰富的控制。

PackStart:

public void PackStart(Widget child, bool expand, bool fill, uint padding)

PackEnd:

public void PackEnd(Widget child, bool expand, bool fill, uint padding)

改动一下代码,看看会发生什么事情:

        private static void InitializeWindow(Window window)        {            var hBox = new HBox();            window.Add(hBox);            for (int i = 0; i < 4; i++)            {                var btn = new Button($"{i + 1}");                btn.SetSizeRequest(30 * (i + 1), 50);                hBox.PackStart(btn, false, false, 3);            }        }

以上代码设置了按钮的大小,并且规定了内边距:

可以看到按钮的宽度设置 padding 都生效了,按钮变得宽窄不一且与容器和临近元素拉开了距离。那么 PackStart 和 PackEnd 有什么区别呢?为了可以更清楚的解释这个问题,需要引入 VBox 。VBox 和 HBox 相似,两者最大的区别就是 VBox 的子部件是从上到下排列的,HBox 是从左到右。

试试使用 VBox 把窗体分为上下两个部分,并对比一下 PackStar 和 PackEnd 有什么区别:

        private static void InitializeWindow(Window window)        {            var vBox = new VBox();            window.Add(vBox);            var hBox1 = new HBox();            var hBox2 = new HBox();            vBox.PackStart(hBox1, false, false, 5);            vBox.PackStart(hBox2, false, false, 5);            for (int i = 0; i < 4; i++)            {                var btn = new Button($"{i + 1}");                btn.SetSizeRequest(30 * (i + 1), 50);                hBox1.PackStart(btn, false, false, 3);            }            for (int i = 0; i < 4; i++)            {                var btn = new Button($"{i + 1}");                btn.SetSizeRequest(30 * (i + 1), 50);                hBox2.PackEnd(btn, false, false, 3);            }        }

以上的代码创建了一个 VBox 并添加到了 Window 中,之后新建了两个 HBox 分别命名为 hBox1 和 hBox2 并添加到了 VBox 中,这个很像我们在组装鞋架,现在,这个鞋架现在有两层。两个 for 循环分别向 hBox1 和 hBox2 中添加了 4 个按钮,按钮添加的顺序和文字均相同,唯一的区别是分别调用了 PackStart 和 PackEnd 两种不同的方法:

程序运行后可以看到,第一行按钮的显示顺序和第二行是相反的。

如何理解这个事情呢?想象一下你现在要把鞋子放在鞋架的其中一层上,并且你只从剩余空间的左边或者右边开始摆放鞋子,那么 PackStart 相当于把这个鞋子摆在了剩余空间的最左边,而 PackEnd 则相当于摆放在最右边。VBox 中对应的方法也类似,只是将方向换成了上和下,验证一下看看:

        private static void InitializeWindow(Window window)        {            var vBox = new VBox();            window.Add(vBox);            var hBox1 = new HBox();            var hBox2 = new HBox();            vBox.PackStart(hBox1, false, false, 5);            vBox.PackStart(hBox2, false, false, 5);            for (int i = 0; i < 4; i++)            {                var btn = new Button($"{i + 1}");                btn.SetSizeRequest(30 * (i + 1), 50);                hBox1.PackStart(btn, false, false, 3);            }            for (int i = 0; i < 4; i++)            {                var btn = new Button($"{i + 1}");                btn.SetSizeRequest(30 * (i + 1), 50);                hBox2.PackEnd(btn, false, false, 3);            }            var btnBtm = new Button("Bottom");            btnBtm.SetSizeRequest(50, 50);            vBox.PackEnd(btnBtm,false,false,5);        }

以上代码新创建了一个名为 btnBtm 的按钮,并通过 PackEnd 方法将其添加到了 VBox 中:

可以看到,名为 btnBtm 的按钮确实被放在了剩余空间的底部。

3、expand 和 fill 参数

PackStart 和 PackEnd 方法还有两个参数,分别为 expand 和 fill 。

expand 就是当 Box 给我们的 Widget 分配了额外的空间后,我们的 Widget 会占住这个空间,不会让给别人。

fill 就是当 expand 为 TRUE 的时候,我们不仅占用 Box 给我们分配的空间,而且会把自己的界面扩大到这个空间上。

所以,简单来说,expand = TRUE, fill = FALSE 就是占住空间但是控件本身大小不变;两个都是TRUE,就是不仅占住空间而且控件也会变得和这个空间一样大;expand = FALSE,fill就没了意义。

GtkHBox 中只要 expand 是TRUE,那么,水平方向上一定 fill,所以 fill 参数此时只影响垂直上是否 fill 。

GtkVBox 中只要 expand 是TRUE,那么,垂直方向上一定 fill,所以 fill 参数此时只影响水平上是否 fill 。

以上关于 expand 和 fill 参数的解释引用自:Super的博客 中的博文 GTK Box(hbox&vbox)的expand和fill两个属性的实践理解 。是否有些难以理解?言语都很苍白,幸好我们可以用代码说话。

现在,可以将窗体宽度调大一些,比如 500*300 :

win.SetDefaultSize(500, 300);

窗体变大后,我们的 HBox 中出现了空白:

目前代码中 expand 和 fill 参数均为 false 所以并不会出现“占满”和“填充”的现象。当 expand = false 时,fill 参数没有意义,那么我们就可以尝试下,当 expand = true 时,fill 分别为 true 和 false 时在 HBox 下会是什么效果:

        private static void InitializeWindow(Window window)        {            var vBox = new VBox();            window.Add(vBox);            var hBox1 = new HBox();            var hBox2 = new HBox();            vBox.PackStart(hBox1, false, false, 5);            vBox.PackStart(hBox2, false, false, 5);            for (int i = 0; i < 4; i++)            {                var btn = new Button($"{i + 1}");                btn.SetSizeRequest(30 * (i + 1), 50);                hBox1.PackStart(btn, true, true, 3);            }            for (int i = 0; i < 4; i++)            {                var btn = new Button($"{i + 1}");                btn.SetSizeRequest(30 * (i + 1), 50);                hBox2.PackEnd(btn, true, false, 3);            }            var btnBtm = new Button("Bottom");            btnBtm.SetSizeRequest(50, 50);            vBox.PackEnd(btnBtm, false, false, 5);        }

代码中将第一行的按钮设置为:沾满并填充,将第二行的按钮设置为:沾满不填充。

可以看到,“占满”并“填充”的按钮将所有的空白区域全部占领了,而不填充的按钮则会在分配给他的空白区域中居中。如果尝试将 btnBtm 设置为“占满”且“填充”,那么画风会是下面这样:

【.NET Core 跨平台 GUI 开发】第二篇:Gtk# 布局入门,初识HBox 和 VBox相关推荐

  1. 【.NET Core 跨平台 GUI 开发】第一篇:编写你的第一个 Gtk# 应用

    本文是[.NET Core 跨平台 GUI 开发]系列博文的第一篇.该系列博文是一个关于 Gtk# 跨平台应用开发的初级随笔集合.该随笔集合介绍了 GTK 和 Gtk# 的基本信息以及开发方法,并展示 ...

  2. core开发linux桌面应用,【.NET Core 跨平台 GUI 开发】第一篇:编写你的第一个 Gtk# 应用...

    本文是[.NET Core 跨平台 GUI 开发]系列博文的第一篇.该系列博文是一个关于 Gtk# 跨平台应用开发的初级随笔集合.该随笔集合介绍了 GTK 和 Gtk# 的基本信息以及开发方法,并展示 ...

  3. 【.NET Core 跨平台 GUI 开发】第三篇:Gtk# 表格布局与事件处理

    除了使用 HBox 和 VBox 进行布局外,还可以使用 Table 对象进行布局.这个有点像 html 的 table,适合方方正正大小差不多的空间集合.本篇将会对 Table 布局进行讲解,利用 ...

  4. Agv、Rgv 车辆控制调度系统开发第二篇

    系列文章目录 Agv.Rgv 车辆控制调度系统开发第二篇(理论片) 文章目录 系列文章目录 前言 一.什么是调度系统? 问题 二.问题分析 1.寻找路线 2.避碰算法 3.移车算法 4.解锁算法 总结 ...

  5. 跟着王进老师学开发Python篇:基础入门案例讲解-王进-专题视频课程

    跟着王进老师学开发Python篇:基础入门案例讲解-166人已学习 课程介绍         共计45个项目案例+项目源码,跟着王进老师尽情玩转Python解释器! 本课程涉及Python的基础语法, ...

  6. 视频教程-跟着王进老师学开发Python篇:基础入门案例讲解-Python

    跟着王进老师学开发Python篇:基础入门案例讲解 教学风格独特,以学员视角出发设计课程,难易适度,重点突出,架构清晰,将实战经验融合到教学中.讲授技术同时传递方法.得到广大学员的高度认可. 王进 ¥ ...

  7. .Net Core跨平台应用研究-HelloArm(串口篇)

    引言 为了验证采用dotnet core技术开发的物联网设备数据采集接入服务应用是否能在高性价比的linux嵌入式平台运行,针对dotnet core应用程序进行嵌入式linux环境的发布部署运行验证 ...

  8. android 串口开发第二篇:利用jni实现android和串口通信

    一:串口通信简介 由于串口开发涉及到jni,所以开发环境需要支持ndk开发,如果未配置ndk配置的朋友,或者对jni不熟悉的朋友,请查看上一篇文章,android 串口开发第一篇:搭建ndk开发环境以 ...

  9. 微信支付之JSAPI开发-第二篇:业务流程详解与方案设计

    微信支付流程 流程: 上图的网址为:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_4 如上图所示,微信网页支付的具体流程大致分为 ...

最新文章

  1. Hybird混合开发APP初学体验
  2. 查找数据挖掘的相关资料
  3. CLR自定义菜单项(ToolStripItem)
  4. Codeforces Round #448 (Div. 2)
  5. Management reporter 2012 与AX 2012
  6. 《四 spring源码》spring的事务注解@Transactional 原理分析
  7. 基于ATMEGA128的密码锁
  8. [Python从零到壹] 十七.可视化分析之Matplotlib、Pandas、Echarts入门万字详解
  9. 解决outlook 中邮件中,点击链接提示(您的组织策略阻止我们为您完成此操作)解决方案
  10. element环境插件
  11. samtools depth 用于外显子未覆盖区域的统计及统计未覆盖区域的意义
  12. 获取官方win10系统镜像方法
  13. 基于JAVA的网上图书商城参考【数据库设计、源码、开题报告】
  14. linux系统fasta程序,求助:如何用命令对fasta格式的序列按长短排序
  15. 分布式系统论文精读2:GFS
  16. 最全面的常用Delphi第三方控件汇总—报表、图表、界面、数据库等
  17. 任务调度 cron使用
  18. 无线自组网AODV路由机制仿真源码
  19. 用html简单做一个课程表。
  20. 用C#实现查询今天是什么节日的方法

热门文章

  1. C#委托,事件理解入门 (译稿)
  2. SharePoint 2010 中的BCS身份验证模式
  3. ipad和iphone切图_如何从iPhone和iPad上的Mail应用程序删除电子邮件帐户
  4. Ubuntu 12.10中的8个新功能,Quantal Quetzal
  5. 百度地图精确定位html,HTML5地理定位,百度地图API,知识点熟悉
  6. E-MapReduce上如何升级EMR-Core
  7. VMware Workstation与VMware vSphere的区别
  8. poj1189 简单dp
  9. 《JavaScript专家编程》——第1章 对象和原型 1.1鸟瞰JavaScript
  10. 网络工程师要如何选择?