UE5 - Basic tree view tutorial

There’s a huge lack of tutorials and posts dealing with the Tree View container in UE5, so I set out on a mission to learn how to use them myself. This has been put together using my knowledge of Lists and some tutorials I found for Tree Views in UE4. I made this as beginner friendly as I could, but I am open to learning :smile: If you have any questions or find any better way to achieve the same results, please let me know.

I will be showing you how to make a simple interactive inventory list using tree views

1. Creating the base entry widget

First, think about what you need your tree view to show. For this tutorial I will be making a simple inventory list using tree views. I want my base entry to represent an item of clothing in said inventory.

Then, we create a WBP to serve as a base for our entries. All entries in the tree will be instances of this blueprint. In this case, I made a base entry widget that consists of:

  • CategoryIcon: An Image for the item category
  • Quantity: container with two Texts, with one static (“x”) and one variable QuantityText that will change dynamically
  • ItemName: Text that will display the entry’s name

All of these fields will have the “IsVariable” checkbox checked in. That will allow us to modify them through Blueprint.

2. Creating the entry’s data structure

Now that we have the basic widgets done, it’s time to think about data. Think of any variables you’ll need in order to properly fill out and populate your tree. In my case, I can think of these:

  • “Name”: the entry’s name - [Text]
  • “Icon”: image to be displayed for any category headers - [Image]
  • “Quantity”: how many of the item we have - [Integer]
  • “IsChild”: is this entry a category header or is it a child? - [Boolean]
  • “Depth”: how deep in the child hierarchy the entry is (0 being the parent) - [Integer]

Once you have your variables figured out, let’s create a Struct to store these data fields. Fill out the variables as needed. I also went to the Default Values tab and set up defaults for all variables.

You can find Structs inside the “Blueprint” section when creating a new object.

3. Creating the Object Class

Now, we need an Object Class to act as a link between our base entry WBP and our data fields Struct. Later, when populating the tree view, we will construct instances of this class, pass them the appropriate data and add them to the tree. The tree will then instantiate our base entry WBP using the data provided by the class.

When creating the object class, go to the “Blueprint” section, pick “Class” and then expand the lower box to show all classes. Pick “Object”.

Inside the class, we need to create a new variable of the same type as the data struct. In my case, that is S_Settings_DataFields. Make sure to check these two options on the variable’s properties:

  • Instance Editable: checked
  • Expose on Spawn: checked

4. Setting up the ObjectListEntry interface

Now we’ll need to go back to the base entry WBP. In order for it to work as an entry for our tree, it needs to implement the UserObjectListEntry interface.

Go to the Graph, then to the Class Settings button and look for the Interfaces section. Add the UserObjectListEntry to the implemented interfaces. This part is the same as how you’d set up any other kind of list.

With that, our base entry widget is implementing the UserObjectListEntry interface. Next we will need to implement the necessary interface events for it all to work correctly. First, let’s implement the most important one: OnListItemObjectSet. This will be called whenever we add a new object to the tree, so we’ll use this event to populate the newly instantiated widget with all the right variables.

To implement it, expand the INTERFACES section on the upper panel (under Graphs and Functions) and double click on the On List Item Object Set event.

~

The event will also pass a List Item Object with all the data we need, stored in its inner Struct variable. We will use this data to populate the widget. Here’s how it looks for my inventory tree:

First, we cast the ListItemObject to our object class, in my case BP_InventoryObject. Then take the object and fetch the data fields. Break the struct to grab each variable independently. What I’m doing next is manually setting any information I need:

  • Setting the right name
  • Setting the right icon
  • Setting the quantity text
  • If the item is a child, call SetUpAsChild with the necessary data:

  • Set up left padding using the Depth value
  • Hide the category header icon
  • Show the Quantity container

This child setup could be done inside the main graph too, but I wanted to separate it into a different function to keep things as clean as possible.

5. Creating the inventory screen

Next we will create a new screen to contain the tree. I called mine WBP_Inventory_Panel. We will then add the tree view to the screen, and check the IsVariable checkbox. Once the tree is added, go to the List Entries section and make sure the Entry Widget Class is set to your base entry widget. In my case, I’ll set it to the one I made: WBP_Inventory_Item.

This step will tell the tree what kind of widget to instantiate when you add a new object to it, so it is very important to pick the right one.

Once that is set up, scroll down to the Events section of the tree. In there, notice the OnGetItemChildren event. We will add a new binding to this event, and manage it through Blueprint. This event is called every time an entry needs to get its children, and without it the tree nesting wouldn’t work.

6. Managing entries through Blueprint

Adding all the entries by hand through Blueprint might be tedious, but we will have a working tree view by the end of it so bear with me! The creation of entries all happens in this WBP_Inventory_Panel widget, so go to the graph and implement the OnInitialized event.

We will set up each entry following these steps:

    1. Set up entry data and create the object class
    1. Save each entry into a variable and add them to the tree view
    1. Create any children we need for each entry

I separated the child creation process into individual functions for each category header for the sake of clarity. It is important we store each entry in a variable, as well as any children arrays. This is so the OnGetItemChildren works correctly for each entry. We need to make sure that this event doesn’t produce an infinite loop or it will crash the editor. I will go more in depth as we go through the children creation process.

