Hello @pim,
In addition to my answer via mail, I will also answer here, as this might be interesting for the rest of the community.
Cheers,
Ferdinand
So, the question was here "Why does my tree view not open with the right size?". The easy answer to this is that:
You did neither set a minimum size for the dialog in GeDialog.Open().
Nor one for the tree view itself via GeDialog.AddCustomGui().
Both in conjunction did result in your dialog collapsing down to zero height.
What can I do?
Not much, the TreeViewCustomGui is not designed to scale to the size of its content. The underlying question is what you expect to happen here.
a. Just have the tree view have some fixed minimum size, regardless of its content.
b. Have the tree view initialize automatically to size, i.e., when the view has 10 items upon opening, it should have exactly 10 items height.
When it is (a.) what you want, then this is easily doable with the minimum size passed to GeDialog.AddCustomGui(). When it is (b.), then you are more or less out of luck, as a tree view cannot scale automatically to the size of its content.
You can adjust the minimum size dynamically based on the content which is going to be placed in the tree view, but when the content changes, you will have to flush your layout in order to be able to set a new minimum size.
On a practical level it is also not so desirable to have a tree view scale like this, as this minimum height is not well defined. Should it be all items, or just all top level items, i.e., fully collapsed or fully expanded (which is implied by your example as all nodes start out as expanded). Let's say we choose fully collapsed. What happens when you have so many root nodes that the tree view will not fit on screen when making space for all root nodes.
Example
Result
The dialog is set to have a minimum height which matches the total number of nodes in it.
58d6b1aa-de83-484f-9d48-d6a0d327a98f-image.png
Code
I had to cut here a bit, but the relevant parts are:
class TreeNode:
# ...
def __len__(self):
"""(f_hoppe): Counts all descendants of this node, including the node itself.
Implemented fully recursively. Should be implemented iteratively for production due to stack
overflows and Python's recursion limit preventing them. Or the data should be acquired
when textures are collected.
"""
count: int = 1
for child in self.children:
count += len(child)
class ListView(c4d.gui.TreeViewFunctions):
COLUMN_COUNT: int = 1
MIN_LINE_HEIGHT: int = 24
MIN_WIDTH: int = 500
def __init__(self):
# The root nodes of the tree view.
self._rootNodes: list[TreeNode] = []
def __len__(self):
"""(f_hoppe): Returns the number of tree nodes in the instance.
"""
return sum([len(node) for node in self._rootNodes])
def GetMinSize(self) -> tuple[int, int]:
"""(f_hoppe): Returns the minimum GUI size for the data of this ListView instance.
"""
# But all these classic API pixel values are quite wonky anyways and the tree view does
# many custom things. So we must do some ugly magic number pushing. Subtracting nine units
# from the actual height of each row gave me sort of the best results, but the tree view
# GUI does not scale linearly in height with the number of rows. Meaning that what looks
# good for 5 items might not look good for 50 items.
return (ListView.MIN_WIDTH, (ListView.MIN_LINE_HEIGHT - 9) * len(self))
def GetColumnWidth(self, root: TreeNode, userdata: None, obj: TreeNode,
col: int, area: c4d.gui.GeUserArea):
"""(f_hoppe): This cannot be a constant value, as we will otherwise clip data.
"""
return area.DrawGetTextWidth(obj.textureName) + 24
def GetLineHeight(self, root, userdata, obj, col, area):
"""(f_hoppe): Used constant value.
"""
return ListView.MIN_LINE_HEIGHT
# ...
class SimpleDialog (c4d.gui.GeDialog):
ID_TRV_TEXTURES: int = 1000
def __init__(self) -> None:
self._treeView: c4d.gui.TreeViewCustomGui = None
# This builds the tree node data so that self._listView._rootNodes holds the top level
# node(s) for the tree managed by this ListView instance.
self._listView: ListView = self.GetTree()
super().__init__()
def CreateLayout(self) -> bool:
"""(f_hoppe): I substantially rewrote this.
"""
# Because we already initialized the ListView data, we can use it to compute the minimum
# size of the gadget.
w, h = self._listView.GetMinSize()
print(f"{self._listView.GetMinSize() = }, {len(self._listView) = }")
# Use these values to define a default size so that it shows all columns. What you want to
# be done here is sort of not intended by the TreView GUI, although admittedly desirable.
# The closest thing we can do is set the minimum size for the gadget, so that all lines
# will fit into it. This will then ofc also have the side effect that the GUI cannot be
# scaled down beyond this point. You might want to implement ListView.GetMinSize() in a
# different manner, so that it does not take into account all nodes and instead just the
# top level nodes.
self._treeView = self.AddCustomGui(
id=SimpleDialog.ID_TRV_TEXTURES,
pluginid=c4d.CUSTOMGUI_TREEVIEW,
name="",
flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT,
minw=w, minh=h,
customdata=SimpleDialog.SETTINGS_TREEVIEW)
if not isinstance(self._treeView, c4d.gui.TreeViewCustomGui):
raise MemoryError(f"Could not allocate tree view.")
# If you want to do this at runtime, i.e., load a new texture path, you would have to
# call GeDialog.LayoutChanged() on the layout group which contains the tree view, add
# the tree view again (with new min size values), and then call GeDialog.LayoutChanged()
# on the group. It might be easier to just live with a fixed minimum size just as
# (300, 300)
return True
# ...