【Godot 4.2】Tree控件与TreeItem完全解析

概述

本篇是控件完全解析系列之一,主要总结一下Tree控件与TreeItem的使用。

Tree控件是一个非常强大的控件,尤其是在编写一些相关的程序或编辑器插件时,非常适合展示树形组织的节点型数据。

本篇将从简单的添加根节点,根节点子节点,设置文本和图标等等基础开始学习,尽量涵盖和串联TreeTreeItem所提供的API。

为Tree添加节点

这里的“节点”只是对TreeItem的一个通俗(但有歧义的)称呼,Tree控件的TreeItem并不算是一个真正意义上的Godot节点,行为方法也很节点不一样。因为它是直接继承自Object类型的,而不是Control,所以无法直接在场景面板中查看到,也无法直接用检视器面板添加和编辑。删除的时候也要使用free()方法。

为了尽量减少歧义,又符合通俗描述,我会在本文中尽量少的地方用“节点”的称呼,而是直接改用TreeItem

创建根节点

  • 我们可以使用Tree控件的create_item()方法创TreeItem
  • create_item()有一个参数,用于传入作为父节点的TreeItem,如果为null,则创建的TreeItem作为Tree根节点被添加。
  • create_item()返回对新建的TreeItem的引用,我们可以用一个变量接收,并通过这个变量继续设定TreeItem的属性或调用其方法。
# 创建根节点
var root:TreeItem = tree.create_item()
root.set_text(0,"root")   # 设定根节点文本
  • 这里我们使用TreeItemset_text()方法设定根节点root的文本
  • Tree控件可以显示多列信息,显示列数由其columns属性设定,默认为1,也就是只显示一列。
  • set_text()第一个参数是所在列的索引,默认为0

上面代码的效果:

image.png

创建根节点的子节点

  • 在添加根节点后,可以继续使用Tree控件的create_item()方法创建根节点的子节点,只是需要将根节点引用root作为父节点传入。
# 创建根节点
var root:TreeItem = tree.create_item()
root.set_text(0,"root")             # 设定根节点文本# 创建根节点的子节点
var itm:TreeItem = tree.create_item(root)
itm.set_text(0,"子节点1")

上面代码的效果:

image.png

  • 除了直接用Tree控件的create_item()方法外,也可以直接利用已经创建的TreeItem(比如根节点)的create_child()方法来创建子节点。
# 创建根节点
var root:TreeItem = tree.create_item()
root.set_text(0,"root")             # 设定根节点文本# 创建根节点的子节点
var itm:TreeItem = root.create_child()
itm.set_text(0,"子节点1")

两种方式得到的效果是一致的。

设定图标

使用TreeItemset_icon()方法,可以设定图标。同样第一个参数指定所在列。

var icon = preload("res://icon.svg")
# 创建根节点
var root:TreeItem = tree.create_item()
root.set_text(0,"root")   # 设定根节点文本
root.set_icon(0,icon)     # 设定根节点图标

上面代码效果:

image.png

设定图标最大宽度

  • 可以看到,单纯用set_icon()方法,只能在TreeItem上显示图标
  • 但是很多情况下我们会遇到图标尺寸不合适的情况,这时就需要用set_icon_max_width方法设定图标最大宽度了。
var icon = preload("res://icon.svg")
# 创建根节点
var root:TreeItem = tree.create_item()
root.set_text(0,"root")             # 设定根节点文本
root.set_icon(0,icon)               # 设定根节点图标
root.set_icon_max_width(0,30)       # 设定根节点图标宽度
  • Godot项目默认图标icon.svg尺寸为128×128像素,通过设定最大宽度为30,则图标将以30×30像素进行显示。
    image.png

使用SpriteSheet或打包纹理作为图标来源

  • SpriteSheet打包纹理都是一种常见的2D游戏或UI美术素材存储形式。SpriteSheet采用尺寸一致的行列划分,每一帧大小尺寸一致。而打包纹理,则是更自由的压缩存储多个大小不一的小图片。

  • 这里我使用如下的一个SpriteSheet来作为一个图标库,用来给不同的TreeItem显示不同的图标。

