SOLVED Buttons do not work when opening dialog with "DLG_TYPE_ASYNC"

Nice to meet you.

I am not good at English.
I have a question about pythonGUI.

windows10
Cinema4D S24.037

I created a window with buttons.
I want to touch the “object manager” with the window open.

But, if you set the "dlgtype" of' “.open” to "DLG_TYPE_ASYNC",
I can no longer click the button of the created window.

With the “DLG_TYPE_MODAL” setting, the button works, but
I'm in trouble because other windows can't be touched.

Is there any other way?
I would like you to tell me.
Please.

import c4d
from c4d import gui

class MyDialogs(c4d.gui.GeDialog):
    def CreateLayout(self):
        self.AddButton(1201,c4d.BFH_LEFT,200,0,"button")
        return True

    def Command(self,id,msg):
        if id == 1201:            
            print("push button")
        return True


# Main function
def main():
    dlg = MyDialogs()
    dlg.Open(c4d.DLG_TYPE_ASYNC,0,-1,-1,100,100,0)
    c4d.EventAdd()

# Execute main()
if __name__=='__main__':
    main()

Hello @mari,

welcome to the Forum and Cinema 4D community and thank you for reaching out to us. Thank you for also following the forum guidelines in making your posting.

@Cairyn did answer your question correctly when one considers only practical approaches (thank you for the answer @Cairyn). When you run a Script Manager script and instantiate the dialog within some function, main() in your case, the dialog will be deallocated once you leave the scope of that function, as it is always the case in a Python script. Due to the dialog be managed in another thread, i.e., being asynchronous, the interpreter will step right through your function main. There are two ways to solve this problem.

  1. Keep the dialog instance bound to the local scope but make the dialog execution blocking.
def main():
    # Instantiate the dialog
    dlg = MyDialog()
    # Open the dialog modally 
    result = dlg.Open(c4d.DLG_TYPE_MODAL)
    # This line will only be reached when the dialog has been closed.

    # Is not needed here
    # c4d.EventAdd()

    return result

    # After returning the dialog result, i.e., after the function has been
    # left by the interpreter, the reference count of `dlg` will be zero, 
    # which will cause the dialog to be garbage collected. If the dialog
    # is asynchronous, this will happen while the dialog is still open, 
    # leading to the problems you did encounter.
  1. Attach the dialog instance to something you know that will surpass the lifetime of the dialog.
class MyObject():
    """Example for a class bound dialog.
    """
    _dialog = None # The reference to the dialog to keep it alive.

    def OpenMyDialog(self):
        """Opens a class bound dialog.
        """
        # Test if the class has already a dialog bound to it. If not, create
        # a dialog instance. The dialog could also be bound to an instance
        # of a class, i.e., self, but usually it is advantageous to bind the
        # dialog to the class to avoid accidentally opening multiple dialogs.
        # But if one wants one dialog per instance, one would have to bind it
        # to an instance.
        if MyObject._dialog is None:
            MyObject._dialog = MyDialog()

        # Since this is async dialog, it could be still open from a former 
        # execution, so check first.
        result = False
        if not MyObject._dialog.IsOpen():
            # Open the dialog in a non-blocking manner.
            result = MyObject._dialog.Open(c4d.DLG_TYPE_ASYNC)
        # This line will be reached right after the dialog has opened.
        return result

        # The dialog will not be deallocated upon the interpreter exiting 
        # OpenMyDialog(), because it is still bound to MyObject via the
        # attribute _dialog.

The object with a lifetime greater than the dialog window is usually a plugin class, for example a CommandData plugin. The implementation can then look like the following as shown in this thread:

"""Example for executing an options dialog.

As discussed in:
    https://plugincafe.maxon.net/topic/13407/
"""
import c4d

class PluginDialog (c4d.gui.GeDialog):
    """A dialog for a CommandData plugin.
    """
    ID_INT_VALUE = 1000

    def CreateLayout(self):
        """Adds gadgets to the dialog.
        """
        self.SetTitle("Some Dialog")
        self.AddEditSlider(
            id=PluginDialog.ID_INT_VALUE,
            flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT)
        return True

    def InitValues(self):
        """Initialize the dialog values.
        """
        self.SetInt32(PluginDialog.ID_INT_VALUE, 42)
        return True


class PC13407(c4d.plugins.CommandData):
    """A CommandData plugin with a dialog.
    """
    ID_PLUGIN = 1057432
    _pluginDialog = None

    @classmethod
    def GetPluginDialog(cls):
        """Returns the class bound instance of the plugin dialog.
        """
        if cls._pluginDialog is None:
            cls._pluginDialog = PluginDialog()
        return cls._pluginDialog

    def Execute(self, doc):
        """
        """
        print("Running {}.Execute()".format(self))
        return True

    def ExecuteOptionID(self, doc, plugid, subid):
        """Opens the option dialog.
        """
        print("Running {}.ExecuteOptionID()".format(self))
        dialog = PC13407.GetPluginDialog()
        result = True
        if not dialog.IsOpen():
            result = dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC,
                                 pluginid=PC13407.ID_PLUGIN)

        return result

    def RestoreLayout(self, secret):
        """Restores the dialog on layout changes.
        """
        dialog = PC13407.GetPluginDialog()
        return dialog.Restore(PC13407.ID_PLUGIN, secret)


