Hello @mogh,
Thank you for reaching out to us. I really must stress here again that we require executable code to be posted, as lined out in our Forum Guidelines. While there are sometimes questions which can be answered abstractly, both the users and we ourselves usually prefer concrete answers, i.e., something executable. When users do not provide code, doing this will be much harder for us, as we have then not only to write that code, but also do a lot of guessing.
About your question:
You must measure the objects that are passed to GetColumnWidth
. The method also passes a GeUserArea
which is conveniently setup to measure string widths. I have used an older tree view example written by Maxime (see end of the posting for the full listing of the modified code) to demonstrate the approach. The core of the example is its method GetColumnWidth
:
def GetColumnWidth(self, root, userdata, obj, col, area):
"""Measures the width of cells.
Although this function is called #GetColumnWidth and has a #col, it is
not only executed by column but by cell. So, when there is a column
with items requiring the width 5, 10, and 15, then there is no need
for evaluating all items. Each item can return its ideal width and
Cinema 4D will then pick the largest value.
Args:
root (any): The root node of the tree view.
userdata (any): The user data of the tree view.
obj (any): The content of the current cell.
col (int): The index of the column #obj is contained in.
area (GeUserArea): An already initialized GeUserArea to measure
the width of strings.
Returns:
TYPE: Description
"""
# The default width of a column is 80 units.
width = 80
# For all calls for cells which contain a string item ...
if isinstance(obj, str):
# Replace the width with the text width. area is a prepopulated
# user area which has already setup all the font stuff, we can
# measure right away.
width = area.DrawGetTextWidth(obj)
return width
If this does not help in your case, we must ask for a concrete code example. As a side note - in our C++ API GUIs like yours are a much less labor-intensive task, since list views and their accompanying helper class SimpleListView can be used there.
Cheers,
Ferdinand
"""Example for setting the column width of a tree view by the width of its
items.
The only changes which have been made in comparison to the original code can
be found in TextureObject.__init__(), TextureObject.RandomString() and
ListView.GetColumnWidth(). The tree view items #TextureObject now generate
random #texturePath and #otherData strings and the treeview scales its columns
to the length of these strings.
Adapted from:
https://plugincafe.maxon.net/topic/10654/14102_using-customgui-listview/2
"""
import c4d
import random
# Be sure to use a unique ID obtained from http://www.plugincafe.com/.
PLUGIN_ID = 1000010 # TEST ID ONLY
# TreeView Column IDs.
ID_CHECKBOX = 1
ID_NAME = 2
ID_OTHER = 3
# A tuple of characters to select from (a-z).
CHARACTERS = tuple(chr (n) for n in range(97, 122))
class TextureObject(object):
"""
Class which represent a texture, aka an Item in our list
"""
def __init__(self):
self.texturePath = TextureObject.RandomString(5, 10)
self.otherData = TextureObject.RandomString(10, 20)
self._selected = False
@staticmethod
def RandomString(minLength: int, maxLength: int) -> str:
"""Returns a string of random characters with a length between
#minLength and #maxLength.
The characters are taken from the 97 (a) to 122 (z) ASCII range.
"""
return "".join(
(random.choice(CHARACTERS) for _ in range(minLength, maxLength)))
@property
def IsSelected(self):
return self._selected
def Select(self):
self._selected = True
def Deselect(self):
self._selected = False
def __repr__(self):
return str(self)
def __str__(self):
return self.texturePath
class ListView(c4d.gui.TreeViewFunctions):
def __init__(self):
"""
"""
# Add ten mock data texture objects.
self.listOfTexture = [TextureObject() for _ in range(10)]
def IsResizeColAllowed(self, root, userdata, lColID):
return True
def IsTristate(self, root, userdata):
return False
def GetColumnWidth(self, root, userdata, obj, col, area):
"""Measures the width of cells.
Although this function is called #GetColumnWidth and has a #col, it is
not only executed by column but by cell. So, when there is a column
with items requiring the width 5, 10, and 15, then there is no need
for evaluating all items. Each item can return its ideal width and
Cinema 4D will then pick the largest value.
Args:
root (any): The root node of the tree view.
userdata (any): The user data of the tree view.
obj (any): The content of the current cell.
col (int): The index of the column #obj is contained in.
area (GeUserArea): An already initialized GeUserArea to measure
the width of strings.
Returns:
TYPE: Description
"""
# The default width of a column is 80 units.
width = 80
# For all calls for cells which contain a string item ...
if isinstance(obj, str):
# Replace the width with the text width. area is a prepopulated
# user area which has already setup all the font stuff, we can
# measure right away.
width = area.DrawGetTextWidth(obj)
return width
def IsMoveColAllowed(self, root, userdata, lColID):
# The user is allowed to move all columns.
# TREEVIEW_MOVE_COLUMN must be set in the container of AddCustomGui.
return True
def GetFirst(self, root, userdata):
"""
Return the first element in the hierarchy, or None if there is no element.
"""
rValue = None if not self.listOfTexture else self.listOfTexture[0]
return rValue
def GetDown(self, root, userdata, obj):
"""
Return a child of a node, since we only want a list, we return None everytime
"""
return None
def GetNext(self, root, userdata, obj):
"""
Returns the next Object to display after arg:'obj'
"""
rValue = None
currentObjIndex = self.listOfTexture.index(obj)
nextIndex = currentObjIndex + 1
if nextIndex < len(self.listOfTexture):
rValue = self.listOfTexture[nextIndex]
return rValue
def GetPred(self, root, userdata, obj):
"""
Returns the previous Object to display before arg:'obj'
"""
rValue = None
currentObjIndex = self.listOfTexture.index(obj)
predIndex = currentObjIndex - 1
if 0 <= predIndex < len(self.listOfTexture):
rValue = self.listOfTexture[predIndex]
return rValue
def GetId(self, root, userdata, obj):
"""
Return a unique ID for the element in the TreeView.
"""
return hash(obj)
def Select(self, root, userdata, obj, mode):
"""
Called when the user selects an element.
"""
if mode == c4d.SELECTION_NEW:
for tex in self.listOfTexture:
tex.Deselect()
obj.Select()
elif mode == c4d.SELECTION_ADD:
obj.Select()
elif mode == c4d.SELECTION_SUB:
obj.Deselect()
def IsSelected(self, root, userdata, obj):
"""
Returns: True if *obj* is selected, False if not.
"""
return obj.IsSelected
def SetCheck(self, root, userdata, obj, column, checked, msg):
"""
Called when the user clicks on a checkbox for an object in a
`c4d.LV_CHECKBOX` column.
"""
if checked:
obj.Select()
else:
obj.Deselect()
def IsChecked(self, root, userdata, obj, column):
"""
Returns: (int): Status of the checkbox in the specified *column* for *obj*.
"""
if obj.IsSelected:
return c4d.LV_CHECKBOX_CHECKED | c4d.LV_CHECKBOX_ENABLED
else:
return c4d.LV_CHECKBOX_ENABLED
def GetName(self, root, userdata, obj):
"""
Returns the name to display for arg:'obj', only called for column of type LV_TREE
"""
return str(obj) # Or obj.texturePath
def DrawCell(self, root, userdata, obj, col, drawinfo, bgColor):
"""
Draw into a Cell, only called for column of type LV_USER
"""
if col == ID_OTHER:
name = obj.otherData
geUserArea = drawinfo["frame"]
w = geUserArea.DrawGetTextWidth(name)
h = geUserArea.DrawGetFontHeight()
xpos = drawinfo["xpos"]
ypos = drawinfo["ypos"] + drawinfo["height"]
drawinfo["frame"].DrawText(name, xpos, ypos - h * 1.1)
def DoubleClick(self, root, userdata, obj, col, mouseinfo):
"""
Called when the user double-clicks on an entry in the TreeView.
Returns:
(bool): True if the double-click was handled, False if the
default action should kick in. The default action will invoke
the rename procedure for the object, causing `SetName()` to be
called.
"""
c4d.gui.MessageDialog("You clicked on " + str(obj))
return True
def DeletePressed(self, root, userdata):
"Called when a delete event is received."
for tex in reversed(self.listOfTexture):
if tex.IsSelected:
self.listOfTexture.remove(tex)
class TestDialog(c4d.gui.GeDialog):
_treegui = None # Our CustomGui TreeView
_listView = ListView() # Our Instance of c4d.gui.TreeViewFunctions
def CreateLayout(self):
# Create the TreeView GUI.
customgui = c4d.BaseContainer()
customgui.SetBool(c4d.TREEVIEW_BORDER, c4d.BORDER_THIN_IN)
customgui.SetBool(c4d.TREEVIEW_HAS_HEADER, True) # True if the tree view may have a header line.
customgui.SetBool(c4d.TREEVIEW_HIDE_LINES, False) # True if no lines should be drawn.
customgui.SetBool(c4d.TREEVIEW_MOVE_COLUMN, True) # True if the user can move the columns.
customgui.SetBool(c4d.TREEVIEW_RESIZE_HEADER, True) # True if the column width can be changed by the user.
customgui.SetBool(c4d.TREEVIEW_FIXED_LAYOUT, True) # True if all lines have the same height.
customgui.SetBool(c4d.TREEVIEW_ALTERNATE_BG, True) # Alternate background per line.
customgui.SetBool(c4d.TREEVIEW_CURSORKEYS, True) # True if cursor keys should be processed.
customgui.SetBool(c4d.TREEVIEW_NOENTERRENAME, False) # Suppresses the rename popup when the user presses enter.
self._treegui = self.AddCustomGui(1000, c4d.CUSTOMGUI_TREEVIEW, "", c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 300, 300, customgui)
if not self._treegui:
print ("[ERROR]: Could not create TreeView")
return False
self.AddButton(1001, c4d.BFH_CENTER, name="Add")
return True
def InitValues(self):
# Initialize the column layout for the TreeView.
layout = c4d.BaseContainer()
layout.SetLong(ID_CHECKBOX, c4d.LV_CHECKBOX)
layout.SetLong(ID_NAME, c4d.LV_TREE)
layout.SetLong(ID_OTHER, c4d.LV_USER)
self._treegui.SetLayout(3, layout)
# Set the header titles.
self._treegui.SetHeaderText(ID_CHECKBOX, "Check")
self._treegui.SetHeaderText(ID_NAME, "Name")
self._treegui.SetHeaderText(ID_OTHER, "Other")
self._treegui.Refresh()
# Set TreeViewFunctions instance used by our CUSTOMGUI_TREEVIEW
self._treegui.SetRoot(self._treegui, self._listView, None)
return True
def Command(self, id, msg):
# Click on button
if id == 1001:
# Add data to our DataStructure (ListView)
newID = len(self._listView.listOfTexture) + 1
tex = TextureObject("T{}".format(newID))
self._listView.listOfTexture.append(tex)
# Refresh the TreeView
self._treegui.Refresh()
return True
class MenuCommand(c4d.plugins.CommandData):
dialog = None
def Execute(self, doc):
if self.dialog is None:
self.dialog = TestDialog()
return self.dialog.Open(c4d.DLG_TYPE_ASYNC, PLUGIN_ID, defaulth=600, defaultw=600)
def RestoreLayout(self, sec_ref):
if self.dialog is None:
self.dialog = TestDialog()
return self.dialog.Restore(PLUGIN_ID, secret=sec_ref)
def main():
c4d.plugins.RegisterCommandPlugin(
PLUGIN_ID, "Python TreeView Example", 0, None, "Python TreeView Example", MenuCommand())
if __name__ == "__main__":
main()