roller_sprites.png

  • 原图尺寸是900×150像素,单个图标的尺寸就是150×150像素。
  • 而我们将最终显示在TreeItem上的图标宽度限定为18像素,这样最终呈现的大小就是18×18像素。
var icon = preload("res://items.png") # 使用SpriteSheet或打包纹理
var cell_size = Vector2(150,150)      # 单元格尺寸# ------ 创建根节点 ------ 
var root:TreeItem = tree.create_item()
root.set_text(0,"root")             # 设定根节点文本
root.set_icon(0,icon)               # 设定根节点图标
root.set_icon_max_width(0,18)       # 设定根节点图标宽度
var cell_pos = Vector2(0,0)         # 帧的位置
# 显示图片局部区域作为图标
root.set_icon_region(0,Rect2(cell_pos * cell_size,cell_size))# ------ 为根节点创建5个子节点 ------ 
for i in range(5):var itm:TreeItem = root.create_child()itm.set_text(0,"item%d" % i)itm.set_icon(0,icon)               # 设定根节点图标itm.set_icon_max_width(0,18)       # 设定根节点图标宽度cell_pos = Vector2(i+1,0)         # 帧的位置itm.set_icon_region(0,Rect2(cell_pos * cell_size,cell_size))

效果:

image.png

对图标颜色进行调制

我们连接并处理Tree控件的item_selected信号。

image.png
信号处理函数代码如下:

# 处理Tree控件的item_selected信号
func _on_tree_item_selected():# 获取选中节点var selected_item:TreeItem = tree.get_selected()# 恢复其他节点的图标颜色var root:TreeItem = tree.get_root()root.set_icon_modulate(0,Color.WHITE)for itm in root.get_children():itm.set_icon_modulate(0,Color.WHITE)# 修改选中节点的图标颜色selected_item.set_icon_modulate(0,Color.BLUE)

运行后可以看到,选中项的图标颜色用蓝色调制的效果,没有使用额外的图标,而是通过颜色调制,做出了选中状态与非选中状态的图标差异效果。

调制图标颜色.gif

创建多列

启用和添加多列

Tree控件支持多列视图,其columns属性控制列的数目。我们可以在检视器面板直接修改,或者使用代码形式设定。

image.png

以下是代码形式:

# 设定显示两列信息
tree.columns = 2
# 创建根节点
var root:TreeItem = tree.create_item()
root.set_text(0,"root")             # 设定根节点第1列文本
root.set_text(1,"第2列")             # 设定第2列文本

我们通过设定第2列文本,就可以看到如下的效果:

image.png

每一列都可以像第1列一样设置图标或其他。

const icon = preload("res://icon.svg")
# 设定显示两列信息
tree.columns = 2
# 创建根节点
var root:TreeItem = tree.create_item()
root.set_text(0,"root")             # 设定根节点第1列文本
root.set_icon(0,icon)               # 设定第1列图标
root.set_icon_max_width(0,16)       # 设定第1列图标最大宽度
root.set_text(1,"第2列")             # 设定第2列文本
root.set_icon(1,icon)               # 设定第1列图标
root.set_icon_max_width(1,16)       # 设定第1列图标最大宽度

上面代码,我们为两列都设定的图标和图标最大宽度。

image.png

显示列标题

和一些其他可视化程序开发语言或数据表格软件的表格控件一样,Tree控件也可以显示列标题(或者通俗叫“字段”)。

你需要用代码或在检视器面板手动开启column_titles_visible

image.png

启用后就会在每列上方显示两个按钮,目前因为还没有设置其文本,所以是不完全的形态:

image.png

通过Treeset_column_title()方法,我们可以设定所有列标题按钮的文本:

# 设定和显示列标题
tree.column_titles_visible = true    # 显示列标题
tree.set_column_title(0,"第1列")     # 设定第1列列标题
tree.set_column_title(1,"第2列")     # 设定第2列列标题

