CollectionProperty in AddonPreferences with 1 "Remove"-button per element

I want to draw a CollectionProperty with “Remove”-buttons for each element in the preferences panel. What is the best way to do this?

I currently use this, which is not DRY and requires me to impose an arbitrary limit on the amount of elements; oh and also it’s stupid:

# In draw method of my bpy.types.AddonPreferences-inheriting class:
        i = 0
        for element in self.myList:
            row = box.row()
            row.prop(element, "foo")
            row.prop(element, "bar")
            row.operator(f"view3d.my_addon_prefs_remove_element{i:d}", text = "-")
            i += 1

# Elsewhere:
def removeElement(index):
    prefs: MyPrefs = bpy.context.preferences.addons[MyPrefs.bl_idname].preferences
    prefs.myList.remove(index)
    return {"FINISHED"}

class RemoveElement0(bpy.types.Operator):
    bl_idname = "view3d.my_addon_prefs_remove_element0"
    bl_label = "Remove Element"
    def invoke(self, context, event):
        return removeElement(0)

class RemoveElement1(bpy.types.Operator):
    bl_idname = "view3d.my_addon_prefs_remove_element1"
    bl_label = "Remove Element"
    def invoke(self, context, event):
        return removeElement(1)

### <--- 8 more copies of RemoveElementX operator for the other indices --->

Referring to https://docs.blender.org/api/2.93/info_gotcha.html#using-operators I assume it’s not possible? It says this:

  • Can’t pass data such as objects, meshes or materials to operate on (operators use the context instead).

not really sure I understand what you’re trying to do, but couldn’t you just use a single operator and set a property?

ie)

for i in range(0,100):
    row.operator("view3d.my_operator").index = i
def removeElement(index):
    prefs: MyPrefs = bpy.context.preferences.addons[MyPrefs.bl_idname].preferences
    prefs.myList.remove(index)

class RemoveElement(bpy.types.Operator):
    bl_idname = "view3d.my_operator"
    bl_label = "Remove Element"

    index: bpy.props.IntProperty()

    def invoke(self, context, event):
        removeElement(self.index)
        return {'FINISHED'}
1 Like

I wasn’t aware that the UI is evaluated after draw() has already returned. That explains why things like

if layout.button("Click me"): #...

aren’t a thing. The immediate mode GUI systems I was already familiar with like to call their draw-methods twice: once to plan the layout (position of elements can change depending on elements that follow) and once more to deal with user input directly, expecting the same UI elements to appear in the same order.

This works fine, thanks!