包含此页的版本:
不含此页的版本:
注意:强烈建议使用 UI 工具包来扩展 Unity 编辑器,因为它提供了比 IMGUI 更现代、更灵活、更可扩展的解决方案。
本页上的信息假定读者对 IMGUI(即时模式 GUI)概念有基本了解。有关IMGUI和自定义编辑器窗口的信息,请参阅扩展编辑器和IMGUI Unity博客。
TreeView 是一个 IMGUI 控件,用于显示可以展开和折叠的分层数据。使用 TreeView 为编辑器窗口创建高度可自定义的列表视图和多列表,您可以将它们与其他 IMGUI 控件和组件一起使用。
有关可用 TreeView API 函数的信息,请参阅 TreeView 上的 Unity Scripting API 文档。
请注意,TreeView 不是树数据模型。您可以使用您喜欢的任何树数据结构来构造 TreeView。这可以是 C# 树模型,也可以是基于 Unity 的树结构,如 Transform 层次结构。
TreeView 的呈现是通过确定称为行的扩展项列表来处理的。每行代表一个TreeViewItem.每TreeViewItem包含父信息和子信息,TreeView 使用这些信息来处理导航(键和鼠标输入)。
TreeView 具有单个根TreeViewItem该值处于隐藏状态,不会显示在编辑器中。此项是所有其他项的根目录。
除了 TreeView 本身之外,最重要的类是 TreeViewItem 和 TreeViewState。
TreeViewState (TreeViewState) 包含与编辑器中的 TreeView 字段交互时更改的状态信息,例如选择状态、展开状态、导航状态和滚动状态。TreeViewState是唯一可序列化的状态。TreeView 本身不可序列化 - 它是在构造或重新加载时从它所表示的数据重建的。添加TreeViewState作为EditorWindow-派生类,以确保在重新加载时用户更改的状态不会丢失脚本一段代码,允许您创建自己的组件、触发游戏事件、随时间修改组件属性以及以您喜欢的任何方式响应用户输入。更多信息
请参阅术语表或进入播放模式(有关如何执行此作的信息,请参阅有关扩展编辑器的文档)。对于包含TreeViewState字段,请参阅下面的示例 1:一个简单的 TreeView。
TreeViewItem (TreeViewItem) 包含有关单个 TreeView 项的数据,用于在编辑器中构建树结构的表示形式。每TreeViewItem必须使用唯一的整数 ID(在 TreeView 中的所有项中唯一)构造。该 ID 用于在树中查找选择状态、展开状态和导航的项。如果树表示 Unity 对象,请对每个对象使用 GetInstanceID 作为TreeViewItem.这些 ID 用于TreeViewState在编辑器中重新加载脚本或进入播放模式时保留用户更改的状态(例如展开的项目)。
都TreeViewItems有一个depth属性,指示视觉缩进。有关详细信息,请参阅下面的初始化 TreeView 示例。
BuildRoot (BuildRoot) 是TreeView必须实现的类才能创建 TreeView。使用此方法处理创建树的根项。每次在树上调用 Reload 时都会调用此函数。对于使用小型数据集的简单树,请创建TreeViewItems在根项下BuildRoot.对于非常大的树,在每次重新加载时创建整棵树并不是最佳选择。在这种情况下,创建根,然后覆盖BuildRows方法仅为当前行创建项目。例如,以下内容BuildRoot在使用中,请参阅下面的示例 1:一个简单的 TreeView。
BuildRows (BuildRows) 是一种虚拟方法,其中默认实现根据BuildRoot.如果根是在BuildRoot,则应重写此方法以处理展开的行。有关更多信息,请参阅下面的初始化 TreeView。
此图总结了BuildRoot和BuildRows事件方法。请注意,BuildRoot方法每次调用一次Reload被称为。BuildRows被调用的频率更高,因为它被调用一次Reload(紧接着BuildRoot)并且每次TreeViewItem展开或折叠。
TreeView 在Reload从 TreeView 对象调用方法。
有两种方法可以设置 TreeView:
创建完整树 - 创建TreeViewItem树模型数据中的所有项。这是默认设置,需要更少的代码来设置。从 TreeView 对象调用 BuildRoot 时,将生成完整树。
仅创建展开的项 - 此方法要求您覆盖BuildRows手动控制正在显示的行,以及BuildRoot仅用于创建根TreeViewItem.此方法最适合大型数据集或经常更改的数据。
第一种方法适用于小型数据集或不经常更改的数据。对于大型数据集或经常更改的数据,使用第二种方法,因为仅创建扩展项而不是完整树会更快。
您可以通过三种方式设置 TreeViewItems:
创造TreeViewItems 的子项、父级和深度从一开始就初始化。
创造TreeViewItems 替换为父级和子级,然后使用 SetupDepthsFromParentsAndChildren 设置深度。
创造TreeViewItems 仅使用深度信息,然后使用 SetupDepthsFromParentsAndChildren 设置父级和子级引用。
要查看下面所示示例的项目和源代码,请下载TreeViewExamples.zip。
要创建 TreeView,请创建一个类,该类将TreeView类并实现抽象方法BuildRoot.以下示例创建一个简单的 TreeView。
class SimpleTreeView : TreeView
{
public SimpleTreeView(TreeViewState treeViewState)
: base(treeViewState)
{
Reload();
}
protected override TreeViewItem BuildRoot ()
{
// BuildRoot is called every time Reload is called to ensure that TreeViewItems
// are created from data. Here we create a fixed set of items. In a real world example,
// a data model should be passed into the TreeView and the items created from the model.
// This section illustrates that IDs should be unique. The root item is required to
// have a depth of -1, and the rest of the items increment from that.
var root = new TreeViewItem {id = 0, depth = -1, displayName = "Root"};
var allItems = new List<TreeViewItem>
{
new TreeViewItem {id = 1, depth = 0, displayName = "Animals"},
new TreeViewItem {id = 2, depth = 1, displayName = "Mammals"},
new TreeViewItem {id = 3, depth = 2, displayName = "Tiger"},
new TreeViewItem {id = 4, depth = 2, displayName = "Elephant"},
new TreeViewItem {id = 5, depth = 2, displayName = "Okapi"},
new TreeViewItem {id = 6, depth = 2, displayName = "Armadillo"},
new TreeViewItem {id = 7, depth = 1, displayName = "Reptiles"},
new TreeViewItem {id = 8, depth = 2, displayName = "Crocodile"},
new TreeViewItem {id = 9, depth = 2, displayName = "Lizard"},
};
// Utility method that initializes the TreeViewItem.children and .parent for all items.
SetupParentsAndChildrenFromDepths (root, allItems);
// Return root of the tree
return root;
}
}
在此示例中,深度信息用于构建 TreeView。最后,调用SetupDepthsFromParentsAndChildren设置TreeViewItems.
请注意,有两种方法可以设置TreeViewItem:直接设置父子项,或使用AddChild方法,如以下示例所示:
protected override TreeViewItem BuildRoot()
{
var root = new TreeViewItem { id = 0, depth = -1, displayName = "Root" };
var animals = new TreeViewItem { id = 1, displayName = "Animals" };
var mammals = new TreeViewItem { id = 2, displayName = "Mammals" };
var tiger = new TreeViewItem { id = 3, displayName = "Tiger" };
var elephant = new TreeViewItem { id = 4, displayName = "Elephant" };
var okapi = new TreeViewItem { id = 5, displayName = "Okapi" };
var armadillo = new TreeViewItem { id = 6, displayName = "Armadillo" };
var reptiles = new TreeViewItem { id = 7, displayName = "Reptiles" };
var croco = new TreeViewItem { id = 8, displayName = "Crocodile" };
var lizard = new TreeViewItem { id = 9, displayName = "Lizard" };
root.AddChild(animals);
animals.AddChild(mammals);
animals.AddChild(reptiles);
mammals.AddChild(tiger);
mammals.AddChild(elephant);
mammals.AddChild(okapi);
mammals.AddChild(armadillo);
reptiles.AddChild(croco);
reptiles.AddChild(lizard);
SetupDepthsFromParentsAndChildren(root);
return root;
}
BuildRoot方法SimpleTreeView以上级别以下示例显示了EditorWindow其中包含SimpleTreeView.TreeView 是用TreeViewState实例。TreeView 的实现者应确定应如何处理此视图状态:其状态是否应保留到 Unity 的下一个会话,或者是否应仅在重新加载脚本后保留其状态(在进入播放模式或重新编译脚本时)。在此示例中,TreeViewState在EditorWindow,确保 TreeView 在关闭并重新打开编辑器时保留其状态。
using System.Collections.Generic;
using UnityEngine;
using UnityEditor.IMGUI.Controls;
class SimpleTreeViewWindow : EditorWindow
{
// SerializeField is used to ensure the view state is written to the window
// layout file. This means that the state survives restarting Unity as long as the window
// is not closed. If the attribute is omitted then the state is still serialized/deserialized.
[SerializeField] TreeViewState m_TreeViewState;
//The TreeView is not serializable, so it should be reconstructed from the tree data.
SimpleTreeView m_SimpleTreeView;
void OnEnable ()
{
// Check whether there is already a serialized view state (state
// that survived assembly reloading)
if (m_TreeViewState == null)
m_TreeViewState = new TreeViewState ();
m_SimpleTreeView = new SimpleTreeView(m_TreeViewState);
}
void OnGUI ()
{
m_SimpleTreeView.OnGUI(new Rect(0, 0, position.width, position.height));
}
// Add menu named "My Window" to the Window menu
[MenuItem ("TreeView Examples/Simple Tree Window")]
static void ShowWindow ()
{
// Get existing open window or if none, make a new one:
var window = GetWindow<SimpleTreeViewWindow> ();
window.titleContent = new GUIContent ("My Window");
window.Show ();
}
}
此示例演示了使用 MultiColumnHeader 类的多列 TreeView。
MultiColumnHeader支持以下功能:重命名项目、多选、使用普通 IMGUI 控件(例如滑块和对象字段)重新排序项目和自定义行内容、列排序以及行的过滤和搜索。
此示例使用类创建数据模型TreeElement和TreeModel.TreeView 从此“TreeModel”获取数据。在此示例中,TreeElement和TreeModel内置类以演示 TreeView 类的功能。这些类已包含在 TreeView 示例项目 (TreeViewExamples.zip) 中。该示例还展示了如何将树模型结构序列化为 ScriptableObject 并保存在资产中。
[Serializable]
//The TreeElement data class is extended to hold extra data, which you can show and edit in the front-end TreeView.
internal class MyTreeElement : TreeElement
{
public float floatValue1, floatValue2, floatValue3;
public Material material;
public string text = "";
public bool enabled = true;
public MyTreeElement (string name, int depth, int id) : base (name, depth, id)
{
floatValue1 = Random.value;
floatValue2 = Random.value;
floatValue3 = Random.value;
}
}
以下 ScriptableObject 类可确保在序列化树时数据保留在资产中。
[CreateAssetMenu (fileName = "TreeDataAsset", menuName = "Tree Asset", order = 1)]
public class MyTreeAsset : ScriptableObject
{
[SerializeField] List<MyTreeElement> m_TreeElements = new List<MyTreeElement> ();
internal List<MyTreeElement> treeElements
{
get { return m_TreeElements; }
set { m_TreeElements = value; }
}
}
以下示例显示了类的片段MultiColumnTreeView,说明了如何实现多列 GUI。在 TreeView 示例项目 (TreeViewExamples.zip) 中查找完整的源代码。
public MultiColumnTreeView (TreeViewState state,
MultiColumnHeader multicolumnHeader,
TreeModel<MyTreeElement> model)
: base (state, multicolumnHeader, model)
{
// Custom setup
rowHeight = 20;
columnIndexForTreeFoldouts = 2;
showAlternatingRowBackgrounds = true;
showBorder = true;
customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f;
extraSpaceBeforeIconAndLabel = kToggleWidth;
multicolumnHeader.sortingChanged += OnSortingChanged;
Reload();
}
上述代码示例中的自定义更改进行了以下调整:
rowHeight = 20:将默认高度(基于 EditorGUIUtility.singleLineHeight 的 16 个点)更改为 20,以便为 GUI 控件增加更多空间。
columnIndexForTreeFoldouts = 2:在本例中,折叠箭头显示在第三列中,因为此值设置为 2(见上图)。如果未更改此值,则折叠将在第一列中呈现,因为默认情况下“columnIndexForTreeFoldouts”为 0。
showAlternatingRowBackgrounds = true:启用交替行背景色,使每行都不同。
showBorder = true:渲染 TreeView,并在其周围留出边距,以便显示细边框以将其与其余内容分隔开来
customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f:将折叠框垂直居中 - 请参阅下面的自定义 GUI。
extraSpaceBeforeIconAndLabel = 20:在树标签之前留出空间,以便显示切换按钮。
multicolumnHeader.sortingChanged += OnSortingChanged:为事件分配一个方法,以检测标题组件中的排序何时发生变化(单击标题列时),以便 TreeView 的行发生变化以反映排序状态。
如果使用默认的 RowGUI 处理,则 TreeView 看起来像SimpleTreeView上面的示例,只有折叠和标签。当对每个项目使用多个数据值时,您必须覆盖 RowGUI 方法以可视化这些值。
protected override void RowGUI (RowGUIArgs args)
以下代码示例是RowGUIArgs结构。
protected struct RowGUIArgs
{
public TreeViewItem item;
public string label;
public Rect rowRect;
public int row;
public bool selected;
public bool focused;
public bool isRenaming;
public int GetNumVisibleColumns ()
public int GetColumn (int visibleColumnIndex)
public Rect GetCellRect (int visibleColumnIndex)
}
您可以扩展TreeViewItem并添加其他用户数据(这会创建一个派生自TreeViewItem).然后,您可以在 RowGUI 回调中使用此用户数据。下面提供了一个示例。看override void RowGUI- 此示例将输入项转换为TreeViewItem<MyTreeElement>.
有三种方法与列处理相关:GetNumVisibleColumns、GetColumn 和 GetCellRect。只有当 TreeView 使用 MultiColumnHeader 构造时,才能调用这些命令,否则会引发异常。
protected override void RowGUI (RowGUIArgs args)
{
var item = (TreeViewItem<MyTreeElement>) args.item;
for (int i = 0; i < args.GetNumVisibleColumns (); ++i)
{
CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args);
}
}
void CellGUI (Rect cellRect, TreeViewItem<MyTreeElement> item, MyColumns column, ref RowGUIArgs args)
{
// Center the cell rect vertically using EditorGUIUtility.singleLineHeight.
// This makes it easier to place controls and icons in the cells.
CenterRectUsingSingleLineHeight(ref cellRect);
switch (column)
{
case MyColumns.Icon1:
// Draw custom texture
GUI.DrawTexture(cellRect, s_TestIcons[GetIcon1Index(item)], ScaleMode.ScaleToFit);
break;
case MyColumns.Icon2:
//Draw custom texture
GUI.DrawTexture(cellRect, s_TestIcons[GetIcon2Index(item)], ScaleMode.ScaleToFit);
break;
case MyColumns.Name:
// Make a toggle button to the left of the label text
Rect toggleRect = cellRect;
toggleRect.x += GetContentIndent(item);
toggleRect.width = kToggleWidth;
if (toggleRect.xMax < cellRect.xMax)
item.data.enabled = EditorGUI.Toggle(toggleRect, item.data.enabled);
// Default icon and label
args.rowRect = cellRect;
base.RowGUI(args);
break;
case MyColumns.Value1:
// Show a Slider control for value 1
item.data.floatValue1 = EditorGUI.Slider(cellRect, GUIContent.none, item.data.floatValue1, 0f, 1f);
break;
case MyColumns.Value2:
// Show an ObjectField for materials
item.data.material = (Material)EditorGUI.ObjectField(cellRect, GUIContent.none, item.data.material,
typeof(Material), false);
break;
case MyColumns.Value3:
// Show a TextField for the data text string
item.data.text = GUI.TextField(cellRect, item.data.text);
break;
}
}
问:在我的 TreeView 子类中,我有函数 BuildRoot 和 RowGUI。是RowGUI要求每个TreeViewItem添加到构建函数中,还是仅用于滚动视图中屏幕上可见的项目?
一个:RowGUI仅对屏幕上可见的项目调用。例如,如果您有 10,000 个项目,则屏幕上只有 20 个可见项目具有其RowGUI叫。
问:我可以获取屏幕上可见的行的索引吗?
答:是的。使用 GetFirstAndLastVisibleRows 方法。
问:是否可以获取 BuildRows 中构建的行列表?
答:是的。使用 GetRows 方法。
问:是否必须调用任何被覆盖的函数base.Method?
答:仅当方法具有要扩展的默认行为时。
问:我只是想列出项目(不是树)。我必须创建根吗?
答:是的,你应该永远有一个根。您可以创建根项并将root.children = rows用于快速设置。
问:我已向行添加了一个切换 - 为什么当我单击该行时,选择没有跳转到该行?
答:默认情况下,仅当鼠标向下未被行的内容消耗时,才会选择该行。在这里,您的 Toggle 使用该事件。要解决此问题,请在调用切换按钮之前使用 SelectionClick 方法。
问:有没有我可以在之前或之后使用的方法RowGUI方法被调用?
答:是的。请参阅有关 BeforeRowsGUI 和 AfterRowsGUI 的 API 文档。
问:有没有一种简单的方法可以从 API 将关键焦点返回到 TreeView?如果我在行中选择 FloatField,则行选择将变为灰色。如何再次将其变蓝?
答:蓝色表示当前哪一行具有关键焦点。由于 FloatField 具有焦点,因此 TreeView 会失去焦点,因此这是预期的行为。设置GUIUtility.keyboardControl = treeViewControlID需要时。
问:如何从id设置为TreeViewItem?
答:使用 FindItem 或 FindRows。
问:当用户更改其在 TreeView 中的选择时,如何接收回调?
答:重写 SelectionChanged 方法(其他有用的回调:DoubleClickedItem 和 ContextClickedItem)。