Adding a String List to the UI

Sparrowhawke3DSparrowhawke3D Posts: 102

I'm having trouble trying to add a TMFNameListPart into the User Interface to simply add a list of strings. I can get the list to appear and to add some strings into it, using a few attempted methods, but then this causes the plugin to become unstable and indirectly cause Nil Pointer errors. I haven't seen some full example code for this but there was a rough outline on the old SDK group.

I started in MCSketch by adding a TMFScrollPart (with attached sliders) then its child leaf part is a TMFNameListPart with a part ID 'myID'. Nothing was added to the resource or PMap for the plugin. If that is all I do I get an empty space for the list and the plugin works as normal.

Then in the plugin's ::HandleEvent(...) I add the following :

...
MCOMErr MyPlugin::HandleEvent(int32 message, IMFResponder* source, void *data)
{
...
switch(message)
{
case kMsg_CUIP_ComponentAttached:
{
TMCCountedPtr sourcePart;
source->QueryInterface(IID_IMFPart,(void**)&sourcePart;);
TMCCountedPtr childPart;
sourcePart>FindChildPartByID(&childPart;,'myID');
TMCCountedPtr listPart;
childPart->QueryInterface(IID_IMFListPart,(void**)&listPart;);

// method 1 : create an TMCClassArray Names; elsewhere and add some strings
// listPart->CreateNamedCells(Names);

// method 2: use IMFPart to SetValue
listPart->AddCells(0,2,true);
TMCString nameString="item 1";
listPart->SelectCell(0,true,false);
childPart->SetValue(&nameString;,kStringValueType,true,true);
nameString="item 2";
listPart->SelectCell(1,true,false);
childPart->SetValue(&nameString;,kStringValueType,true,true);
}
break;
...

When this code is used the string list is filled with the names, they can be selected, but Nil Pointer errors start up elsewhere in the plugin's operations.

Should I be using TMFStringDataPacket here, and if so, how ? What is the correct way to add (and manage) a selectable list of strings into the UI ?

Comments

  • Eric WinemillerEric Winemiller Posts: 84
    edited December 1969

    I use the same set up in Toon! Pro Override without any problems, but I use CreateNamedCells instead of doing the AddCells and SetValue. I don't have any recollection about gotchas for this, but it's been close to a decade since writing this code so I may have landed there because of other issues.

    I did notice that while doing this and processing other messages (e.g. kMsg_PartValueChanged) I set a flag before calling this function titled ignoreSelectionChange which drops me out of another chunk of HandleEvent code if set. Maybe your exception is downstream because you're not fully prepared when you get a selection change.

    
    
    MCCOMErr  ToonEnabled::HandleEvent(MessageID message, IMFResponder* source, void* data)
    
    {
     IDType sourceID;
     TMCCountedPtr sourcePart;
    
    
     source->QueryInterface(IID_IMFPart, (void**) &sourcePart;);
     ThrowIfNil(sourcePart);
     sourceID = sourcePart->GetIMFPartID();
    
     if (message == kMsg_CUIP_ComponentAttached && tree != NULL) {
      TMCCountedPtr listPart;
      TMCCountedPtr list;
      sourcePart->FindChildPartByID(&listPart;, UI_ELEM_SHADER_LIST);
    
      ThrowIfNil(listPart);
      listPart->QueryInterface(IID_IMFListPart, (void**)&list;);
    
      mergeLevelsWithScene();
      fillShadingDomainList(list, 0);
      if (toonSettings.GetElemCount() == 0)
      {
       enableDomainControls(sourcePart, false);
    
       listPart->Enable(false);
      }
    
     }
    
    ...
    
    
    void ToonEnabled::fillShadingDomainList(TMCCountedPtr list, uint32 selection)
    {
     TMCClassArray inNames;
     uint32 domainCount = toonSettings.GetElemCount();
     for (uint32 levelsIndex = 0; levelsIndex < domainCount; levelsIndex++)
     {
      TMCString255 currentState;
      ShadingDomainLineLevels& level = toonSettings.getDomainByRank(levelsIndex);
      if (level.overrideSettings == true)
      {
        currentState = " - overridden";
      }
      else
      {
        currentState = "";
      }
      inNames.AddElem(level.name + currentState);
     }
    
     list->CreateNamedCells(inNames);
     if (inNames.GetElemCount() > 0)
     {
      list->SelectCell(selection, true, false);
     }
     list = NULL;
    }
    
    
    

    Regards,

  • Sparrowhawke3DSparrowhawke3D Posts: 102
    edited December 1969

    Thanks Eric for sharing your wisdom.

    If listPart->CreateNameCells() works and the code I've used is almost the same as yours then I'm on the right road. Knowing that when I ran a debug with more close attention I could see that kMsg_CUIP_ComponentAttached is called many many times and that could narrow it down to making sure the message is directly associated with the list part to solve this.

    There are so many simple things with the UI that are nearly impossible to figure out without the help of 'zot' The Great and Powerful...

  • Sparrowhawke3DSparrowhawke3D Posts: 102
    edited December 1969

    I'm still having difficulty to add such a simple element to the UI - but I've got much closer to something functional by half-hacking and debugging.

    In the code I listed above I left out the obvious ThrowIfNil() checks. One of the errors was showing up as a Nil Pointer because another component (a dialog) was being attached. So if the list part isn't the only one in the UI a safety check is needed.

    ...  case kMsg_CUIP_ComponentAttached:    {    TMCCountedPtr<IMFPart> sourcePart;    source->QueryInterface(IID_IMFPart,(void**)&sourcePart;);    ThrowIfNil(sourcePart);    TMCCountedPtr<IMFPart> childPart;    if( (sourcePart>FindChildPartByID(&childPart;,‘myID’)==MC_S_OK) && (childPart) )      {      TMCCountedPtr<IMFListPart> listPart;      if( (childPart->QueryInterface(IID_IMFListPart,(void**)&listPart;)==MC_S_OK) && (listPart) )        {        if(listPart->GetNumOfCells()==0)          {          listPart->CreateNamedCells(Names);          listPart->SelectCell(0,true,false);          }        }      }        }  break;

    The best solution I can come up with for now is to check if the list is empty first (or the right size) before creating new cells. There is no apparent way to get the contents of the cells later and TMFNameListPart is not included in the SDK. I'll have to hope this will work to build the interface I want where the list can be used to add and remove (and edit) separate influence zones inside a single deformer.

    Whenever a list item is selected it generates another kMsg_CUIP_ComponentAttached event as well, causing some of the trouble.

    Without that check to see if the list has already been filled it put the code into a terminal loop when I called listPart->SelectCell(0,true,false);

    The debug reaches into listPart->CreateNamedCells(Names); twice when the modifier is added to the object. I first tried adding in a boolean flag to create the list only once but this resulted in an empty list. I presume this is due to some sort of cloning for undo and redo.

  • Sparrowhawke3DSparrowhawke3D Posts: 102
    edited December 1969

    I've got another issue with the String List that I can't figure out. Managing the list to be able to add and remove items and respond to a change of selection is achievable with UI buttons and events and is working.

    I can get a button press event and then knowing the hierarchy inside the UI use the responder to query for its source part, get the parent and then find my list using code like this:

    
    MCCOMErr MyPlugin::HandleEvent(int32 message, IMFResponder* source, void *data)
    ...
      TMCCountedPtr sourcePart;
      source->QueryInterface(IID_IMFPart,(void**)&sourcePart;);
      ThrowIfNil(sourcePart);
    ...
      TMCCountedPtr sourceParent;
      sourceParent=sourcePart>GetPartParent();
      TMCCountedPtr childPart;
      TMCCountedPtr listPart;
      if( (sourceParent->FindChildPartByID(&childPart;,'myID' /*the id of my list part*/ )==MC_S_OK) && (childPart) )
        if( (childPart->QueryInterface(IID_IMFListPart,(void**)&listPart;)==MC_S_OK) && (listPart) )
          {
          // change the list in response to the event
          }
    ...

    What I can't figure out is how to make changes to the list outside of ::HandleEvent. I want to be able to build a list then during MyModifier::DeformFacetMesh(...) detect if the scene or selection etc has changed and then update a list.

    It comes down to a general question of how do I get hold of the IMFPart of the UI inside the plugin code outside of ::HandleEvent() ?

  • Eric WinemillerEric Winemiller Posts: 84
    edited December 1969

    I have in the past, passed around a pointer to a UI element using the Clone method, but it was very unsafe and only worked for very specific workflows. I don't do it anymore.

    Is there no activate event or something when you return to the UI where you can resynch with your object?

    My other thought was put an explicit Refresh button there. I've done that in the past where I could not automagically detect when stuff changed.

    Good luck,

  • Sparrowhawke3DSparrowhawke3D Posts: 102
    edited December 1969

    Thanks again for some experienced insight and advice Eric,

    Saving a copy of the IMFPart when the list is created should have occurred to me - but clearly that isn't safe.

    I couldn't see any clear path through the TBasicDataExchanger or IExDataExchanger (that handle the UI) to get to an IMFPart. There is TBasicDataExchanger::GetMyPrefsComponent() which returns IShParameterComponent - from which it might be possible to call ::GetView(IMFPart **outPart). I was hoping to avoid another time wasting fishing trip in debug to see what comes up there.

    I'm still in the design stage at the moment. I want to add a 'parameter link' into a deformer where the user can select another instance in the scene and then I would create a list of compatible modifiers that are found on that tree and select one, to copy and keep up to date with the parameters. All of this should work in ::HandleEvent() and the plugin would easily be able to detect a broken link. To improve user friendliness I wanted to update the list and attempt to repair the link or raise an alert to fix it. Dumping the potential problem on the user might be the only way to proceed.

Sign In or Register to comment.