Custom Paramaters on Component Inputs and AdditionalMenuItems

Hi All,

I am trying to create a custom component with a “special parameter” as input. The main reason it is special is so that I can have a custom context menu when I right click over it. So far I have managed to do that, but the problem is that when I save the file and reopen it my “special parameter” dissapears and gets replaced by the base class I extended from. This is what I have so far:

class MySpecialParam : Param_ScriptVariable
{
  public bool array;

  public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
  {
    base.AppendAdditionalMenuItems(menu);
    var item = Menu_AppendItem(menu, "Array", Menu_ArrayClicked, true, array);
    item.ToolTipText = "Make this parameters an Array";
  }

  private void Menu_ArrayClicked(object sender, EventArgs e)
  {
    RecordUndoEvent("array");
    array = !array;
    Access = array ? GH_ParamAccess.list : GH_ParamAccess.item;
    ExpireSolution(true);
  }
}

and I am adding it on a component that implements the IGH_VariableParameterComponent interface

public IGH_Param CreateParameter(GH_ParameterSide side, int index)
{
  var param = new MySpecialParam();

  param.Name = GH_ComponentParamServer.InventUniqueNickname("ABCDEFGHIJKLMNOPQRSTUVWXYZ", Params.Input);
  param.NickName = param.Name;
  param.Description = "Property Name";
  param.Optional = true;
  param.Access = GH_ParamAccess.item;

  return param;
}

Please note that I am aware that Param_ScriptVariable already does what I am replicating here, I am planning to add more, once I get this simple thing working :stuck_out_tongue:

1 Like

if things are disappearing on copy/paste and save/reopen, you are having a Read and Write serialization problem. I am surprised if it is actually changing the param type; but at the very least you need to store any custom properties you’ve set so that they can be read back in. To do this, override the Read and Write functions to add custom serialization logic. See the Param_JsonInput class in JSwan for an example:

1 Like

Hi Andrew, thanks for the reply. I tried to add the Read an Write functions and some of the extra things I found on your code just in case. I also looked at the Serialize file from JSwan: https://github.com/andrewheumann/jSwan/blob/master/jSwan/Serialize.cs

I am doing a similar logic to that. Please see files below and let me know if you have any ideas. What happens now is whether I copy or save/load the file the parameters custom context menu disappears (they don’t change type). Since I added a GUID on the parameter, I am also getting an error during load:

What I meant is that they seem to change their class type, instead of being the Param_GenericAccess, they become Param_GenericObject type, which doesn’t have any context menu modifications. I hope this makes things clearer!

My Parameter Class:

class Param_GenericAccess : Param_GenericObject, IDisposable
{
  private const string ParamAccessKey = "ParamAccess";

  public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
  {
    base.AppendAdditionalMenuItems(menu);
    if (Kind != GH_ParamKind.output)
    {
      var item0 = Menu_AppendItem(menu, "Item", Menu_Clicked, true, Access == GH_ParamAccess.item);
      item0.ToolTipText = "Make this parameter an Item";
      var item1 = Menu_AppendItem(menu, "Array", Menu_Clicked, true, Access == GH_ParamAccess.list);
      item1.ToolTipText = "Make this parameters an Array";
      var item2 = Menu_AppendItem(menu, "Tree", Menu_Clicked, true, Access == GH_ParamAccess.tree);
      item2.ToolTipText = "Make this parameter a Tree";
    }
  }

  public void Dispose()
  {
    ClearData();
  }

  private void Menu_Clicked(object sender, EventArgs e)
  {
    var menu = sender as ToolStripMenuItem;
    var name = menu.AccessibilityObject.Name;
    RecordUndoEvent("Changing State");

    if (name.Equals("Tree"))
    {
      //RecordUndoEvent("tree");
      Access = GH_ParamAccess.tree;
    }
    else if (name.Equals("Array"))
    {
      //RecordUndoEvent("array");
      Access = GH_ParamAccess.list;
    }
    else if (name.Equals("Item"))
    {
      //RecordUndoEvent("item");
      Access = GH_ParamAccess.item;
    }

    OnObjectChanged(GH_ObjectEventType.DataMapping);
    ExpireSolution(true);

  }

  public override bool Read(GH_IReader reader)
  {
    var result = base.Read(reader);
    if(reader.ItemExists(ParamAccessKey))
    {
      try
      {
        //In case casting produces invalid Access enum
        Access = (GH_ParamAccess)reader.GetInt32(ParamAccessKey);
      }
      catch (Exception)
      {

      }
    }
    return result;
  }
  
  public override Guid ComponentGuid => new Guid("{2E711E3A-A73E-42AD-86FF-0B6BA23E9990}");


  public override bool Write(GH_IWriter writer)
  {
    var result = base.Write(writer);
    writer.SetInt32(ParamAccessKey, (int)Access);
    return result;
  }

}

And this is my class that implements the IGH_VariableParameterComponent

public class CreateCustomData : GH_Component, IGH_VariableParameterComponent
  {

    public bool CanInsertParameter(GH_ParameterSide side, int index)
    {
      return side == GH_ParameterSide.Input;
    }

    public bool CanRemoveParameter(GH_ParameterSide side, int index)
    {
      return side == GH_ParameterSide.Input;
    }

    public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
    {
      base.AppendAdditionalMenuItems(menu);
      var item = Menu_AppendItem(menu, "");
    }

    public IGH_Param CreateParameter(GH_ParameterSide side, int index)
    {
      var param = new Param_GenericAccess();
      //var param = new Param_GenericObject();
      //var param = new Param_ScriptVariable();

      param.Name = GH_ComponentParamServer.InventUniqueNickname("ABCDEFGHIJKLMNOPQRSTUVWXYZ", Params.Input);
      param.NickName = param.Name;
      param.Description = "Property Name";
      param.Optional = true;
      param.Access = GH_ParamAccess.item;

      return param;
    }

    public bool DestroyParameter(GH_ParameterSide side, int index)
    {
      return true;
    }

    public void VariableParameterMaintenance()
    {
    }

    public override bool Write(GH_IWriter writer)
    {
      return base.Write(writer);
    }

    public override bool Read(GH_IReader reader)
    {
      return base.Read(reader);
    }

    public override Guid ComponentGuid
    {
      get { return new Guid("1ae2d4ad-96ca-4fcc-970a-37373684e62b"); }
    }
  }

I admit I’m a bit baffled. Sometimes you can debug Read/Write behavior (if I’m correct this is the issue) by looking at the XML you can when you copy your component and paste it into a text editor. Do the params reflect the correct ComponentGuid?

Also looking at your CreateCustomData class it looks to be incomplete — did you omit some methods? where is the solve instance / register input/output params methods?

Lastly, make sure your classes are public!

Yes I ommited those methods, for clarity, as I am not doing anything special.

Lastly, make sure your classes are public!

Yes that was the solution…my extention was not public…argh!! Thanks a million!