上面代码设置后的效果:

image.png

是树型列表也是二维表格

我们为根节点添加一个子节点:

# 添加子节点
var itm:TreeItem = tree.create_item(root)
itm.set_text(0,"itm1")             # 设定根节点第1列文本
itm.set_icon(0,icon)               # 设定第1列图标
itm.set_icon_max_width(0,16)       # 设定第1列图标最大宽度
itm.set_text(1,"第2列")             # 设定第2列文本
itm.set_icon(1,icon)               # 设定第1列图标
itm.set_icon_max_width(1,16)       # 设定第1列图标最大宽度

此时的效果如下:

image.png

可以看到Tree的第一列保持了树形列表的特征,同时每一列的单元格都可以单独选中和交互。节点的折叠与展开也不受影响,表现出一种树形结构+二维表格的特性。

设定列标题文本对齐方式

  • 对列标题的,也可以设定其文本对齐方式,宽度等细节。

  • set_column_title_alignment设定列标题文本的对齐方式:

tree.set_column_title_alignment(0,HORIZONTAL_ALIGNMENT_LEFT)   # 居左显示

上面的代码设定第一列的列标题居左显示:

image.png

设定最小列宽

tree.set_column_custom_minimum_width(0,100)  # 设定第一列最小宽度

上面的代码设定第一列的最小宽度为100像素。
image.png

处理列标题点击

我们可以连接和处理Tree控件的column_title_clicked信号,来处理列标题的点击。

# 列标题被点击
func _on_tree_column_title_clicked(column, mouse_button_index):tree.select_mode = Tree.SELECT_MULTI # 设置多选模式# 获取Tree的所有节点var root:TreeItem = tree.get_root()var items = [root]items.append_array(root.get_children())# 选中指定列所有单元格for itm in items:for i in range(tree.columns):if i == column:itm.select(column)else:itm.deselect(i)

等价写法:

# 列标题被点击
func _on_tree_column_title_clicked(column, mouse_button_index):tree.select_mode = Tree.SELECT_MULTI # 设置多选模式# 获取Tree的所有节点var root:TreeItem = tree.get_root()var items = [root]items.append_array(root.get_children())tree.deselect_all()  # 清除所有选择# 选中指定列所有单元格for itm in items:itm.select(column)

上面的代码实现点击列标题时,选中当前列的所有单元格:

处理列标题按钮点击.gif

设定选择模式

Tree控件有三种选择模式,不同模式下选中TreeItem的效果哈方式不同,并且可能会影响某些信号的触发和处理。

检视器面板设定:

image.png

代码方式设定:

# 设定选择模式
tree.select_mode = Tree.SELECT_SINGLE # 单个单元格选择
tree.select_mode = Tree.SELECT_ROW    # 按行选择
tree.select_mode = Tree.SELECT_MULTI  # 多选

效果:
在这里插入图片描述

select_mode属性值效果
SELECT_SINGLE如果有多列,则一次只能单独选中某项的某列,也就是某个单元格
SELECT_ROW每次只能选中一行,也就是选中某项的所有列
SELECT_MULTI可以使用Ctrl或Shift键进行多选,一次可以选中多个单元格

创建按钮

在每列的单元格中都可以创建1个或多个按钮。按钮显示在单元格的最右侧,并且只显示一个图标,不显示文本。

但是可以设定其鼠标提示文本等。

这种按钮可以作为树控件单元格或行数据交互的一种形式。

const DIR = preload("res://custom_nodes/icons/dir.png")
# 创建根节点
var root:TreeItem = tree.create_item()
root.set_text(0,"root")             # 设定根节点第1列文本
root.set_icon(0,icon)               # 设定第1列图标
root.set_icon_max_width(0,16)       # 设定第1列图标最大宽度
root.add_button(0,DIR)   # 为根节点0列的单元格创建按钮
tree.set_column_custom_minimum_width(0,200)