Here’s a general view of the whole entry creation process:

6.1. Setting up entry data

First, we will add the ConstructObject node, and select our Object class (in my case, BP_InventoryObject). We will then drag from the DataFields variable and Make a struct. This way, we can set up each variable the entry needs. You can’t see it well because of the theme I’m using, but I filled the struct shown below like this:

  • Setting Name: Hats
  • Icon: I picked an icon showing hats
  • Is Child: false
  • Depth: 0 (it’s the category header)

6.2 Save into variables and add to tree

Pretty self explanatory! We need a reference to every entry in the tree to manage nesting properly.

6.3. Create children for entries

In order to keep things tidy, I decided to create one function for each entry that needed children. To do that, we can go to the Functions section of the graph, then add a new function. After the children are created, save them into a new variable to use later on. This is so the OnGetItemChildren tree function works properly and assigns each entry the correct children.

Incorrectly assigning children can create an infinite loop, which will make the editor crash.

At this point we can create subchildren for children of the category as well, if needed. Here’s the Tops header category, with its children and subchildren.

Every children array should be saved individually.

And here is an example of these MakeChildren functions, in this case MakeTopChildren:

As you can see, it’s the same as before: first we create the Inventory Object, then make the right data for it. All should have the right name and quantities we need, and also be set as Child of the right Depth. An interesting note, you can nest as many children as you want with tree views, so I made it so the last child (Dress Shirts) is also a parent of another subcategory. We will save this parent in its own variable, and also return an array with every child in this category.

7. The OnGetItemChildren function

We’re almost there! Now, to deal with the OnGetItemChildren function, we’ll need to create specific cases for each entry. That’s why we needed to store references to each parent.

The main thing here is that we need to return a different array of children depending on which object it is that is requesting children. In my case I have 4 entries with children, so I will check if the input object is any of them to return their specific children array. If it’s neither of those entries, I will return an empty array.

8. Conclusion

And that’s it! Now we have a working tree view :smile: I added my panel to the viewport and bound it to a key to test it out. Clicking on an entry with children will toggle its expansion, but there are ways to cheat around this if you want the tree to always be expanded. Let me know if you’d like an explanation on this as well!

Some things worth noting: I’ve been trying for a while now to use tree views in more interesting ways, using named slots to dynamically set widgets within, or doing the same “by hand” by having a few containers with different functionality (toggles, sliders, things like that) and turning them on/off by using Visible/Collapsed on the ones I wanted at any given time. However, there is a very obvious bug going on right now with tree views that makes the entries go in the wrong order as you expand and contract an entry. I’ve tried so many things, even saving every individual entry and child to separate variables and making a new array every time the GetItemChildren function is called for the tree. Still no luck, only non-changing fields such as Texts and Images remain functional and in the place they should be. I can share some of my tests in a further comment if anyone is curious, but for now I think sticking to simple fields such as Text or Images would be the best if you need tree views in your project.

Thanks for reading and I hope this was helpful to anyone digging a bit deeper into tree views!

7 Likes

Thank you so much for documenting this! I do have one question on how you managed to get sub categories / items to appear smaller the deeper their depth. I cant seem to get the entries to neatly come together as you did when shrunk any smaller than full size.

Hi! The main thing that creates the visual distinction between levels is adding padding to children elements (proportional to how deep they are inside the tree). You can see how I did this through BP in section 4 of the tutorial, but TLDR: I store a Depth value for each entry, then multiply that by a fixed amount of 50 and set the left padding of the entry accordingly.

Some other considerations!

  • The background image is small (width = 32), but set to fill in its horizontal alignment. This way it will stretch out and use the whole space of the tree. If the image is too big, setting the padding too high might cause the whole entry to shift out of the expected slot and look funky. Scaling a small image to a bigger space is more predictable and works better!
  • If you want to use a texture instead of a solid colour for the background image, set the Draw As option as Box and try to keep the actual image size relatively small.
  • The Horizontal Box used for the inner contents follows the same rule, it’s set to fill horizontally so it stretches dynamically.

I hope this helped! Let me know if you have any other questions :slight_smile:

could you provide the blueprints ?
Thanks

This was a lot of help, thanks for taking the time to write it up. I followed along and had a small hiccup with the OnInitialized event on step 6 - I assumed you meant On Entry Initialized, but that only got called when I expanded a tree. Once I moved the code for creating the objects to the widget’s Event Construct it worked fine.

Made a small improvement as well, I had a bit more complex scenario where I have any number of collections of inventories, and each inventory can have any number of categories, so following your example of hardcoding the categories wasn’t viable. I ended up adding a Children variable to my object class that’s just an array of that object (in your case it would be BP_InventoryObject).

That way, I ended up with a single array of these objects, and if I wanted to add an object as a child, I would just find the parent in the array, and create my new object, and add it to that parent object’s Children array.

This turned out to be very intuitive to work with, and it massively simplified the OnGetItemChildren function since with this change I could just cast it to my object and return the Children array.

1 Like

@LenSolla Thank you very much for the tutorial! It’s a great help. I have one question though: What type is the variable you store your categories in step 6.2? I assume you added them in the blueprint under variables tab, right?

Yes, I add them through the variables tab! In this case, their type is BPInventoryObject but it will of course depend on what you named your objects :slight_smile: