How to update the attribute manager's view?

THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

On 11/11/2009 at 06:28, xxxxxxxx wrote:

User Information:
Cinema 4D Version:   11 
Platform:   Windows  ;   
Language(s) :     C++  ;

---------
Hi all,

I made a tag which allows the user to dynamically add and remove values for this tag in the attribute manager (aka Active Object Manager). There is an add-button and a list of float-sliders - when the user clicks the add button, a new slider should be added to the list.

The descriptions are updated each time the GetDDescription-function is called, so that the new slider should be visible, but my problem is that the attribute manager doesn't update its view after the user clicked the button, so the GetDDescription function is not called. When I click on something else and then back to my tag, it is updated and I see the new slider. Same with removing entries.

I've tried to send MSG_UPDATE or MSG_CHANGE to the tag and the object which holds the tag, but that doesn't change anything.
Even "EventAdd(EVMSG_DOCUMENTRECALCULATED);" doesn't work.

How do I get the attribute manager to update the shown attributes?

Cheers,
Joachim

THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

On 11/11/2009 at 08:45, xxxxxxxx wrote:

I think, a container value has to be changed in order to make the attribute manager be updated. You may e.g. store the number of controls in a container value, that is increased when the user clicks on "Add"...

THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

On 11/11/2009 at 08:49, xxxxxxxx wrote:

Thanks for the hint, but it doesn't work. 😞
If a container value is changed, the attribute manager updates all values, but not the descriptions. So additions and removals are still not updated.

THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

On 13/11/2009 at 20:33, xxxxxxxx wrote:

maybe try   ....   EventAdd(EVENT_FORCEREDRAW); That might work..

THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

On 17/11/2009 at 05:40, xxxxxxxx wrote:

Can you please show the code of the Message method of your TagData plugin?

cheers,
Matthias

THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

On 23/11/2009 at 01:49, xxxxxxxx wrote:

Sorry, I was absent for a week and just returned.

I've tried EventAdd(EVENT_FORCEREDRAW) and EventAdd(EVENT_DOCUMENTRECALCULATED), but both don't show any effect. I guess those events force the view window to be updated, but not the attribute manager's view.

Here is my Message function:

  
Bool C4DMyTag::Message(GeListNode *fp_node, LONG l_type, void *fp_data)   
{   
     switch (l_type)   
     {   
          case MSG_DESCRIPTION_REMOVE_ENTRY:   
               {   
                    // a gui-element is removed, find out which one...   
                    DescriptionCommand &lr;_descriptionCommand(*(DescriptionCommand* )(fp_data));   
                    if (lr_descriptionCommand.id.GetDepth() != 2)   
                         break;   
                    if ((lr_descriptionCommand.id[0].id != TMYTAG_DATA_USE)   
                     && (lr_descriptionCommand.id[0].id != TMYTAG_DATA_VALUE))   
                         break;   
                    if (lr_descriptionCommand.id[1].id < 1000)   
                         break;   
                    LONG l_index = (lr_descriptionCommand.id[1].id - 1000);   
                    loginfo << "removing entry #" << l_index << endlog;   
                    removeEntry(l_index);   
               }   
               break;   
          case MSG_DESCRIPTION_COMMAND:   
               {   
                    //   
                    // a gui-button was pressed, find out which one...   
                    DescriptionCommand &lr;_descriptionCommand(*(DescriptionCommand* )(fp_data));   
                    if (lr_descriptionCommand.id == TMYTAG_ADD)   
                         addEntry();   
               }   
               break;   
     }   
     return SUPER::Message(fp_node, l_type, fp_data);   
}   

What happens in addEntry and removeEntry is that it adds or removes an entry from a list of entries (in a basecontainer) and then calls the updateAttributeManager-function:

  
void C4DMyTag::updateAttributeManager()   
{   
     BaseTag *lp_tag = (BaseTag* )Get();   
     BaseObject *lp_object((lp_tag)?lp_tag->GetObject() :0);   
     if (lp_tag)   
     {   
          lp_tag->Message(MSG_UPDATE);   
          lp_tag->Message(MSG_CHANGE);   
     }   
     if (lp_object)   
     {   
          lp_object->Message(MSG_UPDATE);   
          lp_object->Message(MSG_CHANGE);   
     }   
     EventAdd(EVENT_FORCEREDRAW);   
     EventAdd(EVMSG_DOCUMENTRECALCULATED);   
}   

