Note: It’s strongly recommended to use the UI Toolkit to extend the Unity Editor, as it provides a more modern, flexible, and scalable solution than IMGUI.
The information on this page assumes the reader has basic knowledge of IMGUI (Immediate Mode GUI) concepts. For information about IMGUI and customizing Editor windows, refer to Extending the Editor and the IMGUI Unity blog.
TreeView is an IMGUI control used to display hierarchical data that you can expand and collapse. Use TreeView to create highly customizable list views and multi-column tables for Editor windows, which you can use alongside other IMGUI controls and components.
See Unity Scripting API documentation on TreeView for information about the available TreeView API functions.
Note that the TreeView is not a tree data model. You can construct TreeView using any tree data structure you prefer. This can be a C# tree model, or a Unity-based tree structure like the Transform hierarchy.
The rendering of the TreeView is handled by determining a list of expanded items called rows. Each row represents one TreeViewItem
. Each TreeViewItem
contains parent and children information, which is used by the TreeView to handle navigation (key and mouse input).
The TreeView has a single root TreeViewItem
which is hidden and does not appear in the Editor. This item is the root of all other items.
The most important classes aside from the TreeView itself are TreeViewItem and TreeViewState.
TreeViewState (TreeViewState) contains state information that is changed when interacting with TreeView fields in the Editor, such as selection state, expanded state, navigation state, and scroll state. TreeViewState
is the only state that is serializable. The TreeView itself is not serializable - it is reconstructed from the data that it represents when it is constructed or reloaded. Add the TreeViewState
as a field in your EditorWindow
-derived class to ensure that user-changed states are not lost when reloading scriptsA piece of code that allows you to create your own Components, trigger game events, modify Component properties over time and respond to user input in any way you like. More info
See in Glossary or entering Play mode (see documentation on extending the Editor for information on how to do this). For an example of a class containing a TreeViewState
field, see Example 1: A simple TreeView, below.
TreeViewItem (TreeViewItem) contains data about an individual TreeView item, and is used to build the representation of the tree structure in the Editor. Each TreeViewItem
must be constructed with a unique integer ID (unique among all the items in the TreeView). The ID is used for finding items in the tree for the selection state, expanded state, and navigation. If the tree represents Unity objects, use GetInstanceID for each object as the ID for the TreeViewItem
. The IDs are used in the TreeViewState
to persist user-changed states (such as expanded items) when reloading scripts or entering Play mode in the Editor.
All TreeViewItems
have a depth
property, which indicates the visual indentation. See the Initializing a TreeView example below for more information.
BuildRoot (BuildRoot) is the single abstract method of the TreeView
class that must be implemented to create a TreeView. Use this method to handle creating the root item of the tree. This is called every time Reload is called on the tree. For simple trees that use small data sets, create the entire tree of TreeViewItems
under the root item in BuildRoot
. For very large trees, it is not optimal to create the entire tree on every reload. In this situation, create the root and then override the BuildRows
method to only create items for the current rows. For an example of BuildRoot
in use, see Example 1: A simple TreeView below.
BuildRows (BuildRows) is a virtual method where the default implementation handles building the rows list based on the full tree created in BuildRoot
. If only the root was created in BuildRoot
, this method should be overridden to handle the expanded rows. See Initializing a TreeView, below, for more information.
This diagram summarizes the ordering and repetition of BuildRoot
and BuildRows
event methods during a TreeView’s lifetime. Note that the BuildRoot
method is called once every time Reload
is called. BuildRows
is called more often because it is called once on Reload
(right after BuildRoot
) and every time TreeViewItem
is expanded or collapsed.
The TreeView is initialized when the Reload
method is called from a TreeView object.
There are two ways to set up a TreeView:
Create the full tree - Create TreeViewItem
s for all items in the tree model data. This is the default and requires less code to set up. The full tree is built when BuildRoot is called from a TreeView object.
Create only the expanded items - This approach requires you to override BuildRows
to manually control the rows being shown, and BuildRoot
is only used to create the root TreeViewItem
. This approach scales best with large data sets or data that changes often.
Use the first approach for small data sets, or for data that does not change often. Use the second approach for large data sets, or data that changes often, because it is faster to only create only expanded items rather than a full tree.
There are three ways you can set up TreeViewItems:
Create TreeViewItem
s with children, parent, and depths initialized from the start.
Create TreeViewItem
s with parent and children and then use SetupDepthsFromParentsAndChildren to set the depths.
Create TreeViewItem
s only with depth information and then use SetupDepthsFromParentsAndChildren to set the the parent and children references.
To view the Project and source code for the examples shown below, download TreeViewExamples.zip.
To create a TreeView, create a class that extends the TreeView
class and implement the abstract method BuildRoot
. The following example creates a simple 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; } }
In this example, depth information is used to build the TreeView. Finally, a call to SetupDepthsFromParentsAndChildren
sets the parent and children data of the TreeViewItem
s.
Note that there are two ways to set up the TreeViewItem
: Set the parent and children directly, or use the AddChild
method, as demonstrated in the following example:
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
method for the SimpleTreeView
class aboveThe following example shows the EditorWindow
that contains the SimpleTreeView
. TreeViews are constructed with a TreeViewState
instance. The implementer of the TreeView should determine how this view state should be handled: whether its state should persist until the next session of Unity, or whether it should only preserve its state after scripts are reloaded (either when entering Play mode or recompiling scripts). In this example, the TreeViewState
is serialized in the EditorWindow
, ensuring the TreeView preserves its state when the Editor is closed and reopened.
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 (); } }
This example illustrates a multi-column TreeView that uses the MultiColumnHeader class.
MultiColumnHeader
supports the following functionality: renaming items, multi-selection, reordering items and custom row content using normal IMGUI controls (such as sliders and object fields), sorting of columns, and the filtering and searching of rows.
This example creates a data model using the classes TreeElement
and TreeModel
. The TreeView fetches data from this “TreeModel”. In this example, the TreeElement
and TreeModel
classes have been built in to demonstrate the features of the TreeView class. These classes have been included in the TreeView Examples Project (TreeViewExamples.zip). The example also shows how the tree model structure is serialized to a ScriptableObject and saved in an Asset.
[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; } }
The following ScriptableObject class ensures that data persists in an Asset when the tree is serialized.
[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; } } }
The following example shows snippets of the class MultiColumnTreeView
, which illustrates how the multi column GUI is achieved. Find full source code in the TreeView Examples Project (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(); }
The custom changes in the code sample above make the following adjustments:
rowHeight = 20
: Change the default height (which is based on EditorGUIUtility.singleLineHeight’s 16 points) to 20, to add more room for GUI controls.
columnIndexForTreeFoldouts = 2
: In the example, the fold-out arrows are shown in the third column because this value is set to 2 (see image above). If this value is not changed, the fold-outs are rendered in the first column, because “columnIndexForTreeFoldouts” is 0 by default.
showAlternatingRowBackgrounds = true
: Enable alternating row background colors, so that each row is distinct.
showBorder = true
: Render the TreeView with a margin around it, so that a thin border is shown to delimit it from the rest of the content
customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f
: Center fold-outs vertically in the row - see Customizing the GUI below.
extraSpaceBeforeIconAndLabel = 20
: Make space before the tree labels so the toggle button is shown.
multicolumnHeader.sortingChanged += OnSortingChanged
: Assign a method to the event to detect when the sorting changes in the header component (when the header column is clicked), so that the rows of the TreeView change to reflect the sorting state.
If the default RowGUI handling is used, the TreeView looks like the SimpleTreeView
example above, with only fold-outs and a label. When using multiple data values for each item, you must override the RowGUI method to visualize these values.
protected override void RowGUI (RowGUIArgs args)
The following code sample is the argument structure of the RowGUIArgs
struct.
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) }
You can extend the TreeViewItem
and add additional user data (which creates a class that derives from TreeViewItem
). You can then use this user data in the RowGUI callback. An example of this is provided below. See override void RowGUI
- this example casts the input item to TreeViewItem<MyTreeElement>
.
There are three methods which are related to column handling: GetNumVisibleColumns, GetColumn, and GetCellRect. You can only call these when the TreeView is constructed with a MultiColumnHeader, otherwise an exception is thrown.
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; } }
Q: In my TreeView subclass, I have the functions BuildRoot and RowGUI. Is RowGUI
called for every TreeViewItem
that got added in the build function, or only for items that are visible on screen in the scroll view?
A: RowGUI
is only called for the items visible on screen. For example, if you have 10,000 items, only the 20 visible items on screen have their RowGUI
called.
Q: Can I get the indices of the rows that are visible on the screen?
A: Yes. Use the method GetFirstAndLastVisibleRows.
Q: Can I get the list of rows that are built in BuildRows?
A: Yes. Use the method GetRows.
Q: Is it mandatory for any of the overridden functions to call base.Method
?
A: Only if the method has a default behavior you want to extend.
Q: I just want to make list of items (not a tree). Do I have to create the root?
A: Yes, you should always have a root. You can create the root item and set root.children = rows
for fast setup.
Q: I’ve added a Toggle to my row - why doesn’t the selection jump to that row when I click on it?
A: By default, the row is only selected if the mouse down is not consumed by the contents of the row. Here, your Toggle consumes the event. To fix this, use the method SelectionClick before your Toggle button is called.
Q: Are there methods I can use before or after all RowGUI
methods are called?
A: Yes. See API documentation on BeforeRowsGUI and AfterRowsGUI.
Q: Is there a simple way to return key focus to the TreeView from API? If I select a FloatField in my row, the row selection becomes gray. How do I make it blue again?
A: The blue color indicates which row currently has key focus. Because the FloatField has focus, the TreeView loses focus, so this is the intended behavior. Set GUIUtility.keyboardControl = treeViewControlID
when needed.
Q: How do I convert from id
to a TreeViewItem
?
A: Use either FindItem or FindRows.
Q: How do I receive a callback when a user changes their selection in the TreeView?
A: Override the SelectionChanged method (other useful callbacks: DoubleClickedItem, and ContextClickedItem).
Did you find this page useful? Please give it a rating:
Thanks for rating this page!
What kind of problem would you like to report?
Thanks for letting us know! This page has been marked for review based on your feedback.
If you have time, you can provide more information to help us fix the problem faster.
Provide more information
You've told us this page needs code samples. If you'd like to help us further, you could provide a code sample, or tell us about what kind of code sample you'd like to see:
You've told us there are code samples on this page which don't work. If you know how to fix it, or have something better we could use instead, please let us know:
You've told us there is information missing from this page. Please tell us more about what's missing:
You've told us there is incorrect information on this page. If you know what we should change to make it correct, please tell us:
You've told us this page has unclear or confusing information. Please tell us more about what you found unclear or confusing, or let us know how we could make it clearer:
You've told us there is a spelling or grammar error on this page. Please tell us what's wrong:
You've told us this page has a problem. Please tell us more about what's wrong:
Thank you for helping to make the Unity documentation better!
Your feedback has been submitted as a ticket for our documentation team to review.
We are not able to reply to every ticket submitted.
When you visit any website, it may store or retrieve information on your browser, mostly in the form of cookies. This information might be about you, your preferences or your device and is mostly used to make the site work as you expect it to. The information does not usually directly identify you, but it can give you a more personalized web experience. Because we respect your right to privacy, you can choose not to allow some types of cookies. Click on the different category headings to find out more and change our default settings. However, blocking some types of cookies may impact your experience of the site and the services we are able to offer.
More information
These cookies enable the website to provide enhanced functionality and personalisation. They may be set by us or by third party providers whose services we have added to our pages. If you do not allow these cookies then some or all of these services may not function properly.
These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us to know which pages are the most and least popular and see how visitors move around the site. All information these cookies collect is aggregated and therefore anonymous. If you do not allow these cookies we will not know when you have visited our site, and will not be able to monitor its performance.
These cookies may be set through our site by our advertising partners. They may be used by those companies to build a profile of your interests and show you relevant adverts on other sites. They do not store directly personal information, but are based on uniquely identifying your browser and internet device. If you do not allow these cookies, you will experience less targeted advertising. Some 3rd party video providers do not allow video views without targeting cookies. If you are experiencing difficulty viewing a video, you will need to set your cookie preferences for targeting to yes if you wish to view videos from these providers. Unity does not control this.
These cookies are necessary for the website to function and cannot be switched off in our systems. They are usually only set in response to actions made by you which amount to a request for services, such as setting your privacy preferences, logging in or filling in forms. You can set your browser to block or alert you about these cookies, but some parts of the site will not then work. These cookies do not store any personally identifiable information.