Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Recent
    • Tags
    • Users
    • Register
    • Login

    CUSTOMGUI_QUICKTAB trigger twice when click

    Scheduled Pinned Locked Moved Cinema 4D SDK
    sdkpython2023windows
    7 Posts 4 Posters 477 Views 4 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • DunhouD Online
      Dunhou
      last edited by

      Hello guys,
      I want to achieve a Cycle button gui with python ( like subdivide UVs in a SDS ) , I find CUSTOMGUI_QUICKTAB provide a UI like this , but when I test my dialog , I click on a quicktab , it return id twice, but when I print something , it works well ( I just want keep this gui but not any sub dlg , and do some stuff when change a tab button like refresh ua and treeview , it executed twice is too expensive ) so what's wrong with my codes?

      [win 11@22H2 , c4d 2023.2.0]

      import c4d
      
      ID_QUICKTAB_BAR = 110000  # ID for the quicktab customGui
      
      class QuickTabDialogExample(c4d.gui.GeDialog):
      
          def __init__(self):
              self._quickTab = None  # Stores the quicktab custom GUI
      
          def CreateLayout(self):
      
              bc = c4d.BaseContainer()
              bc.SetBool(c4d.QUICKTAB_BAR, 0)
              bc.SetBool(c4d.QUICKTAB_SHOWSINGLE, True)
              bc.SetBool(c4d.QUICKTAB_NOMULTISELECT, 1)
              self._quickTab = self.AddCustomGui(ID_QUICKTAB_BAR, c4d.CUSTOMGUI_QUICKTAB, '',
                                                 c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 0, 0, bc)
              self._quickTab.AppendString(0, "1", 1)
              self._quickTab.AppendString(1, "2", 0)
              self._quickTab.AppendString(2, "3", 0)
      
              self.AddButton(50, c4d.BFH_SCALEFIT, name="Print Selected")
              return True
      
          def Command(self, id, msg):
              
              print(id)
      
              # Displays the ID and name of the selected tab
              if id == 50:
                  if self._quickTab.IsSelected(0):
                      print("1 select")
                  if self._quickTab.IsSelected(1):
                      print("2 select")
                  if self._quickTab.IsSelected(2):
                      print("3 select")
                  
              return True
      
      
      
      # Main function
      def main():
          # Initializes a QuickTabDialogExample Dialog
          diag = QuickTabDialogExample()
      
          # Opens the Dialog in modal mode
          diag.Open(dlgtype=c4d.DLG_TYPE_MODAL, defaultw=0, defaulth=0)
      
      
      # Execute main()
      if __name__ == '__main__':
          main()
      

      https://boghma.com
      https://github.com/DunHouGo

      1 Reply Last reply Reply Quote 0
      • M Offline
        m_adam
        last edited by

        Hi @Dunhou thanks for reaching us, looks like it was always the case at least I can reproduce it up to 21, and I stopped to check for earlier version. I opened a bug report about it, the only workaround would be track yourself the selection state and compare it each time.
        Find bellow a prototype:

        import c4d
        import dataclasses 
        
        ID_QUICKTAB_BAR = 110000
        
        @dataclasses.dataclass
        class Entry:
            entryId: int
            entryName: str
            selectionState: bool
        
        class QuickTabDialogExample(c4d.gui.GeDialog):
        
            def __init__(self):
                self.entries = []
                self.entriesHash = 0
                self._quickTab = None
        
            def CreateLayout(self):
        
                bc = c4d.BaseContainer()
                bc.SetBool(c4d.QUICKTAB_BAR, 0)
                bc.SetBool(c4d.QUICKTAB_SHOWSINGLE, True)
                bc.SetBool(c4d.QUICKTAB_NOMULTISELECT, True)
                self._quickTab = self.AddCustomGui(ID_QUICKTAB_BAR, c4d.CUSTOMGUI_QUICKTAB, '',
                                                   c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 0, 0, bc)
                
                self.entries.append(Entry(10001, "1", True))
                self.entries.append(Entry(10002, "2", False))
                self.entries.append(Entry(10003, "3", False))
                
                self.entriesHash = hash(str(self.entries))
                
                for entry in self.entries:
                    self._quickTab.AppendString(entry.entryId, entry.entryName, entry.selectionState)
        
                self.AddButton(10050, c4d.BFH_SCALEFIT, name="Print Selected")
                return True
        
            def Command(self, id, msg):
        
                # Displays the ID and name of the selected tab
                if id == 10050:
                    x = self._quickTab.GetData()
                    if self._quickTab.IsSelected(10001):
                        print("1 select")
                    if self._quickTab.IsSelected(10002):
                        print("2 select")
                    if self._quickTab.IsSelected(10003):
                        print("3 select")
                        
                elif id == ID_QUICKTAB_BAR and self._quickTab:
                    for entry in self.entries:
                        entry.selectionState = self._quickTab.IsSelected(entry.entryId)
                    
                    newSelectionState = hash(str(self.entries))
                    if newSelectionState != self.entriesHash:
                        self.entriesHash = newSelectionState
                        print("Selection changed")
        
        
                return True
        
        
        # Main function
        def main():
            diag = QuickTabDialogExample()
            diag.Open(dlgtype=c4d.DLG_TYPE_MODAL, defaultw=0, defaulth=0)
        
        
        # Execute main()
        if __name__ == '__main__':
            main()
        

        Cheers,
        Maxime.

        MAXON SDK Specialist

        Development Blog, MAXON Registered Developer

        DunhouD 1 Reply Last reply Reply Quote 0
        • DunhouD Online
          Dunhou @m_adam
          last edited by

          @m_adam Thanks man! this @dataclasses.dataclass decorator is pretty handy , new knowledge incoming😊

          And maybe some similar bug I have seen before , but I don't remember what it is (and even didn't know it is a bug or my bad coding🤕 ) , if I met some similar bugs I will update the topic , but now let's hope it will be fixed next release .

          Thanks for your help again

          Cheers~

          https://boghma.com
          https://github.com/DunHouGo

          1 Reply Last reply Reply Quote 0
          • GeneG Offline
            Gene
            last edited by

            Sorry for necro'ing the post, I was looking for code references on Quicktabs.

            Regarding Command triggering twice, is it because of the mouse events? mouse up/down
            I print the contents of msg[c4d.BFM_ACTION_INDRAG] inside the Command function for testing.

            • When I click on the Quicktab, the first event returns True, while the second returns False.
            • If I drag over two entries on the Quicktab gadget (click on one entry, then drag to the next entry and let go of the mouse,) Command triggers three times, msg[c4d.BFM_ACTION_INDRAG] returns True the first two times and False for the last one. Also, the first entry returns True for its IsSelected() check on mouse down. After dragging, the the second entry's IsSelected() is True. Second entry's IsSelected() is also true when I let go of the mouse.

            So maybe it was by design, in case the interface needs to update as you drag across entries on the Quicktab gadget. And if you only need to update the gui once (regardless of whether the clicked entry was previously selected or not,) just check if msg[c4d.BFM_ACTION_INDRAG] is False (mouse released.)

            def Command(self, id, msg):
                ## check if Quicktab was clicked, and only process when user is finished clicking
                if id == ID_QUICKTAB_BAR and not msg[c4d.BFM_ACTION_INDRAG]:
                    self.RefreshGuiOnQuicktabMouseUp()
            
            
                return True
            
            ferdinandF 1 Reply Last reply Reply Quote 1
            • ferdinandF Offline
              ferdinand @Gene
              last edited by

              Hey @Gene,

              Thread-necromancy is often not so good, because it almost always derails the original topic and often also lacks in clarity. I also here do not understand what your concrete question is?

              When I click on the Quicktab, the first event returns True, while the second returns False.

              What do you mean by "the event returns true"? It is you, the developer, who returns the result for an event (or you do not handle it at all). Are you talking about something like BFM_ACTION_VALUE?

              If I drag over two entries on the Quicktab gadget (click on one entry, then drag to the next entry and let go of the mouse,) Command triggers three times, msg[c4d.BFM_ACTION_INDRAG] returns True the first two times and False for the last one. Also, the first entry returns True for its IsSelected() check on mouse down. After dragging, the the second entry's IsSelected() is True. Second entry's IsSelected() is also true when I let go of the mouse.

              I do not see any question here, so what is the goal?

              Regarding Command triggering twice, is it because of the mouse events?

              GeDialog.Command is just a convenience wrapper for GeDialog.Message events (most from the BFM_ACTION family). If you want the full picture, use Message. I struggle to see a tangible question which I could answer, what do you want to achieve?

              Yes, some controls fire multiple times in Command, it is impossible (and also somewhat futile) to reconstruct the reason for each of them. Someone thought at some point it would be convenient to forward this kind of event to Command for some feature in Cinema 4D.

              I am not familiar with the exact issue discussed in this topic, but at a glance, what Maxime wrote then looks a bit overkill with the hashing, I would just store the last selected element and be done with it. Alternatively you could design your code so that you do not tie any heavy logic to a tab being activated (which is IMHO not a great idea in general), so that you do not mind when such code runs multiple times.

              Cheers,
              Ferdinand

              class MyDialog (c4d.gui.GeDialog):
                  ID_TAB_1: int = 10000
                  ID_TAB_2: int = 10001
              
                  IDS_TABS: tuple[int, ...] = (ID_TAB_1, ID_TAB_2)
              
                  def __init__(self) -> None:
                      self._activeTab: int = MyDialog.ID_TAB_1
              
                  def Command(self, cid: int, msg: c4d.BaseContainer) -> bool:
                      """
                      """
                      if cid == self._activeTab:
                          return True
                      elif cid in MyDialog.IDS_TABS:
                          self._activeTab = cid
              
                      if cid is MyDialog.ID_TAB_1:
                          foo()
                      elif cid is MyDialog.ID_TAB_2:
                          bar()
              
                      return True
              

              MAXON SDK Specialist
              developers.maxon.net

              GeneG 1 Reply Last reply Reply Quote 0
              • GeneG Offline
                Gene @ferdinand
                last edited by Gene

                @ferdinand said in CUSTOMGUI_QUICKTAB trigger twice when click:

                Thread-necromancy is often not so good, because it almost always derails the original topic and often also lacks in clarity. I also here do not understand what your concrete question is?

                Hi Ferdinand,

                At the end of the discussion, they were talking about how the Command function being triggered twice might be a bug. Because clicking on a Quicktab Gizmo triggers the Command function twice, compared to when clicking on a Button Gizmo triggers it once.

                I wasn't really asking a question. What I was trying to say was that the Command function triggering more than once might be intended, and I was describing the behavior.

                • When you MouseDown on a Quicktab gizmo, the Command function is called.
                • When you drag the mouse, every time the cursor hits another entry on the Quicktab, it will trigger the Command function again.
                • Then when you finally MouseUp, it will trigger the Command function one last time.

                ** The Button gizmo only triggers the Command function once, on MouseUp.

                So depending on the situation, you might want to choose a specific way to handle Quicktabs in the Command function.

                • If you need to only react to a change in the Quicktab's value, then Maxime's solution is ideal. More overhead, but it also lets you ignore the situation where the user clicks on the already-selected item on the Quicktab.
                • If you only need to react to clicking on the Quicktab, inside the Command function you could just check if msg[c4d.BFM_ACTION_INDRAG] is False. (This is the MouseUp event after clicking on the Quicktab, similar behavior to clicking on a Button gizmo.)
                • If you need to react to clicks and drags on the Quicktab, you check if msg[c4d.BFM_ACTION_INDRAG] is True. (It should then be safe to ignore the Command call when msg[c4d.BFM_ACTION_INDRAG] is False, since the contents of Quicktab should stay the same as the previous call.)

                EDIT:
                The behavior's also consistent with other gizmos. The one from GeDialog.AddEditSlider() for example, If you click/drag on the arrows, Command is called multiple times as well. If you click on the arrow, Command is called twice (on MouseDown and MouseUp.) If you hold on the arrow for a while, it's called more than twice. First call is when you MouseDown, the next ones are whenever the value updates as you keep the arrow pressed, and one last time when you MouseUp. If you click on its text field, Command is only called once either after you press Enter, press Escape, or click outside of the text field (Command also triggers every time you press the up/down keys to increment the value.)

                ferdinandF 1 Reply Last reply Reply Quote 0
                • ferdinandF Offline
                  ferdinand @Gene
                  last edited by

                  hey @Gene,

                  So, you were just trying to add information for future readers? That is of course very welcome and explains my inability to extract a question from your posting.

                  I would not have been that generous with the word bug either. Since this has never been fixed, chances are high that we came to the same conclusion internally.

                  In general, you should not expect Command calling patterns to make perfect sense in every case. There are multiple cases where Command is triggered more often than one would think at first glance. I would also not try to interpret some deeper sense into all that.

                  Cheers,
                  Ferdinand

                  MAXON SDK Specialist
                  developers.maxon.net

                  1 Reply Last reply Reply Quote 0
                  • First post
                    Last post