How to add a Textbox in component?

image
for example, people can input string in this white text box…
like this:
image
I find it is hard to code in custom attribute…
do you have some idea to do this?,thank you!

The canvas does not play well with embedded winforms controls. If nothing else, winforms controls cannot easily be zoomed. They also mess with draw order and focus.

If you’re willing to go this risky route anyway, rather than -say- popping up a small window instead, then create a class which derives from Grasshopper.GUI.Base.GH_TextBoxInputBase, and implements the abstract HandleTextInputAccepted method.

The transform you need to embed a control onto the canvas via the void ShowTextInputBox(Control owner, string content, bool selectContent, bool limitToBoundary, Matrix transform); method is:

GH_Viewport.XFormMatrix(GH_Viewport.GH_DisplayMatrix.CanvasToControl)
1 Like


@DavidRutten
this situation is the zoom problem thing yofu mentioned?if it is ,i think it is no problem,because people usually donnot zoom while inputing…

yeah that’s it.

thank you,David

David,i follow your instruction,has a try:

public override Grasshopper.GUI.Canvas.GH_ObjectResponse RespondToMouseDoubleClick(GH_Canvas sender, Grasshopper.GUI.GH_CanvasMouseEvent e)
    {
        if((sender.Viewport.Zoom>=0.9)&& this.Bounds.Contains(e.CanvasLocation))
        {
            (Owner as ComponentTest).textInput.ShowTextInputBox(sender, (Owner as ComponentTest).textInput.Value.ToString(), true, true, sender.Viewport.XFormMatrix(GH_Viewport.GH_DisplayMatrix.CanvasToControl));
        }
        return GH_ObjectResponse.Handled;
    }

but when i double click the component,the component cannot show a textinput control…
please help
this is my totoal code(simplest edition):

using System;
using System.Collections.Generic;
using Rhino;
using Grasshopper.Kernel;
using Rhino.Geometry;
using System.Drawing;
using Grasshopper.Kernel.Attributes;
using Grasshopper.GUI.Canvas;
using Grasshopper.GUI.Base;
using System.Windows.Forms;

namespace FabUnionRobot
{
public class TextInput:GH_TextBoxInputBase
{
    protected override void HandleTextInputAccepted(string text)
    {
        this.Value= Convert.ToDouble(text);
    }
    public  double Value { get; set; }
}

public class AttributesCustom : GH_ComponentAttributes
{
    public AttributesCustom(GH_Component owner):base(owner)
    {
    }
    protected override void Layout()
    {
        base.Layout();
    }
    protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)
    {
        base.Render(canvas, graphics, channel);
    }

    public override Grasshopper.GUI.Canvas.GH_ObjectResponse RespondToMouseDoubleClick(GH_Canvas sender, Grasshopper.GUI.GH_CanvasMouseEvent e)
    {
        if((sender.Viewport.Zoom>=0.9)&& this.Bounds.Contains(e.CanvasLocation))
        {
            (Owner as ComponentTest).textInput.ShowTextInputBox(sender, (Owner as ComponentTest).textInput.Value.ToString(), true, true, sender.Viewport.XFormMatrix(GH_Viewport.GH_DisplayMatrix.CanvasToControl));
        }
        return GH_ObjectResponse.Handled;
    }
}
public class ComponentTest : GH_Component
{
    public TextInput textInput { get; set; }
    
    public ComponentTest()
      : base("ComponentTest", "CT",
          "This is the Description",
          CommonValues.Test, CommonValues.subCatalogOthers)
    {
        textInput = new TextInput();
    }
    public override void CreateAttributes()
    {
        base.m_attributes = new AttributesCustom(this);
    }
    
    protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
    {
    }

    protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
    {
    }

    protected override void SolveInstance(IGH_DataAccess DA)
    {
        Message = this.textInput.Value.ToString();
    }

    protected override System.Drawing.Bitmap Icon
    {
        get
        {
            return Resource1.AngleTest;
        }
    }


    public override Guid ComponentGuid
    {
        get { return new Guid("44974e30-fdd4-4441-9844-42bbcc0990ca"); }
    }
}
}

Did you put a breakpoint on the HandleTextInputAccepted method? Does it get hit?

David,I put a breakpoint,the HandleTextInputAccepted does get hit.

And second thing is:i add " Message = this.textInput.Value.ToString();" in SolveInstance method,and …when i double click the component,nothing show,But,when i recomputer the whole canvas,the component show the number i input…like this(i input 77):
image
unbeliveable,can you explain these,David?Thank you!

So the problem just is that you’re only assigning a local variable, you take no further action that would change the state of the Grasshopper solution.

Yes, you need to trigger a new solution, or nothing changes. After setting the value inside HandleTextInputAccepted, you must also expire the component.

Thank you David,it seems too difficult for me,so, i think may be i would do the method you told me at first reply(" rather than -say- popping up a small window instead").

I may have some time tomorrow to come up with an example, but also maybe not. I have to prepare a workshop I’m doing soon so that’s an actual deadline.

Thank you,David,i am not hurry,do the workshop first.

using System;
using Grasshopper.GUI;
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Attributes;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Parameters;
using Grasshopper.Kernel.Types;

namespace InputComponent
{
  public class NumberInputAttributes : GH_FloatingParamAttributes
  {
    public NumberInputAttributes(IGH_Param param) : base(param) { }

    protected override void Layout()
    {
      base.Layout();

      var rec = Bounds;
      rec.Width = 200;

      Bounds = rec;
    }

    public override GH_ObjectResponse RespondToMouseDoubleClick(GH_Canvas sender, GH_CanvasMouseEvent e)
    {
      if (Owner is NumberInput input)
        if (input.SourceCount == 0)
        {
          string initial = string.Empty;
          if (input.PersistentData.DataCount == 1)
          {
            var first = input.PersistentData.get_FirstItem(true);
            if (first != null)
              initial = first.ToString();
          }

          var matrix = sender.Viewport.XFormMatrix(GH_Viewport.GH_DisplayMatrix.CanvasToControl);
          var field = new NumberInputTextField(input)
          {
            Bounds = GH_Convert.ToRectangle(Bounds)
          };

          field.ShowTextInputBox(sender, initial, true, true, matrix);
          return GH_ObjectResponse.Handled;
        }

      return base.RespondToMouseDoubleClick(sender, e);
    }
  }

  internal class NumberInputTextField : Grasshopper.GUI.Base.GH_TextBoxInputBase
  {
    private readonly NumberInput _input;

    public NumberInputTextField(NumberInput input)
    {
      _input = input ?? throw new ArgumentNullException(nameof(input));
    }

    protected override void HandleTextInputAccepted(string text)
    {
      if (GH_Convert.ToDouble(text, out var number, GH_Conversion.Both))
      {
        _input.PersistentData.Clear();
        _input.PersistentData.Append(new GH_Number(number), new GH_Path(0));
        _input.ExpireSolution(true);
      }
    }
  }

  public class NumberInput : Param_Number
  {
    public NumberInput()
    {
      Name = "Number Input";
      NickName = "NumIn";
      Description = "A numeric value with double-click input.";
    }

    public override void CreateAttributes()
    {
      m_attributes = new NumberInputAttributes(this);
    }
    public override Guid ComponentGuid { get { return new Guid("{1B8BD2D9-660F-48A3-B02C-A81D6C0B9505}"); } }
  }
}
3 Likes

Thank you very much,David,i am reading.
//======
i have read your code,David,and understand,and do a test component(not param) same kind:
image
Thank you very much!

1 Like

Greeetings of the day, iam also trying to do the same in GHpython, can i ask for some help @andrealu2012 @DavidRutten