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).
40 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)
This helped me grasp a lot of the problems I had in trying to understand the UIList. I’ve been combing the code for a few days and still don’t get a few key parts I need to understand.
Just what class is my_list? I see it has the functions move() and add(), but I can’t figure out where it is in the API documentation so I can study it and find out what else it has.
I see, in the command row.template_list(“MY_UL_List”, “The_List”, scene, “my_list”, scene, “list_index”) you use “The_List,” but I don’t see any references to “The_List” anywhere else in the script, so could you clarify what that represents?
Also, in LIST_OT_NewItem() you use just context.scene.my_list.add(). Maybe this would be covered in the API docs, but where is it getting what item to add?
I want to create a UIList that includes cameras within the scene. I’d be able to select cameras and add them to the list (either one at a time or several at once). So I’m not clear if I can use something like my_list.add(sel_camera) to add a camera to the list or if it has to be getting the item to add from the UIList input. (And I’m not clear how I’d specify for the UIList to use the camera names for display instead of raw object data.)
I know that’s a lot, but I’m really trying to grasp this. Blender UI stuff is a little tough to get sometimes and this seems to be the toughest GUI object of all to deal with.
Hi Hal,
- my_list is a CollectionProperty. move() and add() come from it, every other collectionproperty in Blender has them if you check with the Python console. Not sure why they are not documented in the API docs
— “The_List” in this case is an identifier. I think it’s an internal thing because I’ve never had any use for it. It has to be unique for each list though.
— my_list.add() adds a new item to the collection with default values. The type of item that is added is set when creating the CollectionProperty: CollectionProperty(type = ListItem). So the type of object is a ListItem
— To display camera names you need to change the draw_item() function of the list. Something like “layout.label(text=camera.name)”
— CollectionProperty(type = bpy.types.Camera) could do it for cameras (not tested though). But you might want to still use a custom type, and have a PointerProperty for the cameras. Just in case you want to add more data attached to them in the future. Otherwise you would have to register those things in the camera objects themselves.
— When you call add() it will add a default item. You need to catch that into a variable and set it to whatever camera you want (the active object, the active cam in the scene, etc.)
Good luck!
Just wanted to say, “Thanks!” It took me a while, but with your help here, I did get it all worked out.
Awesome! \o/
This article is awesome, exactly what I was looking for. And that you keep maintaining it after 6 years is incredible. Every question I could have possibly had, has already been asked and even answered by you. Thank you very much, Diego!
Damn, 6 years already?
No prob :D, thanks for the kind comment
The best one I’ve read about Blender python lists, I’ve tried the code but as all I’ve tried it gives a error. ” ‘Scene’ object has no attribute ‘my_list’ “. seems to be a nightmare for me at the moment to get any list to work! But as said before the best explanation I’ve read on the web about them. Thank you.
I take it back, Mr Fiddler here had changed a few things not fully understanding what I was doing and messed the whole code up. Smashing job thank you, working just fine now the Fiddler has not fiddled!!! But now I can fiddle and get my head around trying to use this for addon preferences, I’ve got it working in the preferences panels but need to get my head around referencing and storing user input strings! But a big thanks to you I can now fiddle in the direction I want to go for the addon (I think).
Kind regards and thank you,
Vinny
Hi
I’ve posted before after a succesful code run, but I cant seem to find any reference or pointers on how I would get a list to work in the addon preferences panel, I can get this code to make a list box show and work fine BUT no matter what I try I an unsuccessful at getting any of the list item added to save AS addon preferences, any pointers as to how I would achieve this, I’ve tried to us a collection property listed in the addon preferences but with errors on any way that I try?
Hi,
Followed your steps and was able to install and demo the setup. Thanks for the guide.
One question: If I wanted to access the object data which is displayed by the new Spreadsheet editor how would i go about parsing or iterating through the source? I’m trying to pull the name and position property for the object but can’t seem to find the source object / collection. Any help would be really help full.
This article has helped me a lot, since I was looking for a way for users of my add-on to create their own list. Also, right now I’m trying to figure out if there is any way for the UIList to store and retrieve the list, in other words have blender remember it as it remembers user preferences. I do have my own bpy.types.AddonPreferences class as well but I haven’t figured out how to use that together with the UIList. I was wondering if you have any advice, thanks!
At this point I stored my UIList values in a property inside my bpy.types.AddonPreferences class and blender remembers them if I press Save Preferences. However, I’m not sure how the UIList could populate the list from AddonPreferences automatically.