上面的代码为两项的两列都添加了一个按钮。

image.png

处理按钮的点击

我们可以连接Treebutton_clicked信号,来处理单元格中添加的按钮的信号。
其参数返回:

  • item:按钮所在的TreeItem
  • column:按钮所在的列
  • id:按钮在所在单元格按钮中的ID或索引
  • mouse_button_index:点击按钮的鼠标按键,鼠标左键、中键、右键还是其他额外按键。
func _on_tree_button_clicked(item, column, id, mouse_button_index):if mouse_button_index == MOUSE_BUTTON_LEFT: # 鼠标左键match column:  # 某一列0,1:match id:   # 某按钮0:item.set_text(column,"")

上面的信号处理代码,可以让我们清空所在单元格的文本:

处理按钮点击.gif

快速编辑单元格文本

单元格可以进入文本编辑状态,显示一个可以编辑的文本框,从而可以快速修改单元格的文本内容,回车后完成编辑状态。

双击进入编辑状态

Treeitem_activated()信号在TreeItem或其某列单元格双击时触发。

我们通过连接此信号,创建信号处理函数,并设定如下的处理代码:

# 双击单元格
func _on_tree_item_activated():tree.edit_selected(true)  # 选中单元格进入编辑状态

此时,我们就可以通过双击单元格,进入单元格文本的编辑状态,修改完成后,回车确认,退出编辑状态:

双击编辑单元格文本.gif

编辑后的处理

  • Treeitem_edited()信号,在编辑完某个单元格并按回车确认后触发
  • Treeget_edited()方法可以获取当前编辑的TreeItem
  • 通过Treeget_edited_column()方法可以获取刚刚被编辑的TreeItem的列。

这里我只简单的打印输出单元格被修改后的文本:

# 单元格编辑完成后
func _on_tree_item_edited():var edt:TreeItem = tree.get_edited()    # 获取被编辑的TreeItemvar col:int = tree.get_edited_column()  # 被编辑的列索引print(edt.get_text(col))                # 打印修改后的单元格文本

处理TreeItem或单元格选中

  • item_selected()信号,在TreeItem选中时触发,但仅在select_modeSELECT_ROWSELECT_SINGLE 时有效。
# TreeItem被选中或单击
func _on_tree_item_selected():print(tree.get_selected())pass
  • select_modeSELECT_MULTISELECT_SINGLE时,则可以响应cell_selected()信号。
# TreeItem的单元格被选中或点击
func _on_tree_cell_selected():var sel:TreeItem = tree.get_selected()    # 选中的单元格所在TreeItemvar col:int = tree.get_selected_column()  # 选中的单元格列索引print(sel.get_text(col))

总结一下就是:

select_mode属性值item_selected()信号cell_selected()信号
SELECT_SINGLE
SELECT_ROW×
SELECT_MULTI×

用按钮控制Tree控件项的增、删、移动

我们创建一个如下的用户界面场景,创建一个Tree以及多个Button

通过这些按钮,我们实现为Tree控件添加、删除好移动项。

image.png

为场景根节点添加如下代码:

extends Control@onready var tree = $HBoxContainer/Tree@onready var add_btn = $HBoxContainer/VBoxContainer/addBtn
@onready var del_btn = $HBoxContainer/VBoxContainer/delBtn
@onready var moveup_btn = $HBoxContainer/VBoxContainer/moveupBtn
@onready var movedown_btn = $HBoxContainer/VBoxContainer/movedownBtn
@onready var clear_btn = $HBoxContainer/VBoxContainer/clearBtn# 添加
func _on_add_btn_pressed():var parent:TreeItem = nullif tree.get_root(): # 树不是空的if tree.get_selected(): # 有选中项parent = tree.get_selected()else:parent = tree.get_root()var itm = tree.create_item(parent)var idx:int = parent.get_child_count() if parent else 0itm.set_text(0,"新节点%d" % idx)# 删除
func _on_del_btn_pressed():if tree.get_root(): # 树不是空的if tree.get_selected(): # 有选中项tree.get_selected().free()pass# 上移
func _on_moveup_btn_pressed():var sel:TreeItem = tree.get_selected()if sel and sel.get_prev(): # 有选中项sel.move_before(sel.get_prev())# 下移
func _on_movedown_btn_pressed():var sel:TreeItem = tree.get_selected()if sel and sel.get_next(): # 有选中项sel.move_after(sel.get_next())# 清空
func _on_clear_btn_pressed():tree.clear()pass