As you can see, the updateAttributeManager-function should do something to make the attribute manager call the tag's GetDDescription function which is responsible for actually updating the entries which are shown in the attribute manager.

  
Bool C4DMyTag::GetDDescription(GeListNode* fp_node, Description* fp_description, LONG& fr_flags)   
{   
     if (!fp_description->LoadDescription(fp_node->GetType())) return FALSE;   
     fr_flags |= DESCFLAGS_DESC_LOADED;   
  
     BaseTag *lp_tag = (BaseTag* )Get();   
     BaseContainer *lp_data = (lp_tag)?lp_tag->GetDataInstance() :0;   
     BaseContainer *lp_subcontainerUse = (lp_data)?lp_data->GetContainerInstance(TMYTAG_DATA_USE) :0;   
     BaseContainer *lp_subcontainerLink = (lp_data)?lp_data->GetContainerInstance(TMYTAG_DATA_LINK) :0;   
     if (lp_subcontainerUse)   
     {   
          LONG i=0;   
          LONG l_id;   
          while ((l_id = lp_subcontainerUse->GetIndexId(i++)) != NOTOK)   
          {   
               if (l_id < 1000)   
                    continue;   
               LONG l_index = l_id - 1000;   
               String l_name("");   
  
               //   
               // get the name from the object   
               BaseLink *lp_link((lp_subcontainerLink)?lp_subcontainerLink->GetBaseLink(1000+l_index) :0);   
               BaseList2D *lp_object((lp_link)?lp_link->GetLink(lp_tag->GetDocument(), Obase) :0);   
               if (lp_link && lp_object)   
                    l_name = lp_object->GetName() + " ";   
  
               BaseContainer l_bcUse = GetCustomDataTypeDefault(DTYPE_BOOL);   
                l_bcUse.SetString(DESC_NAME, l_name+"Enabled");   
                l_bcUse.SetString(DESC_SHORT_NAME, " ");   
               l_bcUse.SetLong(DESC_CUSTOMGUI, CUSTOMGUI_BOOL);   
               l_bcUse.SetLong(DESC_ANIMATE, DESC_ANIMATE_ON);   
               l_bcUse.SetBool(DESC_REMOVEABLE, TRUE);   
               if (!fp_description->SetParameter(DescID(DescLevel(TMYTAG_DATA_USE), DescLevel(1000+l_index, DTYPE_BOOL, 0)), l_bcUse, DescLevel(TMYTAG_SGROUP))) return FALSE;   
  
               BaseContainer l_bcValue = GetCustomDataTypeDefault(DTYPE_REAL);   
                l_bcValue.SetString(DESC_NAME, l_name+"Value");   
                l_bcValue.SetString(DESC_SHORT_NAME, " ");   
               l_bcValue.SetLong(DESC_CUSTOMGUI, CUSTOMGUI_REALSLIDER);   
               l_bcValue.SetReal(DESC_MIN, 0.0);   
               l_bcValue.SetReal(DESC_MAX, 1.0);   
               l_bcValue.SetReal(DESC_STEP, 0.01);   
               l_bcValue.SetLong(DESC_UNIT, DESC_UNIT_PERCENT);   
               l_bcValue.SetLong(DESC_ANIMATE, DESC_ANIMATE_ON);   
               l_bcValue.SetBool(DESC_REMOVEABLE, TRUE);   
               if (!fp_description->SetParameter(DescID(DescLevel(TMYTAG_DATA_VALUE), DescLevel(1000+l_index, DTYPE_REAL, 0)), l_bcValue, DescLevel(TMYTAG_SGROUP))) return FALSE;   
          }   
     }   
     return SUPER::GetDDescription(fp_node, fp_description, fr_flags);   
}   

