How to change the number of output param ?

for example,i am do a component in c#.
input is one list of double,i want to update the number of output param according to the “count of this list”.
how to do that?i failed, this is my code:

        protected override void SolveInstance(IGH_DataAccess DA)
        {
            List<double> a=new List<double>();
            DA.GetDataList("A", a);
            int list_cnt = a.Count;
          
            if( list_cnt > Params.Output.Count)
            {
                Params.Output.Clear();
                for(int i=0;i<  list_cnt ; i++)
                {
                    Param_Number new_param = new Param_Number();
                    new_param.Name = $"A{i + 1}";
                    new_param.NickName= $"A{i + 1}";
                    new_param.Description = $"A{i + 1} in Degree";
                    new_param.MutableNickName = false;  
                    new_param.Access = GH_ParamAccess.item;
                    Params.RegisterOutputParam(new_param);
                }
            }

            for (int i = 0; i < list_cnt ; i++)
            {
                DA.SetData(i ,a[i]);
            }

        }

this code has error window like this:

hope some advise,thank you.

Hi,

You cannot change the params during a solution. You would have to schedule a new solution in SolveInstance using

And use a method Callback in which you add/remove sufficient inputs/outputs.

I’ve done it for my Smart Dispatch, here is an extract of the code :

public int paramdiff = 0;

        protected override void SolveInstance(IGH_DataAccess DA)
        {
            //get inputs, do some stuff

            //check outputs count
            int N = Params.Output.Count;
            int target = inList.Count;        
            paramdiff = N - target;

            if (paramdiff != 0)
            {
                GH_Document gdoc = Instances.ActiveCanvas.Document;
                gdoc.ScheduleSolution(0, CallBack);
            }
            else
            {
               //do your stuff here once you know you have the correct amounf of outputs.
            }
        }

        public void CallBack(GH_Document gH_Document)
        {
            //Variable Parameter Maintenance            

            //remove unnecessary outputs
            for (int i = 0; i < paramdiff; i++)
            {
                Params.UnregisterOutputParameter(Params.Output[Params.Output.Count - 1]);
                Params.OnParametersChanged();
            }

            //add necessary outputs
            for (int i = 0; i < -paramdiff; i++)
            {
                Param_GenericObject hi = new Param_GenericObject();
                hi.Access = GH_ParamAccess.tree;
                Params.RegisterOutputParam(hi, Params.Output.Count);
            }

            Params.OnParametersChanged();
            this.ExpireSolution(true);
        }

thank you, Magicteddy,the code i have tried,it not works,the function"CallBack"will run again and again.
so I delete “this.expiresolution(true)”.
After deleting “this.expiresolution(true)”,the code works nice,Do you know what is the reason?

Without seeing your entire code it’s hard to tell.

What sould happen is :
A solution is triggered. At some point SolveInstance() of this component is called and the int paramdiff is calculated.
If it’s !=0, a new solution is scheduled. But, at this point Grasshopper doesn’t know what to recompute.
Callback() is called once, the component is expired, so this tells Grasshopper that this component needs to be recomputed.
Callback() ends, the new solution starts immediately, At some point during that solution SolveInstance() is called again, but this time it avoids the scheduling and proceeds to the next piece of code.

If you remove ExpireSolution(true), this component will not be recomputed after its outputs have been added/deleted.

this is my real code:

public class JustTest : GH_Component, IGH_VariableParameterComponent
    {
        /// <summary>
        /// Initializes a new instance of the JustTest class.
        /// </summary>
        public JustTest()
          : base("JustTest", "Nickname",
              "Description",
              CommonValues.Catalog, CommonValues.subCatalogCMD)
        {
        }
    
        /// <summary>
        /// Registers all the input parameters for this component.
        /// </summary>
        protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
        {
            pManager.AddNumberParameter("N", "", "", GH_ParamAccess.list);
        }

        /// <summary>
        /// Registers all the output parameters for this component.
        /// </summary>
        protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
        {
            VariableParameterMaintenance();
        }
        int paramdiff = 0;
        /// <summary>
        /// This is the method that actually does the work.
        /// </summary>
        /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param>
        protected override void SolveInstance(IGH_DataAccess DA)
        {
            List<double> alist=new List<double>();
            DA.GetDataList("N", alist);

            int N = Params.Output.Count;
            int targetCnt = alist.Count;
             paramdiff = N - targetCnt;

            if (paramdiff<0)
            {
                GH_Document gdoc = Instances.ActiveCanvas.Document;
                gdoc.ScheduleSolution(1, CallBack);
            }
            else
            {
                for(int i=0;i<targetCnt;i++)
                {
                    DA.SetData(i, alist[i]);
                }
            }
        }

        private void CallBack(GH_Document doc)
        {
            
            if (paramdiff < 0)
            {
                for (int i = 0; i < -paramdiff; i++)
                {
                    Param_Number hi = new Param_Number();
                    hi.Access = GH_ParamAccess.item;
                    Params.RegisterOutputParam(hi, Params.Output.Count);
                }
            }
            else if (paramdiff > 0)
            {
                for (int i = 0;i<paramdiff;i++)
                {
                    Params.UnregisterOutputParameter(Params.Output[Params.Output.Count - 1]);
                }
            }
            VariableParameterMaintenance();
            Params.OnParametersChanged();
             //this.ExpireSolution(true);
        }

        public bool CanInsertParameter(GH_ParameterSide side, int index)
        {
            return false;
        }

        public bool CanRemoveParameter(GH_ParameterSide side, int index)
        {
            return false;

        }

        public IGH_Param CreateParameter(GH_ParameterSide side, int index)
        {
            return new Param_GenericObject();

        }

        public bool DestroyParameter(GH_ParameterSide side, int index)
        {
            Params.UnregisterOutputParameter(Params.Output[index]);
            return true;
        }

        public void VariableParameterMaintenance()
        {
            int num2 = Params.Output.Count - 1;
            for (int j = 0; j <= num2; j++)
            {
                IGH_Param iGH_Param2 = Params.Output[j];
                iGH_Param2.Access = GH_ParamAccess.item;
                iGH_Param2.MutableNickName = true;
                iGH_Param2.NickName = $"A{j+1}";
                iGH_Param2.Name = $"Item ({iGH_Param2.NickName})";
                iGH_Param2.Description = $"Item ({iGH_Param2.NickName})";
            }
        }

        /// <summary>
        /// Provides an Icon for the component.
        /// </summary>
        protected override System.Drawing.Bitmap Icon
        {
            get
            {
                //You can add image files to your project resources and access them like this:
                // return Resources.IconForThisComponent;
                return null;
            }
        }

        /// <summary>
        /// Gets the unique ID for this component. Do not change this ID after release.
        /// </summary>
        public override Guid ComponentGuid
        {
            get { return new Guid("8D1107E8-0881-4826-AB80-379C5AD32894"); }
        }
    }