可以看到其中主要是对按钮信号的处理代码,实现增、删,上下移动以及清空,效果如下动图所示:

按钮操控-添加.gif按钮操控-删除.gif
按钮操控-上移和下移.gif按钮操控-清空.gif

自由拖放

通过为Tree控件添加基本的控件拖放逻辑,就可以实现用鼠标对TreeItem的移动和放置。

extends Treefunc _get_drag_data(at_position):if get_tree(): # 不为空var sel:TreeItem = get_item_at_position(at_position)if sel: # 有选中项set_drag_preview(make_drag_preview(sel))return [sel]func _can_drop_data(at_position, data):# 如果拖放数据是一个TreeItem就可以放置# 拖放完毕恢复拖动标志设定drop_mode_flags = DROP_MODE_ON_ITEM | DROP_MODE_INBETWEENreturn data.size() > 0 and (data[0] is TreeItem)func _drop_data(at_position, data):# 获取目标拖放位置,-1,0,1分别代表在某项之前、之上和之后var target_pos = get_drop_section_at_position(at_position)# 获取鼠标位置处的TreeItemvar target_itm:TreeItem = get_item_at_position(at_position)# 如果目标位置处TreeItem是data[0]的子孙节点if target_itm in get_items(data[0]):return # 禁止移动match target_pos:-1: # 拖放到了某个TreeItem之前# 根据是否同级进行区别处理if data[0].get_parent() == target_itm.get_parent(): # 如果同级data[0].move_before(target_itm)else:data[0].get_parent().remove_child(data[0])         # 先从原来的父节点删除target_itm.add_child(data[0])                      # 添加到目标位置的TreeItemdata[0].move_before(target_itm)0:  # 拖放到了某个TreeItem上data[0].get_parent().remove_child(data[0])         # 先从原来的父节点删除target_itm.add_child(data[0])                      # 添加到目标位置的TreeItem1: # 拖放到了某个TreeItem之后# 根据是否同级进行区别处理if data[0].get_parent() == target_itm.get_parent(): # 如果同级data[0].move_after(target_itm)else:data[0].get_parent().remove_child(data[0])         # 先从原来的父节点删除target_itm.add_child(data[0])                      # 添加到目标位置的TreeItemdata[0].move_after(target_itm)# 返回某个TreeItem下所有子孙节点的集合
func get_items(item:TreeItem) -> Array[TreeItem]:var arr:Array[TreeItem]if item.get_child_count()>0:arr.append_array(item.get_children())for chd in item.get_children():arr.append_array(get_items(chd))return arr# 创建拖动预览
func make_drag_preview(itm:TreeItem) -> Button:var btn = Button.new()#btn.flat = truebtn.text = itm.get_text(0)btn.icon = itm.get_icon(0)return btn

实现效果:

自由拖放.gif

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

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

相关文章

论文阅读_代码生成模型_CodeGeeX

英文名称: CodeGeeX: A Pre-Trained Model for Code Generation with Multilingual Evaluations on HumanEval-X 中文名称: CodeGeeX:一种用于代码生成的预训练模型,并在HumanEval-X上进行多语言评估 链接: https://arxiv.org/abs/2303.17568 代码: http…

深度学习目标检测】二十二、基于深度学习的肺炎检测系统-含数据集、GUI和源码(python,yolov8)

