Solved embed interface

hello,guys
I'm implement some functions like model manage system, similar with asset browser, but the data come from Remote DataBase,so this is some questions on my way。

my environment
python
c4d R26
code:

import c4d

ID_NAME = 1              

PLUGIN_ID = 3000011 # TEST ID ONLY

class TreeNode(object):
    data = None
    _selected = False

    def __init__(self, data):
        self.data = data

    @property
    def Text(self):
        return self.__text

    @Text.setter
    def Text(self, value: str):
        self.__text = value

    @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 str(self.data)

class TreeView(c4d.gui.TreeViewFunctions):

    def __init__(self):
        self.listOfItems = list() # Store all objects we need to display in this list

    def IsResizeColAllowed(self, root, userdata, lColID):
        return True

    def IsTristate(self, root, userdata):
        return False

    def GetColumnWidth(self, root, userdata, obj, col, area):
        return 80  # All have the same initial 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 False

    def GetFirst(self, root, userdata):
        """
        Return the first element in the hierarchy, or None if there is no element.
        """
        rValue = None if not self.listOfItems else self.listOfItems[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.listOfItems.index(obj)
        nextIndex = currentObjIndex + 1
        if nextIndex < len(self.listOfItems):
            rValue = self.listOfItems[nextIndex]

        return rValue

    def GetPred(self, root, userdata, obj):
        """
        Returns the previous Object to display before arg:'obj'
        """
        rValue = None
        currentObjIndex = self.listOfItems.index(obj)
        predIndex = currentObjIndex - 1
        if 0 <= predIndex < len(self.listOfItems):
            rValue = self.listOfItems[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.listOfItems:
                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:
            for tex in self.listOfItems:
                tex.Deselect()
            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.Text)

    def DrawCell(self, root, userdata, obj, col, drawinfo, bgColor):
        """
        Draw into a Cell, only called for column of type LV_USER
        """
        value = obj.Text
        geUserArea = drawinfo["frame"]
        w = geUserArea.DrawGetTextWidth(value)
        h = geUserArea.DrawGetFontHeight()
        xpos = drawinfo["xpos"]
        ypos = drawinfo["ypos"] + drawinfo["height"]
        drawinfo["frame"].DrawText(value, 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.
        """
        return True

    def EmptyText(self, root: object, userdata: object) -> str:
        return "empty"

class TestTreeDialog(c4d.gui.SubDialog):
    _treegui = None # Our CustomGui TreeView
    _listView = TreeView() # Our Instance of c4d.gui.TreeViewFunctions
    ID_LISTVIEW = 10000

    def CreateLayout(self):

        if self.GroupBegin(0, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=2):
            # Create the TreeView GUI.
            customgui = c4d.BaseContainer()
            customgui.SetBool(c4d.TREEVIEW_BORDER, c4d.BORDER_THIN_OUT)
            # True if the tree view may have a header line.
            customgui.SetBool(c4d.TREEVIEW_HAS_HEADER, True)
            # True if no lines should be drawn.
            customgui.SetBool(c4d.TREEVIEW_HIDE_LINES, False)
            # True if the user can move the columns.
            customgui.SetBool(c4d.TREEVIEW_MOVE_COLUMN, True)
            # True if the column width can be changed by the user.
            customgui.SetBool(c4d.TREEVIEW_RESIZE_HEADER, True)
            # True if all lines have the same height.
            customgui.SetBool(c4d.TREEVIEW_FIXED_LAYOUT, True)
            # Alternate background per line.
            customgui.SetBool(c4d.TREEVIEW_ALTERNATE_BG, True)
            # True if cursor keys should be processed.
            customgui.SetBool(c4d.TREEVIEW_CURSORKEYS, True)
            # Suppresses the rename popup when the user presses enter.
            customgui.SetBool(c4d.TREEVIEW_NOENTERRENAME, False)
            customgui.SetBool(c4d.TREEVIEW_NO_MULTISELECT, True)
            #customgui.SetBool(c4d.TREEVIEW_RESIZABLE, True)

            self._treegui = self.AddCustomGui(
                self.ID_LISTVIEW, c4d.CUSTOMGUI_TREEVIEW, "", c4d.BFH_SCALEFIT|c4d.BFV_SCALEFIT, 50, 100, customgui)
            if not self._treegui:
                print("[ERROR]: Could not create TreeView")
                return False
            self.AddMultiLineEditText(0, c4d.BFH_SCALEFIT|c4d.BFV_SCALEFIT, 80,100)
        self.GroupEnd()
        return True

    def InitValues(self):

        #testcode
        node1 = TreeNode("1")
        node1.Text = "a1"
        self._listView.listOfItems.append(node1)

        node2 = TreeNode("1")
        node2.Text = "a2"
        self._listView.listOfItems.append(node2)

        # Initialize the column layout for the TreeView.
        layout = c4d.BaseContainer()
        layout.SetLong(ID_NAME, c4d.LV_TREE)

        self._treegui.SetLayout(1, layout)

        # Set the header titles.
        self._treegui.SetHeaderText(ID_NAME, "name")
        #self._treegui.Refresh()

        # Set TreeViewFunctions instance used by our CUSTOMGUI_TREEVIEW
        self._treegui.SetRoot(self._treegui, self._listView, None)
        self._treegui.Refresh()

        return True

    def Command(self, messageId, bc):
        return True

class MenuToolData(c4d.plugins.ToolData):
    dialog = None

    def Message(self, doc, data, msgType, t_data):
        return True

    def AllocSubDialog(self, bc):  
        return TestTreeDialog()

    def GetState(self, doc):
        return c4d.CMD_ENABLED

    def MouseInput(self, doc, data, bd, win, msg):
        return True


def main() -> None:
    # c4d.plugins.RegisterCommandPlugin(
    #     PLUGIN_ID, "treeTest1", 0, None, "treeTest1", MenuCommand())

    c4d.plugins.RegisterToolPlugin(PLUGIN_ID, "treeTest2menu", 0, None,"treeTest2describe", MenuToolData())

if __name__ == '__main__':
    main()

1.is there any way to add a separator in the model of the two control, so I'can drag to resize,
e2c12902-f5f4-41fd-aa72-2de1763662d0-图片.png
like asset browser.
GIF 2022-7-27 16-47-21.gif
2.how to hide this title? It's seem useless
90337541-a545-43b9-9f38-0fee8bf3f18b-图片.png
3.can embed the Dialog like asset browser?
a64270ce-54dd-4825-b0bf-fef0fadf36ce-图片.png

thanks a lot!

Hey @mogli,

Thank you for reaching out to us.

  1. is there any way to add a separator in the model of the two control, so I'can drag to resize

Well, you can also add a separator to make it more visible, but the key thing to do, is to allow weights for a group. When you have this group structure

GROUP ID_MY_WEIGHTED_GROUP
{
    COLUMNS 1;
    SCALE_H;
    SCALE_V;
    ALLOW_WEIGHTS;

    GROUP ID_SUB_GROUP_A {SCALE_H; SCALE_V;}
    GROUP ID_SUB_GROUP_B {SCALE_H; SCALE_V;}
}

The user can change the height of the groups ID_SUB_GROUP_A and ID_SUB_GROUP_B by dragging between them as you did in your screencast. The relevant flag is here ALLOW_WEIGHTS, and if you want to use code to create the layout, you must pass BFV_GRIDGROUP_ALLOW_WEIGHTS as part of the group flags for the GeDialog.GroupBegin call for ID_MY_WEIGHTED_GROUP. You can also save and load group weights for a dialog with the methods GeDialog.GroupWeightsLoad and .GroupWeightsSave()

2.how to hide this title? It's seem useless

This title comes from the ToolData plugin you are implementing. I agree that the duplication of the name is a bit distracting. I am not sure if it has always been there, but for now, you cannot change that.

Edit: So, I checked, and the duplication of the title string goes back to at least S22 (I do not have any older versions installed) and was therefore likely always there. It is only that it became more distracting with the new layout, as the contrast between the two strings has increased (could also be that this is just a perception thing with the darker background). This is for example how it looked in R23:

71ffde4e-bceb-45d5-b82c-f498bb127e96-image.png

We will see what we can do, but there will not be any immediate fixes, as ToolData itself does not have the highest priority and we will also be reluctant to remove title strings other plugins might rely on.

3.can embed the Dialog like asset browser?

Yes, you can, find out in this thread how to do it.

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

hi @ferdinand
These responses are very useful to me,Thank you for taking so much time to reply,Have a nice day!

hi @ferdinand about 3,I'm try to exeute the example code by the link provided, but It seems not work,It opens as Separate Window.
2943e1a2-ad68-4539-b3be-80ca0b115c51-图片.png
:dizzy_face:

Hey @mogli

as described in the posting and the example, Cinema 4D relies on layouts, you cannot programmatically inject a dialog into a layout. You must open and dock the dialog once manually, and subsequent clicks on the button will then have the desired behaviour. See this posting for more information on how to handle layout files.

The bottom line is:

  • You can implement the collapsing behaviour akin to the Asset Browser, Timeline/Dopesheet, or Coordinate Manger.

  • You can provide (partial) layouts with your plugin and programatically load them for the user.

  • You cannot inject a partial layout or a single dialog into an existing layout of a user.

  • Even our internal tools do not do that, they rely on the layout files which are provided with Cinema 4D.

  • This is an intentional design decision by us.

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

@ferdinand thanks a lot, best wishes!