Bug in the Params.ParameterNickNameChanged Event (ZUI Components)

Hello,

I am dealing with a ZUI component that implements the IGH_VariableParameterComponent interface, and facing a few issues with managing Input Parameters NickName Changes, maybe bugs?

I tried many ways with different Events but was hoping to rely on the obvious Params.ParameterNickNameChanged event which unfortunately seems to have a bug where it will only fire if the user hits return, but will Not work on clicking back on the canvas! (Bug? as reported previously here: https://www.grasshopper3d.com/forum/topics/gha-developers-implementing-variable-parameters?commentId=2985220%3AComment%3A750575)

On an issue that follows a workaround to avoid the issue above, what would be the best way to “Refresh” the component once the NickName change happens? I am trying to keep track of the NickNames at all times but found that the component doesn’t know of the new change unless it gets recomputed on Expire( ) - which I am trying to avoid! mainly because I’m relying on the Params.ParameterChanged event now which fires while the user is typing the nickname - instead, performance-wise, I’d prefer to Expire it one time only - if necessary, once the whole nickName change is fully done (which is the expected Params.ParameterNickNameChanged behaviour).

Also, I noticed that sometimes the Events are Firing multiple times with a single action, especially when using the Params.ParameterChanged event!

Anyone got any ideas around the topic?

Sorry I missed this post originally. I’ll try and find what the problem is when I get back into the office.

1 Like

Yup, the event is only raised when the NickName is specifically accepted, which is silly because the changes are immediate. I’m afraid you’ll have to handle the events on the parameters themselves, rather than relying on the Params version.

using System;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Parameters;

namespace VarNickNameTest
{
  public class VarNickNameTextComponent : GH_Component, IGH_VariableParameterComponent
  {
    public VarNickNameTextComponent()
      : base("Var Param NickName Test", "VPNT", "Test nickname changes on variable inputs/outputs", "Test", "Test")
    { }
    public override Guid ComponentGuid => new Guid("00000000-5924-4d2c-b08b-41d7fb8ea358");

    protected override void RegisterInputParams(GH_InputParamManager pManager)
    {
      pManager.AddGenericParameter("A", "A", "A", GH_ParamAccess.tree);
    }
    protected override void RegisterOutputParams(GH_OutputParamManager pManager)
    {
      pManager.AddGenericParameter("A", "A", "A", GH_ParamAccess.tree);
    }
    protected override void SolveInstance(IGH_DataAccess DA)
    {
      // nothing needed.
    }

    bool IGH_VariableParameterComponent.CanInsertParameter(GH_ParameterSide side, int index)
    {
      return true;
    }
    bool IGH_VariableParameterComponent.CanRemoveParameter(GH_ParameterSide side, int index)
    {
      return true;
    }
    bool IGH_VariableParameterComponent.DestroyParameter(GH_ParameterSide side, int index)
    {
      // TODO: the event handler should probably be removed here as well.
      return true;
    }

    IGH_Param IGH_VariableParameterComponent.CreateParameter(GH_ParameterSide side, int index)
    {
      return new Param_GenericObject
      {
        Name = "A",
        NickName = "A",
        Description = "A"
      };
    }
    void IGH_VariableParameterComponent.VariableParameterMaintenance()
    {
      foreach (var param in Params)
      {
        param.ObjectChanged -= ParamChangedHandler;
        param.ObjectChanged += ParamChangedHandler;
      }
    }
    private void ParamChangedHandler(IGH_DocumentObject sender, GH_ObjectChangedEventArgs e)
    {
      if (e.Type == GH_ObjectEventType.NickName)
        Rhino.RhinoApp.WriteLine($"Parameter nickname changed: \"{e.Sender.NickName}\"");
    }
  }
}
1 Like

Thanks David!

That’s a great workaround! I gave it a shot with a gh c# script component and works nicely. Will test it within the plugin & feedback soon.

Also, what would be the most efficient way to “refresh” the component once a nickName is changed? from my tests (including this one) I noticed the new NickName wouldn’t be recognized in the component until it gets recomputed… I’ve experimented with:

  • ExpireSolution(true),
  • Params.OnParametersChanged(), (do we need to run this everytime there is a change? or just on addition/removal of zui params?)
  • Params.Sync() (though i’m not very clear on how this entierly work within the component):
    GH_ComponentParamServer.IGH_SyncObject ighsync = Params.EmitSyncObject();
    then later calling: Params.Sync(ighsync);

I’m having to trigger a recompute within the event handler now to let it recognize the nickName change (is that the intended behaviour?):

private void RunScript (object ParamA, object ParamB, ref object A)
{
    SetupEventHandlers ( );
    var nickNames = new List<Object> ( );
    foreach (IGH_Param input in Component.Params.Input)
        nickNames.Add (input.NickName);
    A = nickNames;
}
// <Custom additional code> 
private bool _handled = false;
private void SetupEventHandlers ( )
{
    if (_handled)
        return;
    foreach (IGH_Param input in Component.Params.Input)
        input.ObjectChanged += InputParamChanged;
    _handled = true;
}
private void InputParamChanged (IGH_DocumentObject sender, GH_ObjectChangedEventArgs e)
{
    if (e.Type == GH_ObjectEventType.NickName)
    {
        Rhino.RhinoApp.WriteLine ("Parameter nickname changed: \"{0}\"", e.Sender.NickName);
        Component.ExpireSolution (true);
    }
}

any thoughts?

You should never have to call Params.OnParametersChanged unless you are adding or removing parameters outside of the standard mechanisms. The ui that comes with IGH_VariableParameterComponent does all that for you. Same with the Sync method.

If you need your component to update on name changes then ExpireSolution is the way to go. However I’d avoid calling it on every name change. That’ll be too often and it may also introduce delays between the typing of characters.

Ideally what you’d do is start a timer on every name change event for -say- half a second, and then destroy that timer and start a new one if another event comes in before it ticks. So you can keep postponing the recalculation until the user is done typing. That’s quite a lot of code though, so you may just want to schedule a solution inside the event.

// doing this on a tablet from memory... code is probably not correct. 
private void ParameterChangedHandler(...)
{
  if (e.Type == ...NickName)
    OnPingDocument.ScheduleSolution(1000, CallbackMethod);
}
private void CallbackMethod(GH_Document doc)
{
  ExpireSolution(false);
}
1 Like

Thanks David!

I thought of implementing an Async solution sometime ago as a workaround to triggering a recompute for the ParamsChanged Events, but wasn’t sure if it was the right thing to do…
I did indeed have lots of recomputes happening at every single letter typing, which is insane, I’ll go for the timer now… thanks for pointing me to the OnPingDocument.ScheduleSolution(). Many thanks!

Hi @DavidRutten, I’m running into issues with the timer logic and doesn’t seem to be the way to go, can I ask how is the “SortList” component put together? Is it triggering a recompute to update the output nickname as we type the inputs nicknames?

To share my particular scenario, I am developing a component that parses an input string for a specific Regex pattern and recognizes those to create input params… the component then matches the param NickName to its input replacement for the matched Regex patterns:

once we hit the “Generate Inputs” context menu option (or a button trigger in the _Generate input) then all the detected strings get created automatically and inputs get populated in the script output string:

I’m also allowing manual Input variables user creation with a ZUI interface to allow more Grasshopper-y script components approach like the c# & python components - and this is where I have the issue with the NickName Events and where I’d like to optimize the component performance and nickname changes.

.

So I’m kind of doing a mixed approach between the ExplodeTree + SortList OOTB components.

Let me know your thoughts.
Thanks!

It does exactly as described above, handling the Params.ParameterChanged event. However the names of the inputs and outputs are not relevant to the Sort component, so it never triggers a new solution, it just immediately sets the name of the corresponding input/output to the same as the parameter which was changed.

Names of inputs/outputs I think only matter for Expression and Script components in the standard component set, so all other name changes do not require a new solution.

1 Like

Thank you David.