肺炎尽管很常见,但准确诊断是一项困难的任务。它要求训练有素的专家对胸部X光片进行检查,并通过临床病史,生命体征和实验室检查进行确认。肺炎通常表现为胸部X光片上一个或多个区域的阴影(opacity)增加。但是,由于肺部有许多其他状…

【笔记】OpenHarmony和HarmonyOS区别及应用开发简介

一、概念 OpenHarmony(OH) : OpenAtom OpenHarmonyHarmonyOS(HO):开发 | 华为开发者联盟 (huawei.com) HO当前最高是3.1,在华为mate 60上面也是。关于4.0、5.0和next这类版本说法都是面向用户的,不是开发人员。对于程序员&#…

Python爬虫——解析常用三大方式之JsonPath

目录 JsonPath 安装 使用 我们的json数据 基本使用 案例 总结 JsonPath 主要适用于解析一些json的数据 安装 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ jsonpath 使用 obj json.load(open( json文件 , r , encoding utf-8 ) )ret jsonpath.…

【C++ 函数栈】栈区保存函数参数和函数调用的过程

目录 1 调用过程 🙋‍♂️ 作者:海码007📜 专栏:C专栏💥 标题:【C 函数栈】栈区保存函数参数和函数调用的过程❣️ 寄语:人生的意义或许可以发挥自己全部的潜力,所以加油吧&#xff…

yolov5训练太慢的解决方案

问题原因 训练太慢大多是因为没有安装CUDA和pytorch,导致的只有cpu在跑,显卡没跑 这就是很典型的。 解决方案 第一步:安装CUDA 在本机上面安装CUDA,记住只有N卡可以安装,一开始的电脑是自带CUDA的。 如果不是自带的CUDA&…

自己本地模拟内存数据库增删改查

目录 学习初衷准备代码实现结果感谢阅读 学习初衷 用于满足自己的测试要求,不连接数据库,也不在意数据丢失 准备 maven依赖 org.springframework.boot spring-boot-starter-test test 代码实现 内存数据库(InMemoryDatabase&#xff0…

Java中的List

List集合的特有方法 方法介绍 方法名描述void add(int index,E element)在此集合中的指定位置插入指定的元素E remove(int index)删除指定索引处的元素,返回被删除的元素E set(int index,E element)修改指定索引处的元素,返回被修改的元素E get(int inde…

【OpenGL编程手册-04】详细解释着色器

着色器 目录 一、说明二、着色器语言GLSL2.1 典型的着色器代码2.2 数据类型2.2.1 向量 2.3 输入与输出2.3.1 顶点着色器2.3.2 片段着色器 2.4 Uniform2.5 函数后缀含义2 .6 更多属性! 三、我们自己的着色器类四、从文件读取五、 编译着色器练习 一、说明 在Hello T…

故障诊断 | 一文解决,XGBoost极限梯度提升树的故障诊断(Matlab)

效果一览 文章概述 故障诊断 | 一文解决,XGBoost极限梯度提升树的故障诊断(Matlab) 模型描述 XGBoost通过集成多个决策树来建立一个强大的预测模型。它采用了一种特殊的梯度提升技术,称为极限梯度提升(Extreme Gradient Boosting),以提高模型的性能和鲁棒性。 极限梯度…

足球青训俱乐部|基于Springboot的足球青训俱乐部管理系统设计与实现(源码+数据库+文档)

足球青训俱乐部管理系统目录 目录 基于Springboot的足球青训俱乐部管理系统设计与实现 一、前言 二、系统设计 1、系统架构设计 三、系统功能设计 1、管理员登录界面 2、公告信息管理界面 3、学员管理界面 4、商品信息管理界面 5、课程安排管理界面 四、数据库设计…

Hackthebox - Authority

#ansible #Certify 赛博雨天:https://yutianqaq.github.io/ Recon smb smbmap -H 10.10.11.222 -u 挂载 smb 到 kali mount -t cifs //10.10.11.222/Development ./smb -o usernamesmb 在 cat smb/Automation/Ansible/PWM/defaults/main.yml 得到 ansible 的加…