Using UiLists in Blender
Hello fellow Pythonistas, in this tutorial we’ll find out how to use UILists in our scripts and add-ons. If your users need to handle large amounts (or variable) amounts of data, this is the widget you want.
While you could just use a loop to draw widgets UILists have several benefits over custom-made solutions, like filtering, searching and managing space correctly (less scrolling). It’s also how it’s done throughout the UI, so you can stay consistent with the rest of Blender.
By the way, I’m assuming you already know your way around the bpy and Python. This is an intermediate tutorial.
Define the properties
First we have to create some properties to hold the data on our list first. If we are populating the list with data of our own, we’ll need to define the structure of the items in the list. You don’t need to define an item if you’re using existing datablocks like materials, or textures.
class ListItem(PropertyGroup):
"""Group of properties representing an item in the list."""
name: StringProperty(
name="Name",
description="A name for this item",
default="Untitled")
random_prop: StringProperty(
name="Any other property you want",
description="",
default="")
We’ll also need a Collection Property that will hold our list data, and an Integer property to hold the index number. Let’s add them in our register() function.
def register():
bpy.types.Scene.my_list = CollectionProperty(type = ListItem)
bpy.types.Scene.list_index = IntProperty(name = "Index for my_list",
default = 0)
Make the list widget
Now we can create the actual widget by extending the UIList class. The UIList has one method (that we care about) called draw_item(). This method is called to draw each item in the list and it works pretty much like any other drawing callback in Blender. You can use labels, check boxes, operator buttons, etc.
draw_item() includes several parameters we can use:
- data: Object containing the collection property (in our case, the scene)
- item: The current item being drawn
- icon: Calculated icon for the current item as an integer (could be one of Blender’s or an icon for a material/texture/etc. depending on the item)
- active_data: Object containing the active property of the collection (in our case, the scene too)
- active_propname: The name of the active property (index). In our case, it would be “list_index”)
- index: Index of the current item (this one is optional)
Don’t forget to register your UIList subclasses, or Blender won’t know about them.
class MY_UL_List(UIList):
"""Demo UIList."""
def draw_item(self, context, layout, data, item, icon, active_data,
active_propname, index):
# We could write some code to decide which icon to use here...
custom_icon = 'OBJECT_DATAMODE'
# Make sure your code supports all 3 layout types
if self.layout_type in {'DEFAULT', 'COMPACT'}:
layout.label(text=item.name, icon = custom_icon)
elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER'
layout.label(text="", icon = custom_icon)
# ( inside register() )
bpy.utils.register_class(MY_UL_List)
Now we can call it in our UI with the template_list() function.
row = layout.row()
row.template_list("MY_UL_List", "The_List", scene,
"my_list", scene, "list_index")
Let users modify the list
What good is a list if users can’t do anything with it? We’ll let users add, delete and move items up and down. These are the most basic operations, you can easily use them as a template to make your own.
Let’s begin by adding the operators.
class LIST_OT_NewItem(Operator):
"""Add a new item to the list."""
bl_idname = "my_list.new_item"
bl_label = "Add a new item"
def execute(self, context):
context.scene.my_list.add()
return{'FINISHED'}
The add operator is pretty straightforward. Just call the add() method in the list.
class LIST_OT_DeleteItem(Operator):
"""Delete the selected item from the list."""
bl_idname = "my_list.delete_item"
bl_label = "Deletes an item"
@classmethod
def poll(cls, context):
return context.scene.my_list
def execute(self, context):
my_list = context.scene.my_list
index = context.scene.list_index
my_list.remove(index)
context.scene.list_index = min(max(0, index - 1), len(my_list) - 1)
return{'FINISHED'}
For the delete op we first have to check we have something to delete. We use the poll() method to check that there’s at least one item in the list. The cool thing about using poll() is that you it will also disable the operator button in the ui automatically.
Deleting is done by calling remove() with the list index (which will be whatever we have selected in the ui).
Finally we have to fix the index. If we delete at the end of the list the index will have an invalid value (larger than the length of the list), so we always move it back one position unless it’s zero.
class LIST_OT_MoveItem(Operator):
"""Move an item in the list."""
bl_idname = "my_list.move_item"
bl_label = "Move an item in the list"
direction = bpy.props.EnumProperty(items=(('UP', 'Up', ""),
('DOWN', 'Down', ""),))
@classmethod
def poll(cls, context):
return context.scene.my_list
def move_index(self):
""" Move index of an item render queue while clamping it. """
index = bpy.context.scene.list_index
list_length = len(bpy.context.scene.my_list) - 1 # (index starts at 0)
new_index = index + (-1 if self.direction == 'UP' else 1)
bpy.context.scene.list_index = max(0, min(new_index, list_length))
def execute(self, context):
my_list = context.scene.my_list
index = context.scene.list_index
neighbor = index + (-1 if self.direction == 'UP' else 1)
my_list.move(neighbor, index)
self.move_index()
return{'FINISHED'}
Moving an item around takes some more consideration because we have to make sure the index stays valid.
The move() method allows us to swap items at two different indexes. We only have to figure out which index to swap with. Remember we start counting from zero, increasing downwards.
Finally we call our own move_index() method to change the index while clamping it between 0 and the list length.

