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

这是 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 设置为“占满”且“填充”,那么画风会是下面这样:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/312921.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java开发Web Service的几种解决方案

转自&#xff1a;http://blog.csdn.net/zolalad/article/details/25158995 Java开发中经常使用到的几种WebService技术实现方案 随着异构系统互联需求的不断增加&#xff0c;WebService的重要性也日益彰显出来。凭借webservice&#xff0c;我们可以实现基于不同程序语言的项目的…

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

本文是【.NET Core 跨平台 GUI 开发】系列博文的第一篇。该系列博文是一个关于 Gtk# 跨平台应用开发的初级随笔集合。该随笔集合介绍了 GTK 和 Gtk# 的基本信息以及开发方法&#xff0c;并展示了如何使用 .NET Core 技术栈开发基于 Gtk# 的跨平台 GUI 程序。博文假设你已经对 C…

ASP.NET Core快速入门(第4章:ASP.NET Core HTTP介绍)--学习笔记

点击蓝字关注我们课程链接&#xff1a;http://video.jessetalk.cn/course/explore良心课程&#xff0c;大家一起来学习哈&#xff01;任务22&#xff1a;课程介绍1.HTTP 处理过程2.WebHost 的配置与启动3.Middleware 与管道4.Routing MiddleWare 介绍任务23&#xff1a;Http请求…

Java使用JWS API开发Web Service

JAX-WS&#xff0c;即Java API for XML Web Service&#xff0c;是Java开发基于SOAP协议的Web Service的标准。使用JWS API就可以直接开发简单的Web Service应用。 一、创建Web Service 打开Eclipse&#xff0c;新建一个Java Project&#xff0c;如下图所示&#xff1a; 新建了…

ASP.NET Core快速入门(第3章:依赖注入)--学习笔记

点击蓝字关注我们课程链接&#xff1a;http://video.jessetalk.cn/course/explore良心课程&#xff0c;大家一起来学习哈&#xff01;任务16&#xff1a;介绍1、依赖注入概念详解从UML和软件建模来理解从单元测试来理解2、ASP.NET Core 源码解析任务17&#xff1a;从UML角度来理…

使用wsimport命令创建Web Service客户端

一、wsimport简介 在jdk的bin文件夹中&#xff0c;有一个wsimport.exe工具。这个工具可以依据Web Service的描述文件wsdl生成相应的类文件&#xff0c;然后用这些类文件&#xff0c;被Web Service的客户端导入之后&#xff0c;就可以像调用本地的类一样调用WebService提供的方法…

读《持续交付2.0》

几年前看过《持续交付(发布可靠软件的系统方法)》&#xff0c;感触不是很深&#xff0c;最近看了这本书的译者乔梁编写的《持续交付2.0》&#xff0c;结合工作中的种种&#xff0c;又有一种相见恨晚的感觉。可见好书是需要经常翻阅的&#xff0c;每次都会带来新的收获和思考。全…

Java使用Apache CXF开发Web Service

转自&#xff1a;http://blog.csdn.net/hu_shengyang/article/details/38384597 以前工作中也用CXF,但都是用别人现成搭好的环境&#xff0c;这次自己重头搭建一遍环境。过程中也有遇到的问题&#xff0c;也做了简单的整理。 对于CXF是干什么用的&#xff0c;我不想多说&#x…

程序员修神之路--kubernetes是微服务发展的必然产物

菜菜哥&#xff0c;我昨天又请假出去面试了战况如何呀&#xff1f;多数面试题回答的还行&#xff0c;但是最后让我介绍微服务和kubernetes的时候&#xff0c;挂了话说微服务和kubernetes内容确实挺多的那你给我大体介绍一下呗可以呀&#xff0c;不过要请和coffee哦◆◆kubernet…

.NET core3.0 使用Jwt保护api

摘要&#xff1a;本文演示如何向有效用户提供jwt&#xff0c;以及如何在webapi中使用该token通过JwtBearerMiddleware中间件对用户进行身份认证。认证和授权区别&#xff1f;首先我们要弄清楚认证&#xff08;Authentication&#xff09;和授权&#xff08;Authorization&#…

Java ArrayList的实现原理详解

ArrayList是Java List类型的集合类中最常使用的&#xff0c;本文基于Java1.8&#xff0c;对于ArrayList的实现原理做一下详细讲解。 &#xff08;Java1.8源码&#xff1a;http://docs.oracle.com/javase/8/docs/api/&#xff09; 一、ArrayList实现原理总结 ArrayList的实现原…

.NET开发者的机遇与Web Blazor基础(有彩蛋)

一.唠唠WebAssembly的发展历程目前有很多支持WebAssembly的项目&#xff0c;但发展最快的是Blazor&#xff0c;这是一个构建单页面的.NET技术&#xff0c;目前已经从Preview版本升级到了beta版本&#xff0c;微软计划在2020年5月发布Blazor的第一个版本。Blazor是什么&#xff…

Java LinkedList的实现原理详解

LinkedList是Java List类型的集合类的一种实现&#xff0c;此外&#xff0c;LinkedList还实现了Deque接口。本文基于Java1.8&#xff0c;对于LinkedList的实现原理做一下详细讲解。 &#xff08;Java1.8源码&#xff1a;http://docs.oracle.com/javase/8/docs/api/&#xff09…

知乎高赞:中国有哪些不错的开源软件产品?

点击蓝字“dotNET匠人”关注我哟加个“星标★”&#xff0c;每日 7:15&#xff0c;好文必达&#xff01;在知乎上&#xff0c;有个问题问“中国有什么拿得出手的开源软件产品&#xff08;在 GitHub 等社区受欢迎度较好的&#xff09;&#xff1f;”事实上&#xff0c;还不少呢~…

容器日志管理 (2) 开源日志管理方案 ELK/EFK

本篇已加入《.NET Core on K8S学习实践系列文章索引》&#xff0c;可以点击查看更多容器化技术相关系列文章。上一篇《容器日志管理&#xff08;1&#xff09;》中介绍了Docker自带的logs子命令以及其Logging driver&#xff0c;本篇将会介绍一个流行的开源日志管理方案ELK/EFK…

关于Scrum起源,读这一篇论文就足够啦!《新新产品开发游戏》

关于Scrum的起源&#xff0c;我们经常会提到1986年发表在HBR上的一篇论文&#xff0c;《The New New Product Development Game》&#xff0c;今天我们把它重新翻译&#xff0c;一起重温为何Scrum会如此设置3355&#xff1f;为何会用橄榄球的术语来代表Scrum&#xff1f;The Ne…

Java HashMap的实现原理详解

HashMap是Java Map类型的集合类中最常使用的&#xff0c;本文基于Java1.8&#xff0c;对于HashMap的实现原理做一下详细讲解。 &#xff08;Java1.8源码&#xff1a;http://docs.oracle.com/javase/8/docs/api/&#xff09; 一、HashMap实现原理总结 HashMap的实现原理总结如下…

ASP.NET Core快速入门(第5章:认证与授权)--学习笔记

点击蓝字关注我们课程链接&#xff1a;http://video.jessetalk.cn/course/explore良心课程&#xff0c;大家一起来学习哈&#xff01;任务31&#xff1a;课时介绍1.Cookie-based认证与授权2.Cookie-based认证实现3.Jwt认证与授权介绍4.Jwt认证与授权实现5.Jwt认证与授权6.Role …

Java HashSet的实现原理详解

HashSet是Java Map类型的集合类中最常使用的&#xff0c;本文基于Java1.8&#xff0c;对于HashSet的实现原理做一下详细讲解。 &#xff08;Java1.8源码&#xff1a;http://docs.oracle.com/javase/8/docs/api/&#xff09; 一、HashSet实现原理总结 HashSet的实现原理总结如下…

asp.net mvc 自定义 pager 封装与优化

asp.net mvc 自定义 pager 封装与优化Intro之前做了一个通用的分页组件&#xff0c;但是有些不足&#xff0c;从翻页事件和分页样式都融合在后台代码中&#xff0c;到翻页事件可以自定义&#xff0c;再到翻页和样式都和代码分离&#xff0c; 自定义分页 pager 越来越容易扩展了…