GeDialog TabGroup



  • Hi,
    I have a GeDialog and would like to provide a group of tabs the user can select from.
    The number of tabs should be dynamical, for this I was playing with the idea to provide an empty "Add" tab. When the user selects this tab a new tab with gadgets is inserted before the "Add" tab.

    This seems to be an easy solution, but there is a drawback.
    When more tabs are created than available space, an extra button is displayed in the group, used to navigate through the tabs (left and right arrows).
    Unfortunately, when the last-but-one tab is selected and user presses this "right-navigation" button, the "Add" tab gets selected. Since the tabgroup is in the process of being updated as a result of navigation changes, and a press on the "Add" tab results in an extra tab being inserted, the whole thing triggers a breakpoint. I admit, not a crash, but still something I'd like to avoid.

    Is there a way to detect the user selected this "navigation" button? A message being sent?



  • Just from a GUI designer perspective: Don't.

    It may look like a good idea because of reasons... but it's an unusual way to use a tab; it's not what a user expects (even with it saying "Add" - it's a tab and not a button); it's breaking the familiar way of how a tab row works.



  • Thanks for the feedback.
    I just wanted to mimic the way Firefox or Chrome handles the tabs and creation of new ones.
    If that's a bad design, I'll look for something different.


  • Global Moderator

    Hi @C4DS,
    First of all, as @Cairyn said, keep in mind this behavior/workflow is completely new for C4D user and it can be a bit weird for them. But is up to you to decide the overall design of your plugin.

    With that's said and as you already figured it out, the main issue is you are changing data within a redraw.
    To fix that, use a CoreMessage. See GeDialog Core Messages Manual.

    #define GROUP_ID 9999
    #define TAB_GROUP_ID 10000
    #define REDRAW_MSG_ID 1000001 // Get a unique ID at https://plugincafe.maxon.net/c4dpluginid_cp
    
    class TabDialog : public GeDialog
    {
    
    public:
    	maxon::BaseArray<Int32> tabIds;
    
    	Bool CreateLayout()
    	{
    		if (tabIds.IsEmpty())
    		{
    			tabIds.Append(10001);
    			tabIds.Append(10011);
    			tabIds.Append(10021);
    		}
    		DrawTab();
    		return true;
    	};
    
    	void DrawTab(Bool redraw=false)
    	{
    		// If we redraw, the group already exist so we flush it.
    		if (redraw)
    			LayoutFlushGroup(GROUP_ID);
    
    		// Otherwise we create it.
    		else
    			GroupBegin(GROUP_ID, BFH_SCALEFIT | BFV_TOP, 1, 1, ""_s, 0);
    
    		// Then we create our TabGroup
    		TabGroupBegin(TAB_GROUP_ID, BFH_SCALEFIT | BFV_SCALEFIT, TAB_TABS);
    		Int32* lastId = tabIds.GetLast();
    		for (Int32 x : tabIds)
    		{
    			String groupTitle;
    			if (x == *lastId)
    				groupTitle += "+"_s;
    			else
    				groupTitle += FormatString("Group @"_s, x);
    
    			GroupBegin(x, BFH_SCALEFIT | BFV_SCALEFIT, 1, 0, groupTitle, 0);
    			AddCheckbox(x + 1, BFH_LEFT, 0, 0, "Some Elements"_s);
    			GroupEnd();
    		}
    		GroupEnd();
    		GroupEnd();
    
    		if (redraw)
    			LayoutChanged(GROUP_ID);
    	};
    
    	Bool Command(Int32 id, const BaseContainer& msg)
    	{
    		// Check the command come from the Tab
    		if (id == TAB_GROUP_ID)
    		{
    			// Get the clicked tab ID
    			Int32 newTabId;
    			GetInt32(TAB_GROUP_ID, newTabId);
    
    			// Get the last value of our tabs
    			Int32* lastValue = tabIds.GetLast();
    			if (lastValue == nullptr)
    				return GeDialog::Command(id, msg);
    
    			// If it's the last value, we add a new tab, and call a Redraw
    			if (newTabId == *lastValue)
    			{
    				tabIds.Append(*lastValue + 10);
    				SpecialEventAdd(REDRAW_MSG_ID);
    			}
    			
    		}
    		return GeDialog::Command(id, msg);
    	};
    
    	Bool CoreMessage(Int32 id, const BaseContainer& msg)
    	{
    		switch(id)
    		{
    			case REDRAW_MSG_ID:
    			{
    				// check if this core message is new
    				if (!CheckCoreMessage(msg))
    					break;
    
    				DrawTab(true);
    				
    				// Set the active tab to the one which is just added
    				if (tabIds.GetCount() >= 2)
    					SetInt32(TAB_GROUP_ID, tabIds[tabIds.GetCount() - 2]);
    				break;
    			}
    		}
    		return GeDialog::CoreMessage(id, msg);
    	}
    
    };
    

    If you have any question, please let me know.
    Cheers,
    Maxime.



  • Thanks for the explanation and the code, Maxime.
    However, looking at your code I can see the logic how to avoid updating data during a redraw. But I still don't see how this solves the actual issue of the navigation button.

    With your example, when using the right navigation button, it will still create extra tabs due to the "Add" tab being selected.
    I was hoping to capture a message related to this navigation button and actually be able to skip the "set add tab active".
    Currently, there seems no possibility to distinguish between the actual user pressing a tab, or a tab being selected through the navigation tab. Let alone a way to detect if the navigation button's left or right part is being selected.


  • Global Moderator

    Hi @C4DS,

    Unfortunately, there are no messages for clicking on this button since Tab Group was really not designed to handle to this kind of cases.

    With that's said, I've thought of BFM_GETCURSORINFO, maybe you can find a way to get the coordinate of theses button, and if they are drawn. Then you can define a member variable for enabling/disabling the addition of a new Tab according to the mouse position.

    Cheers,
    Maxime.