if __name__ == '__main__':
    c4d.plugins.RegisterCommandPlugin(
        id=PC13407.ID_PLUGIN,
        str="Options Test",
        info=c4d.PLUGINFLAG_COMMAND_OPTION_DIALOG,
        icon=None,
        help="",
        dat=PC13407())

Which is not possible in a Script Manager script and asynchronous dialogs are not officially supported there. What one can do however, is attach the dialog instance to something else here, to avoid it being garbage collected. For example, just attach it to the module:

def main():
    # !!! PLEASE DO NOT DO THIS IN A PRODUCTION ENVIRONMENT !!!
    # Declare dlg as a module attribute
    global dlg
    # And attach the dialog to that module attribute
    dlg = MyDialogs()
    return dlg.Open(c4d.DLG_TYPE_ASYNC,0,-1,-1,100,100,0)
    # dlg will now linger after the function has been left

But this is very much NOT AN INTENDED usage of a script manger script, as it will cause the script module to linger, which can cause other problems. You should implement a plugin if you want to implement something permanent, attaching the dialog to a script module, or something similar, is a temporary solution which is only valid for testing purposes.

Cheers,
Ferdinand

You cannot call an asynchronous dialog from a script.

When your dialog is asynchronous, then the Open function returns immediately. The main() function returns. Then the script ends. The class MyDialogs will essentially be gone. This leaves the dialog window without a script to handle it.

If you need an asynchronous dialog, you may want to program a CommandData plugin instead of a script.

Hello @mari,

welcome to the Forum and Cinema 4D community and thank you for reaching out to us. Thank you for also following the forum guidelines in making your posting.

@Cairyn did answer your question correctly when one considers only practical approaches (thank you for the answer @Cairyn). When you run a Script Manager script and instantiate the dialog within some function, main() in your case, the dialog will be deallocated once you leave the scope of that function, as it is always the case in a Python script. Due to the dialog be managed in another thread, i.e., being asynchronous, the interpreter will step right through your function main. There are two ways to solve this problem.

  1. Keep the dialog instance bound to the local scope but make the dialog execution blocking.
def main():
    # Instantiate the dialog
    dlg = MyDialog()
    # Open the dialog modally 
    result = dlg.Open(c4d.DLG_TYPE_MODAL)
    # This line will only be reached when the dialog has been closed.

    # Is not needed here
    # c4d.EventAdd()

    return result

    # After returning the dialog result, i.e., after the function has been
    # left by the interpreter, the reference count of `dlg` will be zero, 
    # which will cause the dialog to be garbage collected. If the dialog
    # is asynchronous, this will happen while the dialog is still open, 
    # leading to the problems you did encounter.
  1. Attach the dialog instance to something you know that will surpass the lifetime of the dialog.
class MyObject():
    """Example for a class bound dialog.
    """
    _dialog = None # The reference to the dialog to keep it alive.

    def OpenMyDialog(self):
        """Opens a class bound dialog.
        """
        # Test if the class has already a dialog bound to it. If not, create
        # a dialog instance. The dialog could also be bound to an instance
        # of a class, i.e., self, but usually it is advantageous to bind the
        # dialog to the class to avoid accidentally opening multiple dialogs.
        # But if one wants one dialog per instance, one would have to bind it
        # to an instance.
        if MyObject._dialog is None:
            MyObject._dialog = MyDialog()

        # Since this is async dialog, it could be still open from a former 
        # execution, so check first.
        result = False
        if not MyObject._dialog.IsOpen():
            # Open the dialog in a non-blocking manner.
            result = MyObject._dialog.Open(c4d.DLG_TYPE_ASYNC)
        # This line will be reached right after the dialog has opened.
        return result

        # The dialog will not be deallocated upon the interpreter exiting 
        # OpenMyDialog(), because it is still bound to MyObject via the
        # attribute _dialog.

The object with a lifetime greater than the dialog window is usually a plugin class, for example a CommandData plugin. The implementation can then look like the following as shown in this thread:

"""Example for executing an options dialog.

As discussed in:
    https://plugincafe.maxon.net/topic/13407/
"""
import c4d

class PluginDialog (c4d.gui.GeDialog):
    """A dialog for a CommandData plugin.
    """
    ID_INT_VALUE = 1000

    def CreateLayout(self):
        """Adds gadgets to the dialog.
        """
        self.SetTitle("Some Dialog")
        self.AddEditSlider(
            id=PluginDialog.ID_INT_VALUE,
            flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT)
        return True

    def InitValues(self):
        """Initialize the dialog values.
        """
        self.SetInt32(PluginDialog.ID_INT_VALUE, 42)
        return True


