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.
이 페이지의 정보는 독자가 IMGUI(Immediate Mode GUI) 개념에 관한 기본 지식이 있음을 전제로 합니다. IMGUI와 에디터 창의 커스텀화에 대한 내용은 에디터 확장과 IMGUI Unity 블로그를 참조하십시오.
TreeView는 펼치고 접을 수 있는 계층 구조적 데이터를 나타낼 때 사용하는 IMGUI 컨트롤입니다. 다른 IMGUI 컨트롤 및 컴포넌트와 함께 사용할 수 있는 고도로 커스텀화 가능한 리스트 뷰를 생성할 때와 에디터 창을 위한 다중 열로 구성된 표를 생성할 때 TreeView를 사용합니다.
사용 가능한 TreeView API 함수에 대한 내용은 TreeView Unity 스크립팅 API 문서를 참조하십시오.
TreeView는 트리 데이터 모델이 아님에 유의하십시오. 선호하는 모든 트리 데이터 구조를 사용하여 TreeView를 구성할 수 있습니다. C# 트리 모델 또는 Transform 계층 구조와 같은 Unity 기반 트리 구조가 될 수 있습니다.
TreeView의 렌더링은 행이라고 하는 펼쳐지는 아이템의 리스트을 결정하여 처리됩니다. 각 행은 하나의 TreeViewItem
을 나타냅니다. 각 TreeViewItem
은 부모와 자식 정보를 포함하며 TreeView에서 내비게이션(키와 마우스 입력)을 처리하는 데 사용됩니다.
TreeView는 숨겨져 있고 에디터에는 보이지 않는 단일 루트 TreeViewItem
을 가지고 있습니다. 이 아이템은 다른 모든 아이템의 루트입니다.
TreeView 자체를 제외한 가장 중요한 클래스는 TreeViewItem과 TreeViewState입니다.
TreeViewState (TreeViewState)에는 에디터에서 TreeView 필드와 연결할 때 변경되는 상태 정보(예: 선택 상태, 확장 상태, 내비게이션 상태, 스크롤 상태)가 들어 있습니다. TreeViewState
는 직렬화가 가능한 유일한 상태입니다. TreeView 자체는 직렬화되지 않습니다. 대신에 구성되거나 다시 로드될 때 표시하는 데이터에서 재구성됩니다. EditorWindow
파생 클래스에 TreeViewState
를 필드로 추가하면 스크립트를 다시 로드하거나 재생 모드를 사용할 때에도 사용자가 변경한 상태가 손실되지 않습니다(자세한 내용은 에디터 확장에 관한 문서 참조). TreeViewState
필드가 포함된 클래스 예제는 아래의 예제 1: 간단한 TreeView를 참조하십시오.
TreeViewItem (TreeViewItem)은 개별 TreeView 아이템에 관한 데이터를 포함하며 에디터의 트리 구조 모양을 만들 때 사용됩니다. 각각의 TreeViewItem
은 고유 정수 ID(TreeView의 모든 아이템 사이에서 고유함)로 구성되어야 합니다. ID는 선택 상태, 확장 상태, 내비게이션 상태를 위한 아이템을 트리에서 찾는 데 사용됩니다. 트리가 Unity 오브젝트를 나타내는 경우, GetInstanceID를 각 오브젝트를 위한 TreeViewItem
의 ID로 사용합니다. ID는 에디터에서 스크립트 재로딩 또는 플레이 모드 시작 시 TreeViewState
에서 사용자 변경 상태(펼쳐진 아이템과 같은)를 유지시키기 위해 사용됩니다.
모든 TreeViewItems
는 depth
프로퍼티를 가지며, 이 프로퍼티는 그려지는 순서를 나타냅니다. 더 많은 정보를 보려면 아래 Initializing a TreeView 예제를 참조하십시오.
BuildRoot (BuildRoot)는 TreeView를 생성하기 위해 실행해야 하는 TreeView
클래스의 단일 추상 메서드입니다. 이 메서드를 사용하여 트리의 루트 아이템 생성을 처리합니다. 이 메서드는 트리 Reload가 호출될 때마다 호출됩니다. 작은 데이터 세트를 사용하는 단순한 트리를 위해서는 BuildRoot
의 루트 아이템에 TreeViewItems
트리 전체를 생성합니다. 아주 큰 트리의 경우, 트리 전체를 재로드할 때마다 생성하는 것이 최상의 방법은 아닙니다. 이 경우에는 루트를 생성한 다음에 BuildRows
메서드를 오버라이드하여 현재 행을 위한 아이템만 생성하도록 합니다. BuildRoot
가 사용되는 예제를 보려면 아래의 예제 1: 간단한 TreeView를 참조하십시오.
BuildRows(BuildRows)는 기본 구현이 BuildRoot
에 생성된 트리 전체를 기반으로 행 리스트 구축을 처리하는 가상 메서드입니다. BuildRoot
에 루트만 생성되었다면, 이 메서드는 확장된 행을 처리하도록 오버라이드 되어야 합니다. 더 많은 내용은 아래 TreeView 초기화를 참조하십시오.
이 다이어그램은 TreeView의 수명 주기 동안 BuildRoot
와 BuildRows
이벤트 메서드의 순서와 반복을 요약합니다. BuildRoot
메서드는 Reload
가 호출될 때마다 한 번 호출되는 점에 유의하십시오. BuildRows
는 Reload
(BuildRoot
직후) 시에 한 번 그리고 TreeViewItem
이 확장되거나 접힐 때마다 호출되기 때문에 더 자주 호출됩니다.
TreeView는 Reload
메서드가 TreeView 오브젝트로부터 호출되었을 때 초기화됩니다.
TreeView를 설정하는 방법은 두 가지가 있습니다.
트리 전체 생성 - 모든 아이템을 위한 TreeViewItem
을 트리 모델 데이터에 생성합니다. 이 방법이 기본값이며 설정에 더 적은 코드를 요구합니다. 트리 전체는 BuildRoot가 TreeView 오브젝트로부터 호출되었을 때 만들어집니다.
확장된 아이템만 생성 - 이 방법은 사용자가 BuildRows
를 오버라이드하여 수동으로 보여지는 행을 컨트롤해야 하고 BuildRoot
는 루트 TreeViewItem
생성을 위해서만 사용해야 합니다. 이 방법은 큰 데이터 세트 또는 자주 바뀌는 데이터에 가장 적합합니다.
첫 번째 방법은 작은 데이터 세트 또는 자주 바뀌지 않는 데이터에 사용합니다. 두 번째 방법은 큰 데이터 세트 또는 자주 바뀌는 데이터에 사용합니다. 트리 전체를 생성하는 것보다 확장된 아이템만 생성하는 것이 더 빠르기 때문입니다.
TreeViewItem을 설정하는 방법은 세 가지가 있습니다.
시작할 때 TreeViewItem
을 자식, 부모, 뎁스를 초기화하여 생성합니다.
TreeViewItem
을 부모와 자식과 함께 생성한 다음 SetupDepthsFromParentsAndChildren을 사용하여 뎁스를 설정합니다.
TreeViewItem
을 뎁스 정보만 가지고 생성한 다음 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
을 호출하면 TreeViewItem
의 부모와 자식 데이터가 설정됩니다.
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;
}
SimpleTreeView
클래스를 위한 얼터너티브 BuildRoot
메서드다음 예제는 SimpleTreeView
를 포함하는 EditorWindow
를 보여줍니다. 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; }
}
}
다음 예제는 다중 열 GUI를 어떻게 얻는지 보여주는 MultiColumnTreeView
클래스의 일부분을 보여줍니다. 소스 코드 전체는 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 메서드를 오버라이드해야 합니다.
보호된 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, and 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;
}
}
Q: TreeView 서브클래스에 함수 BuildRoot와 RowGUI가 있습니다. RowGUI
가 빌드 함수에 추가된 모든 TreeViewItem
을 위해 호출됩니까? 아니면 스크롤 뷰에서 화면에 보이는 아이템을 위해서만 호출됩니까?
A: RowGUI
는 화면에 보이는 아이템을 위해서만 호출됩니다. 예를 들어, 10,000개의 아이템이 있다면, 화면에 보이는 20개 아이템만 RowGUI
를 호출합니다.
Q: 화면에 보이는 행의 인덱스를 얻을 수 있습니까?
A: 네, GetFirstAndLastVisibleRows 메서드를 사용하면 됩니다.
Q: BuildRows에 만들어진 행의 리스트를 얻을 수 있습니까?
A: 네, GetRows 메서드를 이용합니다.
Q: 모든 오버라이드된 함수가 base.Method
를 호출해야 됩니까?
A: 메서드가 사용자가 확장하고자 하는 기본 동작이 있는 경우에만 그렇습니다.
Q: 트리가 아닌 아이템 리스트만 만들고 싶습니다. 루트를 생성해야 합니까?
A: 네, 언제나 루트가 있어야 합니다. 루트 아이템을 생성한 다음 root.children = rows
로 설정하여 빠르게 설정할 수 있습니다.
Q: 행에 토글을 추가했습니다. 왜 클릭했을 때 메시지가 해당 행으로 가지 않습니까?
A: 기본적으로, 마우스로 누를 때 행의 콘텐츠가 사용되지 않는 경우에만 행을 선택할 수 있습니다. 이 경우에는 토글이 이벤트를 사용합니다. 이 문제를 해결하려면 SelectionClick 메서드를 사용한 후 토글 버튼을 호출하십시오.
Q: 모든 RowGUI
메서드가 호출되기 전 또는 후에 사용할 수 있는 메서드가 있습니까?
A: 네, API 관련 문서 BeforeRowsGUI 및 AfterRowsGUI를 참조하십시오.
Q: 키 포커스를 API에서 TreeView로 돌리는 간단한 방법이 있습니까? 행에서 FloatField를 선택하면, 선택된 행은 회색으로 변합니다. 어떻게 다시 파란색으로 바꿀 수 있습니까?
A: 파란색은 어느 행에 키 포커스가 있는지를 나타냅니다. FloatField에 포커스가 있기 때문에, TreeView는 포커스를 잃게 되므로, 이것은 의도된 동작입니다. 필요한 경우에는 GUIUtility.keyboardControl = treeViewControlID
로 설정합니다.
Q: 어떻게 id
에서 TreeViewItem
으로 전환할 수 있습니까?
A: FindItem 또는 FindRows를 사용합니다.
Q: 사용자가 TreeView에서 선택한 것을 바꿨을 때, 어떻게 콜백을 받습니까?
A: SelectionChanged 메서드를 오버라이드합니다(기타 유용한 콜백: DoubleClickedItem 및 ContextClickedItem).