Solved Dialog Menu

Hi,
I have two dialogs in different python scripts and I would like to join them in a single python script so the user via buttons can choose what dialog to show.

I tried searching the forums as well as reading the documentation but I have not been able to find a starting guide to solve this issue.

Does anyone have an idea how I could achieve a solution to this problem?

Thanks so much for your time.

Hey @joel,

you are running there in a fringe ownership error of the dialog object. You must create a dummy reference to the dialog before you overwrite the instance bound reference self.subDialog to avoid a race condition between the GC and C4D which is still using the dialog. Or what I would recommend - keep two dialog instances around, as this code looks then more sane to me.

Find below a fix.

Cheers,
Ferdinand

import typing
import c4d
from c4d import gui

ID_SUBDIALOG = 10000
ID_Dialog1_BUTTON = 10001
ID_Dialog2_BUTTON = 10002

class Dialog1(c4d.gui.SubDialog):
    def CreateLayout(self):
        self.AddStaticText(1, c4d.BFH_SCALEFIT, 0, 0, "Dialog 1")
        return True

class Dialog2(c4d.gui.SubDialog):
    def CreateLayout(self):
        self.AddStaticText(1, c4d.BFH_SCALEFIT, 0, 0, "Dialog 2")
        return True

class MainDialog(c4d.gui.GeDialog):
    """
    """
    def __init__(self) -> None:
        """
        """
        # You made your subDialog attribute class bound but then later used it as instance bound.
        # I assume that was an oversight and you did not want the sub dialog to be class bound but
        # instance bound.
        self._dlg1: Dialog1 = Dialog1() # Dialog instance for type 1
        self._dlg2: Dialog2 = Dialog2() # Dialog instance for type 2

    def CreateLayout(self):
        self.GroupBegin(0, c4d.BFH_SCALEFIT, 2)
        self.AddButton(ID_Dialog1_BUTTON, c4d.BFH_SCALEFIT, name="Dialog1")
        self.AddButton(ID_Dialog2_BUTTON, c4d.BFH_SCALEFIT, name="Dialog2")
        self.GroupEnd()
        self.AddSubDialog(ID_SUBDIALOG, c4d.BFH_SCALEFIT, 100, 100)
        return True

    def Command(self, mid, msg):
        # Switch the dialog based on which button has been pressed.
        if mid in (ID_Dialog1_BUTTON, ID_Dialog2_BUTTON):
            self.AttachSubDialog(self._dlg1 
                                 if mid == ID_Dialog1_BUTTON
                                 else self._dlg2, ID_SUBDIALOG)
            print(self.LayoutChanged(ID_SUBDIALOG))
        return True

if __name__=='__main__':
    dlg = MainDialog()
    dlg.Open(c4d.DLG_TYPE_MODAL_RESIZEABLE, defaultw=900, xpos=-2, ypos=-2)

MAXON SDK Specialist
developers.maxon.net

EDIT:
Sorry this was not what I thought I have a dual Dialog example somewhere but not at hand ...

*This is an Subdialog Example from the SDK.

Github Subdialog

hope that is in line what you are searching.*
Cheers mogh

Hello @joel,

thank you for reaching out to us. I am a bit confused about the nature of your question; when you have implemented two dialogs, you should be able to do this on your own.

Interpreting your question in the most straight forward way, you have two dialogs A and B and want the user to be able to open either of them from a single script. You then must simply implement a dialog C with two buttons which open the respective dialogs. When you attach the dialog instances A and B to C you can also add some logic which would prevent users from opening multiple instances of A or B or opening A and B at the same time.

Then there are multiple interpretations of your question which are less literal.

  • As pointed out by @mogh (thanks!), there is the concept of sub-dialogs in Cinema 4D. This effectively just means that the content of one ore multiple dialogs can be rendered within a hosting dialog. See GeDialog.AddSubDialog for a point of entry to the concept in the API. There are also two C++ and two Python code examples making use of this concept.
  • There is also the tab gadget with which you can structure larger groups of disjunct UI elements in a singular dialog. See GeDialog.TabGroupBegin for a point of entry. There exist only C++ Code examples for this gadget, although using it in Python is perfectly possible.
    3bb20552-58ca-4fce-b551-ff923ba3ab3a-image.png
  • You could also use three layout groups in a singular dialog, let's call them A, B, and C. A and B contain the layout of two dialogs as in the prior example. C also contains two buttons as in the prior example. You then add the groups in the order C, A, and B to a singular dialog. When the user presses a button in C, you hide and unhide the groups A and B using GeDialog.HideElement, so that the user sees only one of them at a time.
    • You can also dynamically rebuild the whole GUI at runtime, but that should not be necessary in this case. Find an example here.
  • This is more of an academic point, since it seems unlikely to me that you are using a dialog resource. But when you have defined two dialog resources RA and RB, nothing prevents you from (a.) loading them both into their seperate dialog but also (b.) into a singular dialog by simply calling GeDialog.LoadDialogResource twice in succession.