class PC13407(c4d.plugins.CommandData):
    """A CommandData plugin with a dialog.
    """
    ID_PLUGIN = 1057432
    _pluginDialog = None

    @classmethod
    def GetPluginDialog(cls):
        """Returns the class bound instance of the plugin dialog.
        """
        if cls._pluginDialog is None:
            cls._pluginDialog = PluginDialog()
        return cls._pluginDialog

    def Execute(self, doc):
        """
        """
        print("Running {}.Execute()".format(self))
        return True

    def ExecuteOptionID(self, doc, plugid, subid):
        """Opens the option dialog.
        """
        print("Running {}.ExecuteOptionID()".format(self))
        dialog = PC13407.GetPluginDialog()
        result = True
        if not dialog.IsOpen():
            result = dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC,
                                 pluginid=PC13407.ID_PLUGIN)

        return result

    def RestoreLayout(self, secret):
        """Restores the dialog on layout changes.
        """
        dialog = PC13407.GetPluginDialog()
        return dialog.Restore(PC13407.ID_PLUGIN, secret)


if __name__ == '__main__':
    c4d.plugins.RegisterCommandPlugin(
        id=PC13407.ID_PLUGIN,
        str="Options Test",
        info=c4d.PLUGINFLAG_COMMAND_OPTION_DIALOG,
        icon=None,
        help="",
        dat=PC13407())

Which is not possible in a Script Manager script and asynchronous dialogs are not officially supported there. What one can do however, is attach the dialog instance to something else here, to avoid it being garbage collected. For example, just attach it to the module:

def main():
    # !!! PLEASE DO NOT DO THIS IN A PRODUCTION ENVIRONMENT !!!
    # Declare dlg as a module attribute
    global dlg
    # And attach the dialog to that module attribute
    dlg = MyDialogs()
    return dlg.Open(c4d.DLG_TYPE_ASYNC,0,-1,-1,100,100,0)
    # dlg will now linger after the function has been left

But this is very much NOT AN INTENDED usage of a script manger script, as it will cause the script module to linger, which can cause other problems. You should implement a plugin if you want to implement something permanent, attaching the dialog to a script module, or something similar, is a temporary solution which is only valid for testing purposes.

Cheers,
Ferdinand

Thank you for your reply!
@Cairyn and @ferdinand

Thank you for your kind and polite reply.

I found that I need to program the CommandData plugin.
I succeeded in saving the sample I received as a pyp and loading it as a plugin.
I will study how to make it.

When I looked at Maxon's official website,
I found a description that could create "Hallo CommandData!", But I couldn't find this window myself.

Is there a window in which you can create a program?
Please tell me again.
Thank you.

I want to add another question.

Is there a rule for plug-in ID?
What number or later should be used, etc ...

I feel it is dangerous because it may affect other functions.

Hello @mari,

When I looked at Maxon's official website, I found a description that could create "Hallo CommandData!", But I couldn't find this window myself. Is there a window in which you can create a program?

I assume with that you mean that you want to know how to create a sub-menu entry for your plugin in the "Extensions" menu of the main menu of Cinema 4D, right? Cinema does this sort of automatically. As soon as Cinema does encounter a directory which contains more than one plugin definition in a subdirectory in one of the plugin paths specified by the user, Cinema will wrap all the plugins defined there in a sub menu named after the directory these plugin definitions are contained in. So, as shown below, if you create a directory "My Fancy Plugin Collection" in one of the plugin search paths, and then put two plugin definitions, either in one or in multiple files, into this directory, Cinema will wrap all plugins there within a s sub-menu called "My Fancy Plugin Collection".

plugins_subfolder.png

Is there a rule for plug-in ID? What number or later should be used, etc ...

Plugin IDs have to be regsitered here on Plgin Café with the plugin id generator.

Cheers,
Ferdinand

@mari said in Buttons do not work when opening dialog with "DLG_TYPE_ASYNC":

Is there a window in which you can create a program?

Just in case you really mean an editor window: no. The Script Manager handles pure scripts that reside in the script directory, which is different from the plugin directory (but you found that out already). While the Script Manager theoretically handles any text, it would be quite awkward to write plugins with it.

Personally, I am using an external editor to write plugins in Python. You can use any editor or Python IDE, but make sure to store the results as Unicode UTF-8 (or use the Python notation for a different encoding). While you are changing the source code, you can reload the plugin with Cinema's "Reload Python Plugins" functionality to see the changes without restarting Cinema altogether.

It is a good idea to start with a very simple plugin which loads correctly and then expand from there. If the plugin has certain errors, it may fail during loading and not appear in Cinema's plugin list. In that case, it will not be loaded by "Reload Python Plugins" even after you externally corrected it, as that function apparently only affects plugins that are already there.

I think there is a manual for connecting an IDE for debugging with Cinema somewhere here on this forum.

Hello! @ferdinand ,@Cairyn

Thank you for politely teaching me!

I was able to solve the title problem.
I will use an external editor.
I also understood loading plugins!

I was restarting the software many times.

I have a new question, but if you study a little more and don't solve it, let me ask you another thread.

I'm really thankful to you!