C# Custom widgets


(Paul Bomke) #1

Hi all, hi David :slight_smile:

I’m wondering if it’s possible to create custom widgets.

I’ve started out creating one by inheriting from GH_Widget. The implementation itself is straight forward it seems, but how do I get my widget code to be called for it to show up on the canvas?

Is there some sort of hook that the widget needs to be registered to so they are created on document loading?

If this is not possible I’d resort to creating a custom component that behaves like a widget but I’d rather go by the proper way of doing this…

Thanks in advance!
Best,
Paul

p.s. all the widget stuff seems to be missing from the SDK, is that intended?


(David Rutten) #2

There’s no mechanism for automatically have your widgets detected and added to the menu. You can only add widgets to a canvas when a new canvas is constructed. This means that in your GHA plugin load routine (which is usually called before any canvasses are loaded, but may happen after if your plugin is loaded during runtime), you have to handle the static GH_Canvas.WidgetListCreated event. Inside the handler you’ll have to append your widget class.

As you can see it wasn’t really designed with non-standard widgets in mind.


Plugin install event
(Paul Bomke) #3

As before, your suggestions have been very helpful!

I’ve got a basic custom widget implementation running including dragging it over the canvas and the nice hand cursors. It took me a while to figure out that the first mouse event needs to return GH_ObjectResponse.Capture to get hold of the widget.
Here’s the code for the widget:

public class CustomWidget : Grasshopper.GUI.Widgets.GH_Widget
{
	public RectangleF Bounds = new RectangleF(100, 100, 24, 24);
	private bool visible = true;
	private bool drag = false;
	private PointF clickOffset = new PointF(0, 0);

	public CustomWidget() : base()
	{
	}

	public override string Description
	{
		get
		{
			return "A basic widget implementation";
		}
	}

	public override string Name
	{
		get
		{
			return "Basic widget"
		}
	}

	public override bool Visible
	{
		get
		{
			return visible;
		}
		set
		{
			visible = value;
		}
	}

	public override Bitmap Icon_24x24
	{
		get
		{
			return Grasshopper.Plugin.GH_ResourceGate.Info_24x24;
		}
	}

	public override void Render(GH_Canvas Canvas)
	{
		System.Drawing.Drawing2D.Matrix viewportTransform = Canvas.Viewport.XFormMatrix(GH_Viewport.GH_DisplayMatrix.CanvasToControl);
		Canvas.Graphics.ResetTransform();
		Canvas.Graphics.DrawImage(Icon_24x24, Bounds);
		Canvas.Graphics.Transform = viewportTransform;
	}

	public override bool Contains(System.Drawing.Point pt_control, PointF pt_canvas)
	{
		if (Bounds.Contains(pt_control) || Bounds.Contains(Owner.Viewport.ProjectPoint(pt_canvas))) return true;
		else return false;
	}

	public override GH_ObjectResponse RespondToMouseDown(GH_Canvas sender, GH_CanvasMouseEvent e)
	{
		if (e.Button == MouseButtons.Left && Bounds.Contains(e.ControlLocation))
		{
			drag = true;
			Grasshopper.Instances.CursorServer.AttachCursor(Owner, "GH_HandClosed");
			Owner.Refresh();
			clickOffset = new PointF (e.ControlX - Bounds.X, e.ControlY - Bounds.Y);
			return GH_ObjectResponse.Capture;
		}
		return GH_ObjectResponse.Ignore;
	}

	public override GH_ObjectResponse RespondToMouseUp(GH_Canvas sender, GH_CanvasMouseEvent e)
	{
		if (e.Button == MouseButtons.Left)
		{
			drag = false;
		}
		return GH_ObjectResponse.Release;
	}

	public override GH_ObjectResponse RespondToMouseMove(GH_Canvas sender, GH_CanvasMouseEvent e)
	{
		if(drag)
		{
			Bounds.X = e.ControlX - clickOffset.X;
			Bounds.Y = e.ControlY - clickOffset.Y;
			Owner.Refresh();
			return GH_ObjectResponse.Handled;
		}
		if (Bounds.Contains(e.ControlLocation))
		{
			Grasshopper.Instances.CursorServer.AttachCursor(Owner, "GH_HandOpen");
			Owner.Refresh();
			return GH_ObjectResponse.Handled;
		}
		return GH_ObjectResponse.Ignore;	
	}

	public override bool IsTooltipRegion(PointF canvas_coordinate)
	{
		// Transform canvas coordinate to control coordinate.
		PointF p = Owner.Viewport.ProjectPoint(canvas_coordinate);
		return Bounds.Contains(p);
	}
}

And that’s the override of GH_AssemblyPriority that attaches the widget to the canvas:

public class Your_AssemblyPriority : GH_AssemblyPriority
{
    public Your_AssemblyPriority() { }

    public override GH_LoadingInstruction PriorityLoad()
    {
        Grasshopper.GUI.Canvas.GH_Canvas.WidgetListCreated += new Grasshopper.GUI.Canvas.GH_Canvas.WidgetListCreatedEventHandler(addWidgets);
    }

    private void addWidgets(object o, Grasshopper.GUI.Canvas.GH_CanvasWidgetListEventArgs args)
    {
    	YourPlugin.CustomWidget w = new YourPlugin.CustomWidget();
    	args.AddWidget(w);
    }
}

This is what it looks like:

widget

Best, Paul


(Paul Bomke) #4

One problem remains: I’m trying to set the initial position of the widget to be in the top left corner of the canvas control like this:

public void Layout()
{
    Bounds.X = Grasshopper.Instances.ActiveCanvas.Width - Bounds.Width - 20;
    Bounds.Y = 20;
}

I’m calling this after args.AddWidget(w); in the Your_AssemblyPriority class.
This does not work and the widget does not show up at all, I guess because at the point where the widget is created and added to the widget list the canvas is not existing. Is that the case? Is there another possibility?

Best, Paul


(David Rutten) #5

Top left corner is always (0,0), so putting it at the fixed coordinate (30,30) will always place it top left. The other corners are trickier since they depend on the Width and Height of the canvas, which will not be known yet when the canvas constructor raises the widget event.

The way I solve it with the compass widget is to store a normalised position instead of an absolute one. (0,0)=upper left, (1,0)=upper right, (0.5, 1.0)=middle bottom etc. Then the actual absolute position is computed during drawing or mouse events based on the current canvas dimensions.


(Paul Bomke) #6

That’s one of those face palm moments.
I simply have to call my Layout method from the Render method. This way I don’t even need the relative coordinates…