⚠ Script Manager scripts are not meant to have extensive GUIs. While one can make things work, it is easier and safer to wrap things into a CommandData plugin. You will need a plugin ID which you can get here on the forum with the plug icon in the top menu bar. The plugin itself is then straight forward, you can ignore the CommandData stuff and reuse the code in the dynamic dialog GUI example which I have posted above; the only thing you would have to change are the Plugin ID and the name of the plugin.

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

I tried using the subdialog as the github example shows. If I just want to do one subdialog change the github example that @mogh posted works just find. However when I add another button so I can switch between the subdialogs the Dialog does not update.

The code I used is a bit modified from the github but more or less the same:

import c4d
from c4d import gui

ID_SUBDIALOG = 10000
ID_Dialog1_BUTTON = 10001
ID_Dialog2_BUTTON = 10002

class Dialog1(c4d.gui.SubDialog):
    def CreateLayout(self):
        self.AddStaticText(1, c4d.BFH_SCALEFIT, 0, 0, "Dialog 1")
        return True

class Dialog2(c4d.gui.SubDialog):
    def CreateLayout(self):
        self.AddStaticText(1, c4d.BFH_SCALEFIT, 0, 0, "Dialog 2")
        return True

class MainDialog(c4d.gui.GeDialog):
    subDialog = Dialog1()
    
    def CreateLayout(self):
        self.GroupBegin(0, c4d.BFH_SCALEFIT, 2)
        self.AddButton(ID_Dialog1_BUTTON, c4d.BFH_SCALEFIT, name="Dialog1")
        self.AddButton(ID_Dialog2_BUTTON, c4d.BFH_SCALEFIT, name="Dialog2")
        self.GroupEnd()
        
        self.AddSubDialog(ID_SUBDIALOG, c4d.BFH_SCALEFIT, 100, 100)
        self.AttachSubDialog(self.subDialog, ID_SUBDIALOG)
        
        return True
    
    def Command(self, mid, msg):
        if mid == ID_Dialog1_BUTTON:
            self.subDialog = Dialog1()
            self.AttachSubDialog(self.subDialog, ID_SUBDIALOG)
            print(self.LayoutChanged(ID_SUBDIALOG))
        if mid == ID_Dialog2_BUTTON:
            self.subDialog = Dialog2()
            self.AttachSubDialog(self.subDialog, ID_SUBDIALOG)
            print(self.LayoutChanged(ID_SUBDIALOG))
        return True

if __name__=='__main__':
    dlg = MainDialog()
    dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=900, xpos=-2, ypos=-2)

When I click the buttons the first button click from either button the LayoutChanged returns True but on the second button click from either button returns False.

What am I doing wrong?

Hey @joel,

you are running there in a fringe ownership error of the dialog object. You must create a dummy reference to the dialog before you overwrite the instance bound reference self.subDialog to avoid a race condition between the GC and C4D which is still using the dialog. Or what I would recommend - keep two dialog instances around, as this code looks then more sane to me.

Find below a fix.

Cheers,
Ferdinand

import typing
import c4d
from c4d import gui

ID_SUBDIALOG = 10000
ID_Dialog1_BUTTON = 10001
ID_Dialog2_BUTTON = 10002

class Dialog1(c4d.gui.SubDialog):
    def CreateLayout(self):
        self.AddStaticText(1, c4d.BFH_SCALEFIT, 0, 0, "Dialog 1")
        return True

class Dialog2(c4d.gui.SubDialog):
    def CreateLayout(self):
        self.AddStaticText(1, c4d.BFH_SCALEFIT, 0, 0, "Dialog 2")
        return True

class MainDialog(c4d.gui.GeDialog):
    """
    """
    def __init__(self) -> None:
        """
        """
        # You made your subDialog attribute class bound but then later used it as instance bound.
        # I assume that was an oversight and you did not want the sub dialog to be class bound but
        # instance bound.
        self._dlg1: Dialog1 = Dialog1() # Dialog instance for type 1
        self._dlg2: Dialog2 = Dialog2() # Dialog instance for type 2

    def CreateLayout(self):
        self.GroupBegin(0, c4d.BFH_SCALEFIT, 2)
        self.AddButton(ID_Dialog1_BUTTON, c4d.BFH_SCALEFIT, name="Dialog1")
        self.AddButton(ID_Dialog2_BUTTON, c4d.BFH_SCALEFIT, name="Dialog2")
        self.GroupEnd()
        self.AddSubDialog(ID_SUBDIALOG, c4d.BFH_SCALEFIT, 100, 100)
        return True

    def Command(self, mid, msg):
        # Switch the dialog based on which button has been pressed.
        if mid in (ID_Dialog1_BUTTON, ID_Dialog2_BUTTON):
            self.AttachSubDialog(self._dlg1 
                                 if mid == ID_Dialog1_BUTTON
                                 else self._dlg2, ID_SUBDIALOG)
            print(self.LayoutChanged(ID_SUBDIALOG))
        return True

if __name__=='__main__':
    dlg = MainDialog()
    dlg.Open(c4d.DLG_TYPE_MODAL_RESIZEABLE, defaultw=900, xpos=-2, ypos=-2)

MAXON SDK Specialist
developers.maxon.net

Creating an instance for each subdialog solved the issue thank you so much.