if i hide “this.ExpireSolution(true);” ,it works nice.but i not hide,the CallBack function will run again and again, and never run into solveinstance().

I just ran the code and I can confirm ExpireSolution(true) IS required, else the outputs of the component are null. So the component is working properly with a single list as input.

The issue I did not see at first is that your component has its input as list, so SolveInstance() is called for each branch of the tree, and Callback is only called after all of those run. This makes Callback being called as many times as the number of branches, and with the wrong value for paramdiff.

The optimal thing would be to compute paramdiff using VolatileData during BeforeSolveInstance(). It gets called once, and the solution is scheduled if it’s negative. Then all SolveInstance()s run, but do nothing. Then CallBack gets called once, solution is expired and this time the outputs are set.

public class JustTest : GH_Component, IGH_VariableParameterComponent
    {
        /// <summary>
        /// Initializes a new instance of the JustTest class.
        /// </summary>
        public JustTest()
          : base("JustTest", "Nickname",
              "Description",
              "Magic", "Util")
        {
        }

        /// <summary>
        /// Registers all the input parameters for this component.
        /// </summary>
        protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
        {
            pManager.AddNumberParameter("N", "", "", GH_ParamAccess.list);
        }

        /// <summary>
        /// Registers all the output parameters for this component.
        /// </summary>
        protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
        {
        }
        
        int paramdiff = 0;
        /// <summary>
        /// This is the method that actually does the work.
        /// </summary>
        /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param>
        protected override void BeforeSolveInstance()
        {
            IGH_Structure ighs = Params.Input[0].VolatileData;
            int maxLen = 0;
            for (int i = 0; i < ighs.PathCount; i++)
            {
                maxLen = Math.Max(maxLen, ighs.get_Branch(i).Count);
            }
            paramdiff = Params.Output.Count - maxLen;

            if (paramdiff < 0)
            {
                GH_Document gdoc = Instances.ActiveCanvas.Document;
                gdoc.ScheduleSolution(0, CallBack);
            }
        }

        protected override void SolveInstance(IGH_DataAccess DA)
        {
            List<double> alist = new List<double>();
            DA.GetDataList(0, alist);
            
            if (paramdiff>=0)
            {               
                for (int i = 0; i < alist.Count; i++)
                {
                    DA.SetData(i, alist[i]);
                }
            }
        }

        private void CallBack(GH_Document doc)
        {
                for (int i = 0; i < -paramdiff; i++)
                {
                    Param_Number hi = new Param_Number();
                    hi.Access = GH_ParamAccess.item;
                    Params.RegisterOutputParam(hi, Params.Output.Count);
                }

                VariableParameterMaintenance();
                Params.OnParametersChanged();
                this.ExpireSolution(true);
        }

        public bool CanInsertParameter(GH_ParameterSide side, int index)
        {
            return false;
        }

        public bool CanRemoveParameter(GH_ParameterSide side, int index)
        {
            return false;

        }

        public IGH_Param CreateParameter(GH_ParameterSide side, int index)
        {
            return new Param_GenericObject();
        }

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

        public void VariableParameterMaintenance()
        {
            int num2 = Params.Output.Count - 1;
            for (int j = 0; j <= num2; j++)
            {
                IGH_Param iGH_Param2 = Params.Output[j];
                iGH_Param2.Access = GH_ParamAccess.item;
                iGH_Param2.MutableNickName = true;
                iGH_Param2.NickName = $"A{j + 1}";
                iGH_Param2.Name = $"Item ({iGH_Param2.NickName})";
                iGH_Param2.Description = $"Item ({iGH_Param2.NickName})";
            }
        }

        /// <summary>
        /// Provides an Icon for the component.
        /// </summary>
        protected override System.Drawing.Bitmap Icon
        {
            get
            {
                //You can add image files to your project resources and access them like this:
                // return Resources.IconForThisComponent;
                return null;
            }
        }

        /// <summary>
        /// Gets the unique ID for this component. Do not change this ID after release.
        /// </summary>
        public override Guid ComponentGuid
        {
            get { return new Guid("8D1107E8-0881-4826-AB80-379C5AD32894"); }
        }
    }
2 Likes

Great code and video,thank you Magicteddy!