Item Data
Showing data from a list item is easy too, just use the index. Don’t forget to make sure it’s valid though.
if scene.list_index >= 0 and scene.my_list:
item = scene.my_list[scene.list_index]
row = layout.row()
row.prop(item, "name")
row.prop(item, "random_property")
The Final code
import bpy
from bpy.props import StringProperty, IntProperty, CollectionProperty
from bpy.types import PropertyGroup, UIList, Operator, Panel
class ListItem(PropertyGroup):
"""Group of properties representing an item in the list."""
name: StringProperty(
name="Name",
description="A name for this item",
default="Untitled")
random_prop: StringProperty(
name="Any other property you want",
description="",
default="")
class MY_UL_List(UIList):
"""Demo UIList."""
def draw_item(self, context, layout, data, item, icon, active_data,
active_propname, index):
# We could write some code to decide which icon to use here...
custom_icon = 'OBJECT_DATAMODE'
# Make sure your code supports all 3 layout types
if self.layout_type in {'DEFAULT', 'COMPACT'}:
layout.label(text=item.name, icon = custom_icon)
elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER'
layout.label(text="", icon = custom_icon)
class LIST_OT_NewItem(Operator):
"""Add a new item to the list."""
bl_idname = "my_list.new_item"
bl_label = "Add a new item"
def execute(self, context):
context.scene.my_list.add()
return{'FINISHED'}
class LIST_OT_DeleteItem(Operator):
"""Delete the selected item from the list."""
bl_idname = "my_list.delete_item"
bl_label = "Deletes an item"
@classmethod
def poll(cls, context):
return context.scene.my_list
def execute(self, context):
my_list = context.scene.my_list
index = context.scene.list_index
my_list.remove(index)
context.scene.list_index = min(max(0, index - 1), len(my_list) - 1)
return{'FINISHED'}
class LIST_OT_MoveItem(Operator):
"""Move an item in the list."""
bl_idname = "my_list.move_item"
bl_label = "Move an item in the list"
direction: bpy.props.EnumProperty(items=(('UP', 'Up', ""),
('DOWN', 'Down', ""),))
@classmethod
def poll(cls, context):
return context.scene.my_list
def move_index(self):
""" Move index of an item render queue while clamping it. """
index = bpy.context.scene.list_index
list_length = len(bpy.context.scene.my_list) - 1 # (index starts at 0)
new_index = index + (-1 if self.direction == 'UP' else 1)
bpy.context.scene.list_index = max(0, min(new_index, list_length))
def execute(self, context):
my_list = context.scene.my_list
index = context.scene.list_index
neighbor = index + (-1 if self.direction == 'UP' else 1)
my_list.move(neighbor, index)
self.move_index()
return{'FINISHED'}
class PT_ListExample(Panel):
"""Demo panel for UI list Tutorial."""
bl_label = "UI_List Demo"
bl_idname = "SCENE_PT_LIST_DEMO"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "scene"
def draw(self, context):
layout = self.layout
scene = context.scene
row = layout.row()
row.template_list("MY_UL_List", "The_List", scene,
"my_list", scene, "list_index")
row = layout.row()
row.operator('my_list.new_item', text='NEW')
row.operator('my_list.delete_item', text='REMOVE')
row.operator('my_list.move_item', text='UP').direction = 'UP'
row.operator('my_list.move_item', text='DOWN').direction = 'DOWN'
if scene.list_index >= 0 and scene.my_list:
item = scene.my_list[scene.list_index]
row = layout.row()
row.prop(item, "name")
row.prop(item, "random_prop")
def register():
bpy.utils.register_class(ListItem)
bpy.utils.register_class(MY_UL_List)
bpy.utils.register_class(LIST_OT_NewItem)
bpy.utils.register_class(LIST_OT_DeleteItem)
bpy.utils.register_class(LIST_OT_MoveItem)
bpy.utils.register_class(PT_ListExample)
bpy.types.Scene.my_list = CollectionProperty(type = ListItem)
bpy.types.Scene.list_index = IntProperty(name = "Index for my_list",
default = 0)
def unregister():
del bpy.types.Scene.my_list
del bpy.types.Scene.list_index
bpy.utils.unregister_class(ListItem)
bpy.utils.unregister_class(MY_UL_List)
bpy.utils.unregister_class(LIST_OT_NewItem)
bpy.utils.unregister_class(LIST_OT_DeleteItem)
bpy.utils.unregister_class(LIST_OT_MoveItem)
bpy.utils.unregister_class(PT_ListExample)
if __name__ == "__main__":
register()
That’s it guys! You now have a basic but fully functional UIList. If you’re interested in more advanced topics like custom filters you can check the templates in the text editor (templates > python > ui list).
28 Comments
Thanks this tutorial looks great. Only suggestion, maybe join the finished script to see it work before decript/learn.
regards,
–
tmaes
Hey, good idea! I’ve posted the a script at the end.
thanks
The final code greate some errors: LIST_OT_DeleteItem.…
Fixed, thanks for spotting that!
I use blender 2.76b.
when i run the code in the texteditor i have some trouble with the props.
And it don´t want to register the my_list…
Can you check this?
Sorry about that. I don’t know why the editor has been eating so many lines and idents. It’s working now, I also fixed the UI labels.
Is the CollectionProperty doing the same job of PointerPropery, I have seen several examples and they seem interchangable, do you mind elaborating?
A PointerPropery points to a single instance of a PropertyGroup, while a CollectionProperty holds multiple instances. If you’re only going to have one instance they could probably be interchangeable but in the case of a list you have to use a CollectionProperty.
PS how would we implement the double or ctrl click function to update within the list? I find that works a bit nicer or more convenient
You would have to use a layout.prop() without emboss instead of a label: layout.prop(item, ‘name’, text=”, emboss=False, icon=icon)
Been a while and was looking for info about pointergroups and pointerproperties and all.
PS there was another issue, the StringProperty random_property in PT_ListExample. It should be random_prop as you declared the StringProperty in the beginning
Fixed, Thanks for mentioning!
Hi
I tried this under 2.83 While it works the listings do not show up any names, any added item int he list comes out nameless
Hi, looks like I forgot to update this one for 2.8x. Fixed all the code, the only change is that the labels’ text has to be passed as a keyword argument like this: layout.label(text=item.name, icon = custom_icon).
Thanks for letting me know!
Hi
That works thank you!
Hi Diego
Thanks for this tutorial, simple and well explained 🙂
I would like to initialize the list with data, but I can’t find how to do it.
For example, from a variable:
MyVar = [‘azerty’, ‘qsdfgh’, ‘wxcvbn’, ‘poiuyt’, ‘mlkjhg’]
Can you help me?
Thanks
Hi Sarka, you would need to initialize them in the CollectionProperty you use to hold the values for the list. You could do this in the register() function of your addon, or at any point after you have registered the collection property into a variable (something like bpy.types.Scene = my_collection). Then you can call my_collection.add(), which returns a new item in the collection, and set the different properties there.
Thank you for your quick response Diego.
I will test and I will come back to you if I can’t 🙂
I’m starting in blender programming 😉
I used your example to test.
In the Register function, the CollectionProperty is declared with the line “bpy.types.Scene.my_list = CollectionProperty(type = ListItem)”. I added the line “bpy.types.Scene.my_list.add()”, but I get the error “AttributeError: ‘tuple’ object has no attribute ‘add’ ”.
Sorry i’m very newbie 🙁
Ok, i found my error 🙂
Thanks a lot for your help Diego 😉
Ah you got it faster than I could reply.
Glad to hear! Welcome to Blender scripting 🙂
Hi Diego
I followed your tutorial and the directions you gave to Sarka.
In Scripting mode, everything works fine, but when I activate my addon I get an error, When the RenderSettings_Init() function is executed
Here’s the code:
#=========================
def RenderSettings_Init():
#=========================
bpy.context.scene.my_list.clear()
bpy.context.scene.active_index = 0
for f in os.listdir(rendersettings_path):
if f[-3::] == “dat”:
item = bpy.context.scene.my_list.add()
item.name = bpy.path.display_name(f).title()
item.pathname = rendersettings_path
#==================
def register():
#==================
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.my_list = CollectionProperty(type = ListItem)
bpy.types.Scene.list_index = IntProperty(name = “Index for my_list”, default = 0)
bpy.types.Scene.active_index = IntProperty(name = “Active settings”, default = 0)
RenderSettings_Init()
Here traceback:
Traceback (most recent call last):
File “/usr//share/bforartists/2.91/scripts/modules/addon_utils.py”, line 382, in enable
mod.register()
File “/home/passion3d/.config/bforartists/2.91/scripts/addons/RenderSettings/__init__.py”, line 465, in register
RenderSettings_Init()
File “/home/passion3d/.config/bforartists/2.91/scripts/addons/RenderSettings/__init__.py”, line 289, in RenderSettings_Init
bpy.context.scene.my_list.clear()
AttributeError: ‘_RestrictContext’ object has no attribute ‘scene’
Hi! hmm I see. The context in the register function doesn’t include the scene yet (makes sense actually). The other option is to “lazy load” the list, in other words, initialize it when it is drawn for the first time. There’s two ways you can do that: you can add a flag (set to True initially) or you can check if the list is empty. In either case you would add this as this at the top of the draw() function in the UIList class. Something like:
if first_run:
RenderSettings_Init()
first_run = False
Hi
Ok thanks Diego 😉
import bpy
class ListItem(bpy.types.PropertyGroup):
“””
Group of properties representing an item in the list.
“””
name: bpy.props.StringProperty(name=‘Name’, description=‘A name for this item’, default=‘Untitled’)
random_prop: bpy.props.StringProperty(name=‘Any other property you want’, description=”, default=”)
class MY_UL_List(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
custom_icon = ‘OBJECT_DATAMODE’
if self.layout_type in {‘DEFAULT’, ‘COMPACT’}:
layout.label(text=item.name, icon=custom_icon)
elif self.layout_type in {‘GRID’}:
layout.alignment = ‘CENTER’
layout.label(text=”, icon=custom_icon)
class LIST_OT_NEW_ITEM(bpy.types.Operator):
bl_idname = ‘my_list.new_item’
bl_label = ‘Add a new item’
def execute(self, context):
context.scene.my_list.add()
return {‘FINISHED’}
class LIST_OT_DELETE_ITEM(bpy.types.Operator):
“””
Delete the selected item from the list.
“””
bl_idname = ‘my_list.delete_item’
bl_label = ‘Deletes an item’
@classmethod
def poll(cls, context):
return context.scene.my_list
def execute(self, context):
my_list = context.scene.my_list
index = context.scene.list_index
my_list.remove(index)
context.scene.list_index = min(max(0, index — 1), len(my_list) — 1)
return {‘FINISHED’}
class LIST_OT_MOVE_ITEM(bpy.types.Operator):
“””
Move an item in the list.
“””
bl_idname = ‘my_list.move_item’
bl_label = ‘Move an item in the list’
direction: bpy.props.EnumProperty(
items=(
(‘UP’, ‘Up’, ”),
(‘DOWN’, ‘Down’, ”),
)
)
@classmethod
def poll(cls, context):
return context.scene.my_list
def move_index(self):
“””
Move index of an item render queue while clamping it.
“””
index = bpy.context.scene.list_index
list_length = len(bpy.context.scene.my_list) — 1 # (index starts at 0)
new_index = index + (-1 if self.direction == ‘UP’ else 1)
bpy.context.scene.list_index = max(0, min(new_index, list_length))
def execute(self, context):
my_list = context.scene.my_list
index = context.scene.list_index
neighbor = index + (-1 if self.direction == ‘UP’ else 1)
my_list.move(neighbor, index)
self.move_index()
return {‘FINISHED’}
class PT_LIST_EXAMPLE(bpy.types.Panel):
“””
Demo panel for UI list Tutorial.
“””
bl_label = ‘UI_List Demo’
bl_idname = ‘SCENE_PT_LIST_DEMO’
bl_space_type = ‘PROPERTIES’
bl_region_type = ‘WINDOW’
bl_context = ‘scene’
def draw(self, context):
layout = self.layout
scene = context.scene
row = layout.row()
row.template_list(‘MY_UL_List’, ‘The_List’, scene, ‘my_list’, scene, ‘list_index’)
row = layout.row()
row.operator(‘my_list.new_item’, text=‘NEW’)
row.operator(‘my_list.delete_item’, text=‘REMOVE’)
row.operator(‘my_list.move_item’, text=‘UP’).direction = ‘UP’
row.operator(‘my_list.move_item’, text=‘DOWN’).direction = ‘DOWN’
if scene.list_index >= 0 and scene.my_list:
item = scene.my_list[scene.list_index]
row = layout.row()
row.prop(item, ‘name’)
row.prop(item, ‘random_prop’)
def register():
bpy.utils.register_class(ListItem)
bpy.utils.register_class(MY_UL_List)
bpy.utils.register_class(LIST_OT_NEW_ITEM)
bpy.utils.register_class(LIST_OT_DELETE_ITEM)
bpy.utils.register_class(LIST_OT_MOVE_ITEM)
bpy.utils.register_class(PT_LIST_EXAMPLE)
bpy.types.Scene.my_list = bpy.props.CollectionProperty(type=ListItem)
bpy.types.Scene.list_index = bpy.props.IntProperty(name=‘Index for my_list’, default=0)
def unregister():
del bpy.types.Scene.my_list
del bpy.types.Scene.list_index
bpy.utils.unregister_class(ListItem)
bpy.utils.unregister_class(MY_UL_List)
bpy.utils.unregister_class(LIST_OT_NEW_ITEM)
bpy.utils.unregister_class(LIST_OT_DELETE_ITEM)
bpy.utils.unregister_class(LIST_OT_MOVE_ITEM)
bpy.utils.unregister_class(PT_LIST_EXAMPLE)
if __name__ == ‘__main__’:
register()
(for ctrl+c, ctrl+v purposes)