SOLVED How to Get checked/unchecked state of menu item?

Hi,
Use MenuInitString() change checked/unchecked state of menu item,
how to get checked/unchecked state?
Thanks for any help!

Hello @chuanzhen,

Thank you for reaching out to us. There is no mechanism to get the state of a menu item as they are not meant to be the data layer of your app. You must store and track these values yourself; properties are a good pattern to solve such things, as you can encapsulate the GUI-DATA bindings in a very clean way with them. See end of my posting for a small example.

Cheers,
Ferdinand

An example output:


MY_DIALOG.IsFiles = False
MY_DIALOG.IsFolders = False

self._isFiles = True
self._isFolders = True
self._isFiles = False
self._isFolders = False
self._isFiles = True

The code:

"""Provides a simple example for handling checkable menu items and their state with properties 
inside a GeDialog instance. 
"""

import c4d

class MyDialog (c4d.gui.GeDialog):
    """Implements a dialog which encapsulates menu item states in properties.
    """
    ID_MENU_DATA_FILES: int = 1000 # The ID for the "Files" menu item.
    ID_MENU_DATA_FOLDERS: int = 1001 # The ID for the "Folders" menu item.

    def __init__(self) -> None:
        """Initializes the dialog.
        """
        self._isFiles: bool = False # The internal field for the IsFiles property.
        self._isFolders: bool = False # The internal field for the IsFolders property.

        super().__init__()

    def CreateLayout(self) -> bool:
        """Called by Cinema 4D to populate the dialog with gadgets.
        """
        # We build a very simply menu with the structure:
        #   Data
        #   +-- Files
        #   +-- Folders
        self.MenuSubBegin("Data")
        self.MenuAddString(MyDialog.ID_MENU_DATA_FILES, "Files")
        self.MenuAddString(MyDialog.ID_MENU_DATA_FOLDERS, "Folders")
        self.MenuSubEnd()
        self.MenuFinished()

        return super().CreateLayout()

    def Command(self, cid: int, data: c4d.BaseContainer) -> bool:
        """Called by Cinema 4D when a gadget, including menus, has been invoked.
        """
        # When one of the menu items has been invoked, we simply toggle the matching property.
        if cid == MyDialog.ID_MENU_DATA_FILES:
            self.IsFiles = not self.IsFiles

        if cid == MyDialog.ID_MENU_DATA_FOLDERS:
            self.IsFolders = not self.IsFolders
            
        return super().Command(cid, data)

    # --- The menu items realized as properties ----------------------------------------------------

    @property
    def IsFiles(self) -> bool:
        """Gets the "Files" state.

        We just get the value from the private field.
        """
        return self._isFiles

    @IsFiles.setter
    def IsFiles(self, value: bool) -> None:
        """Sets the "Files" state.

        The setter also updates the GUI via GeDialog.MenuInitString, making sure that the properties
        are always correctly reflected. In this case we also print out the new value to demonstrate
        how the dialog works.
        """
        if not isinstance(value, bool):
            raise TypeError(value)
        if self._isFiles == value:
            return

        self._isFiles = value
        self.MenuInitString(MyDialog.ID_MENU_DATA_FILES, enabled=True, value=self._isFiles)
        print (f"{self._isFiles = }")

    @property
    def IsFolders(self) -> bool:
        """Gets the "Folders" state.
        """
        return self._isFolders

    @IsFolders.setter
    def IsFolders(self, value: bool) -> None:
        """Sets the "Folders" state.
        """
        if not isinstance(value, bool):
            raise TypeError(value)
        if self._isFolders == value:
            return

        self._isFolders = value
        self.MenuInitString(MyDialog.ID_MENU_DATA_FOLDERS, enabled=True, value=self._isFolders)
        print (f"{self._isFolders = }")

# Attribute deceleration for a MyDialog instance used by main().
MY_DIALOG: MyDialog

def main():
    """Runs the example.
    """
    # We are using here the global attribute hack to make the dialog work in async mode in a Script
    # Manager script. Please do not use this hack in a production environment, Script Manger scripts
    # should not open async dialogs, they are restricted to plugins as they can properly manage the
    # lifetime of the dialog.

    global MY_DIALOG
    MY_DIALOG = MyDialog()
    
    # Open the dialog in asynchronous mode.
    MY_DIALOG.Open(c4d.DLG_TYPE_ASYNC, defaultw=200, defaulth=100)

    # We can now either directly read and write the properties of the dialog ...
    print (f"\n{MY_DIALOG.IsFiles = }")
    print (f"{MY_DIALOG.IsFolders = }\n")
    MY_DIALOG.IsFiles = True

    # or interact via GeDialog.Command with the dialog to toggle the properties and their GUI.
    MY_DIALOG.Command(MY_DIALOG.ID_MENU_DATA_FOLDERS, c4d.BaseContainer())