...but the GetDDescription function is only called when I click on another object and then back to my tag to show its attributes again.
What I want is to show the newly added or removed entry while the tag's attributes are visible. Otherwise the user clicks on the button to add an entry and nothing seems to happen.

So what I'm searching for is how I can trigger the attribute manager in my updateAttributeManager-function to call GetDDescription again.

cheers
Joachim

THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

On 24/11/2009 at 06:29, xxxxxxxx wrote:

I can't really see anything wrong (unless I missed something). The only important thing is to send the MSG_CHANGE message to the tag after adding the additional data to the tag's container. It's also a good idea to add an undo.

Here the code of my message method (it's an object plugin but the same applies to tags) :

  
Bool MorphMixerObject::Message(GeListNode *node, LONG type, void *data)  
{  
  switch (type)  
  {  
      case MSG_DESCRIPTION_COMMAND:  
      {  
          DescriptionCommand *dc = (DescriptionCommand* ) data;  
          if (dc->id[0].id==MORPHMIXER_ADD)  
          {  
              BaseList2D *op = (BaseList2D* ) node;  
              BaseDocument *doc = op->GetDocument(); if (!doc) return FALSE;  
              BaseContainer *bc = ((BaseList2D* )node)->GetDataInstance(); if (!bc) return FALSE;  
              BaseContainer *main = bc->GetContainerInstance(ID_MORPHMIXER_OBJECT); if (!main) return FALSE;  
  
              doc->StartUndo();  
  
              doc->AddUndo(UNDO_CHANGE_SMALL,node);  
  
              main->SetLong(MORPH_CNT, main->GetLong(MORPH_CNT,0)+1);  
  
              doc->EndUndo();  
  
              op->Message(MSG_CHANGE);  
          }  
      }  
  }  
  
  return SUPER::Message(node,type,data);  
}  

cheers,
Matthias

THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

On 24/11/2009 at 06:43, xxxxxxxx wrote:

Changing the value of a basecontainer's entry works and is updated immediately in the attribute manager.
But what I'm trying to do is to add or remove entries of the basecontainer while the tag's attributes are visible in the attribute manager. Then those added or removed entries should also be visibly (the GUI-elements) added or removed in the attribute manager's view.

So that's the point where it doesn't work - the attribute manager (=AM) only updates the values of its GUI-elements, but it doesn't add or remove GUI-elements (unless you close the AM and open it again by clicking on some other object and then back to the tag).

As far as I understood, adding and removing of GUI elements is done by calling the tag's GetDDescription function, which then prepares the Description element for the AM. But I don't get the AM to call the GetDDescription function again to request a new Description and to realize that there are new GUI elements to show.

Besides, the adding of undo is really a good idea, I didn't think of that, thanks.

THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

On 24/11/2009 at 06:57, xxxxxxxx wrote:

Originally posted by xxxxxxxx

Changing the value of a basecontainer's entry works and is updated immediately in the attribute manager.
But what I'm trying to do is to add or remove entries of the basecontainer while the tag's attributes are visible in the attribute manager. Then those added or removed entries should also be visibly (the GUI-elements) added or removed in the attribute manager's view.

What I am doing here is to store all the data of the dynamic description elements in a sub-container in the object's container. So in my message method I just store the number of dynamic description elements and the initialized values for the dynamic elements in the sub-container of the object's container. After this I send the MSG_CHANGE message.

cheers,
Matthias

THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

On 24/11/2009 at 07:16, xxxxxxxx wrote:

Originally posted by xxxxxxxx

What I am doing here is to store all the data of the dynamic description elements in a sub-container in the object's container. So in my message method I just store the number of dynamic description elements and the initialized values for the dynamic elements in the sub-container of the object's container. After this I send the MSG_CHANGE message.

I doubt if I can follow you here... You mean you're adding gui elements just by inserting them into the basecontainer which is a subcontainer of the object's container?
Don't you have to call the Description::SetParameter function to insert a new dynamic description as I do it in my GetDDescription function?

In your example, I see that you're just incrementing the count-value in the Message function when the user clicks on the button and then trigger MSG_CHANGE. But what makes the new GUI-elements appear? There must be some code called which sais: ok, the count has been incremented, now we need to create an additional checkbox and a link field and maybe a slider. Or am I missing something?

cheers
Joachim

btw.: I've added the undo as you suggested and now the behaviour is interesting: If I click on my button to add a new GUI-element, I see nothing (as before), but if I then click on undo and redo multiple times, I see the GUI element appear and disappear. So the undo and redo obviously makes the AM call the GetDDescription function as it should be done when the user clicks my button.

THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

On 24/11/2009 at 08:47, xxxxxxxx wrote:

Originally posted by xxxxxxxx

I doubt if I can follow you here... You mean you're adding gui elements just by inserting them into the basecontainer which is a subcontainer of the object's container?
Don't you have to call the Description::SetParameter function to insert a new dynamic description as I do it in my GetDDescription function?

No, it's just to store the information for the dynamic description elements in the object's container to read them out in GetDDescription. This way they are saved with the document.

Here the complete code of my simple example (it's just dynamically adding Bool description elementsL.

  
/////////////////////////////////////////////////////////////  
// CINEMA 4D SDK                                           //  
/////////////////////////////////////////////////////////////  
// (c) 1989-2004 MAXON Computer GmbH, all rights reserved  //  
/////////////////////////////////////////////////////////////  
  
// morph mixing example  
  
#include "c4d.h"  
#include "c4d_symbols.h"  
#include "omorphmixer.h"  
  
  
// be sure to use unique IDs obtained from www.plugincafe.com  
#define ID_MORPHMIXER_OBJECT    1001156  
  
enum  
{  
  MORPH_CNT = 1000,  
  MORPH_INDEX  
};  
  
  
class MorphMixerObject : public ObjectData  
{  
  INSTANCEOF(MorphMixerObject,ObjectData)  
  
  public:  
      virtual Bool Init(GeListNode *node);  
      virtual Bool Message(GeListNode *node, LONG type, void *data);  
      virtual BaseObject* GetVirtualObjects(PluginObject *op, HierarchyHelp *hh);  
      virtual Bool GetDDescription(GeListNode *node, Description *description,LONG &flags);  
  
      static NodeData *Alloc(void) { return gNew MorphMixerObject; }  
};  
  
Bool MorphMixerObject::Init(GeListNode *node)  
{  
  BaseObject *op = (BaseObject* )node;  
  BaseContainer *data = op->GetDataInstance();  
  
  BaseContainer main;  
  main.SetLong(MORPH_CNT,0);  
  data->InsData(ID_MORPHMIXER_OBJECT, main);  
  
  return TRUE;  
}  
  
Bool MorphMixerObject::GetDDescription(GeListNode *node, Description *description,LONG &flags)  
{  
  if (!description->LoadDescription(node->GetType())) return FALSE;  
  
  // important to check for speedup c4d!  
  const DescID *singleid = description->GetSingleDescID();  
  
  LONG index = MORPH_INDEX;  
    
  BaseObject *op = (BaseObject* )node;  
  BaseContainer *data = op->GetDataInstance();  
  BaseContainer *main = data->GetContainerInstance(ID_MORPHMIXER_OBJECT);  
  
  LONG i,cnt = main->GetLong(MORPH_CNT,0);  
    
  Bool initbc2 = FALSE;  
  BaseContainer bc2;  
  
  for (i=0; i<cnt; i++)  
  {  
      DescID cid = DescLevel(index,DTYPE_BOOL,0);  
      if (!singleid || cid.IsPartOf(*singleid,NULL)) // important to check for speedup c4d!  
      {  
          if (!initbc2)  
          {  
              initbc2 = TRUE;  
              bc2 = GetCustomDataTypeDefault(DTYPE_BOOL);  
              bc2.SetLong(DESC_ANIMATE,DESC_ANIMATE_ON);  
              bc2.SetBool(DESC_REMOVEABLE,FALSE);  
          }  
          bc2.SetString(DESC_NAME,"On "+LongToString(i));  
          bc2.SetString(DESC_SHORT_NAME,"On "+LongToString(i));  
          if (!description->SetParameter(cid,bc2,DescLevel(ID_OBJECTPROPERTIES))) return FALSE;  
      }  
  
      index++;  
  }  
    
  flags |= DESCFLAGS_DESC_LOADED;  
  
  return SUPER::GetDDescription(node,description,flags);  
}  
  
Bool MorphMixerObject::Message(GeListNode *node, LONG type, void *data)  
{  
  switch (type)  
  {  
      case MSG_DESCRIPTION_COMMAND:  
      {  
          DescriptionCommand *dc = (DescriptionCommand* ) data;  
          if (dc->id[0].id==MORPHMIXER_ADD)  
          {  
              BaseList2D *op = (BaseList2D* ) node;  
              BaseDocument *doc = op->GetDocument(); if (!doc) return FALSE;  
              BaseContainer *bc = ((BaseList2D* )node)->GetDataInstance(); if (!bc) return FALSE;  
              BaseContainer *main = bc->GetContainerInstance(ID_MORPHMIXER_OBJECT); if (!main) return FALSE;  
  
              doc->StartUndo();  
  
              doc->AddUndo(UNDO_CHANGE_SMALL,node);  
  
              main->SetLong(MORPH_CNT, main->GetLong(MORPH_CNT,0)+1);  
  
              doc->EndUndo();  
  
              op->Message(MSG_CHANGE);  
          }  
      }  
  }  
  
  return SUPER::Message(node,type,data);  
}  
  
// create morphed object  
BaseObject *MorphMixerObject::GetVirtualObjects(PluginObject *op, HierarchyHelp *hh)  
{  
  return NULL;  
}  
  
  
Bool RegisterMorphMixer(void)  
{  
  // decide by name if the plugin shall be registered - just for user convenience  
  String name=GeLoadString(IDS_MORPHMIXER); if (!name.Content()) return TRUE;  
  return RegisterObjectPlugin(ID_MORPHMIXER_OBJECT,name,OBJECT_GENERATOR|OBJECT_INPUT,MorphMixerObject::Alloc,"Omorphmixer","morphmixer.tif",0);  
}  

cheers,
Matthias

THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

On 24/11/2009 at 09:51, xxxxxxxx wrote:

Thanks a lot for posting the example code, that helped to clarify the issue.

First thing is that my code isn't that different from yours, I also store the elements in a basecontainer and read from that container in the GetDDescription function to create the gui-element-descriptions.

But to be sure, I copy&pasted; your code into an empty tag-plugin. Then I ran C4D with the new plugin, created a box, attached the new tag, pushed the button and...: nothing happened!
It has exactly the same behaviour I described earlier: it just doesn't call the GetDDescription-function to update the gui-elements int the AM.

Then I copy&pasted; your code into a new object-plugin. Running C4D, creating a new object of the new type and pushing the button worked as you predicted - I see a new GUI element appear each time I click the button.

Now I investigated the case with the debugger, which showed that in case of an object plugin, C4D calls the GetDDescription function right after the button is pushed, but in case of a tag plugin, it doesn't!

I guess that's a bug, isn't it?

cheers
Joachim

Btw.: I'm using R11.026 on Windows XP if that was going to be the next question 🙂

THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

On 24/11/2009 at 10:01, xxxxxxxx wrote:

ok, thanks. I'll investigate and report back later.

cheers,
Matthias

THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

On 25/11/2009 at 02:27, xxxxxxxx wrote:

Ok, I can confirm the issue. I'll ask the developers for a solution.

cheers,
Matthias

THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

On 27/11/2009 at 06:45, xxxxxxxx wrote:

Got an answer from the developers. What you have to do is to set the tag dirty instead of sending MSG_CHANGE.

  
tag->SetDirty(DIRTY_DATA);  

Unfortunatly this is currently not very consistent, send MSG_CHANGE is OK for object but not for tags.

cheers,
Matthias

THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

On 27/11/2009 at 06:54, xxxxxxxx wrote:

Great, that indeed worked!
You're right, it's very unintuitive to assume that a tag needs another updating mechanism than an object. Especially when sending the MSG_CHANGE syntactically works.

Thanks a lot for your help!