if __name__ == "__main__":
    main()

Hello @chuanzhen,

Thank you for reaching out to us. There is no mechanism to get the state of a menu item as they are not meant to be the data layer of your app. You must store and track these values yourself; properties are a good pattern to solve such things, as you can encapsulate the GUI-DATA bindings in a very clean way with them. See end of my posting for a small example.

Cheers,
Ferdinand

An example output:


MY_DIALOG.IsFiles = False
MY_DIALOG.IsFolders = False

self._isFiles = True
self._isFolders = True
self._isFiles = False
self._isFolders = False
self._isFiles = True

The code:

"""Provides a simple example for handling checkable menu items and their state with properties 
inside a GeDialog instance. 
"""

import c4d

class MyDialog (c4d.gui.GeDialog):
    """Implements a dialog which encapsulates menu item states in properties.
    """
    ID_MENU_DATA_FILES: int = 1000 # The ID for the "Files" menu item.
    ID_MENU_DATA_FOLDERS: int = 1001 # The ID for the "Folders" menu item.

    def __init__(self) -> None:
        """Initializes the dialog.
        """
        self._isFiles: bool = False # The internal field for the IsFiles property.
        self._isFolders: bool = False # The internal field for the IsFolders property.

        super().__init__()

    def CreateLayout(self) -> bool:
        """Called by Cinema 4D to populate the dialog with gadgets.
        """
        # We build a very simply menu with the structure:
        #   Data
        #   +-- Files
        #   +-- Folders
        self.MenuSubBegin("Data")
        self.MenuAddString(MyDialog.ID_MENU_DATA_FILES, "Files")
        self.MenuAddString(MyDialog.ID_MENU_DATA_FOLDERS, "Folders")
        self.MenuSubEnd()
        self.MenuFinished()

        return super().CreateLayout()

    def Command(self, cid: int, data: c4d.BaseContainer) -> bool:
        """Called by Cinema 4D when a gadget, including menus, has been invoked.
        """
        # When one of the menu items has been invoked, we simply toggle the matching property.
        if cid == MyDialog.ID_MENU_DATA_FILES:
            self.IsFiles = not self.IsFiles

        if cid == MyDialog.ID_MENU_DATA_FOLDERS:
            self.IsFolders = not self.IsFolders
            
        return super().Command(cid, data)

    # --- The menu items realized as properties ----------------------------------------------------

    @property
    def IsFiles(self) -> bool:
        """Gets the "Files" state.

        We just get the value from the private field.
        """
        return self._isFiles

    @IsFiles.setter
    def IsFiles(self, value: bool) -> None:
        """Sets the "Files" state.

        The setter also updates the GUI via GeDialog.MenuInitString, making sure that the properties
        are always correctly reflected. In this case we also print out the new value to demonstrate
        how the dialog works.
        """
        if not isinstance(value, bool):
            raise TypeError(value)
        if self._isFiles == value:
            return

        self._isFiles = value
        self.MenuInitString(MyDialog.ID_MENU_DATA_FILES, enabled=True, value=self._isFiles)
        print (f"{self._isFiles = }")

    @property
    def IsFolders(self) -> bool:
        """Gets the "Folders" state.
        """
        return self._isFolders

    @IsFolders.setter
    def IsFolders(self, value: bool) -> None:
        """Sets the "Folders" state.
        """
        if not isinstance(value, bool):
            raise TypeError(value)
        if self._isFolders == value:
            return

        self._isFolders = value
        self.MenuInitString(MyDialog.ID_MENU_DATA_FOLDERS, enabled=True, value=self._isFolders)
        print (f"{self._isFolders = }")

# Attribute deceleration for a MyDialog instance used by main().
MY_DIALOG: MyDialog

def main():
    """Runs the example.
    """
    # We are using here the global attribute hack to make the dialog work in async mode in a Script
    # Manager script. Please do not use this hack in a production environment, Script Manger scripts
    # should not open async dialogs, they are restricted to plugins as they can properly manage the
    # lifetime of the dialog.

    global MY_DIALOG
    MY_DIALOG = MyDialog()
    
    # Open the dialog in asynchronous mode.
    MY_DIALOG.Open(c4d.DLG_TYPE_ASYNC, defaultw=200, defaulth=100)

    # We can now either directly read and write the properties of the dialog ...
    print (f"\n{MY_DIALOG.IsFiles = }")
    print (f"{MY_DIALOG.IsFolders = }\n")
    MY_DIALOG.IsFiles = True

    # or interact via GeDialog.Command with the dialog to toggle the properties and their GUI.
    MY_DIALOG.Command(MY_DIALOG.ID_MENU_DATA_FOLDERS, c4d.BaseContainer())

if __name__ == "__main__":
    main()

@ferdinand Thanks!