#C override LinkedParameterAttributes

grasshopper
unhandled

(Paul Bomke) #1

Hi all,

I’ve written some custom parameter attributes to handle double click events in order to show a small menu.
paramAttributes

The problem is, once I add this parameter to a component as an input, it changes its attributes to GH_LinkedParamAttributes which do of course not implement my custom double click behaviour.

I’ve tried to work with the component’s mouse events but they are not firing if the mouse is over one of the inputs.

Is there a way to override GH_LinkedParamAttributes so I can have my double click behaviour for embedded parameters or is it possible to assign new attributes to an embedded parameter?

I’m looking forward to reading your comments :slight_smile:

Thanks and kind regards,
Paul


(David Rutten) #2

Nope, once a parameter is part of a component the events are handled by the component. All you can do at this point is provide a custom context menu.


(Paul Bomke) #3

Hi David and thanks for the quick response.

You say all events are handled by the component – which is fine as I’m also customizing the component’s attributes. However, my RespondToMouse…() methods in the component’s attributes don’t fire if I click on an input parameter. Is there maybe a way to change this? I would only need the double click event which should not interfere with the basic input behaviour.


(David Rutten) #4

Oh dear… you could try overriding the AppendToAttributeTree on your custom component attributes and reversing the order of the attributes you return. This will include a call to the base-class method with an empty list, reversing that list and then appending it to list passed into the method. This will cause Grasshopper to ‘find’ your component attributes before it finds the input and output attributes. I suspect however that such a change will cause all sorts of annoying side-effects.


(Paul Bomke) #5

Thanks again, David!

I’ve changed my custom component attribute’s AppendToAttributeTree like so:

public override void AppendToAttributeTree(List<IGH_Attributes> attributes)
	{
		List<IGH_Attributes> foo = new List<IGH_Attributes>();
		base.AppendToAttributeTree(foo);
		foo.Reverse();
		attributes.AddRange(foo);		
	}

No I’m stuck with the annoying side-effects you mentioned :wink:

I’m able to handle mouse events on the component even if the end up on the parameters. For example, in my component attribute’s RespondToMouseDoubleClick – which I would like to customize – I do

for (int i = 0; i < comp.Params.Input.Count; i++)
{
	if (comp.Params.Input[i].Attributes.Bounds.Contains(e.CanvasLocation))
	{
		// TODO: Paint small menu on top of paramter...
	}
}

However, I’m experiencing the problem that all other mouse events (up, down, move) do not get passed on to the underlying parameters. In RespondToMouseDown I do:

for (int i = 0; i < comp.Params.Input.Count; i++)
{
	if (comp.Params.Input[i].Attributes.Bounds.Contains(e.CanvasLocation))
	{
		// Pass the event to the parameter.
		return GH_ObjectResponse.Ignore;
	}
}

I thought that simply returning GH_ObjectResponse.Ignore in the component attributes would cause the mouse event to be passed on to the next item in the attributes tree – which are the parameters.

I’ve also tried calling comp.Params.Output[i].Attributes.RespondToMouseDown(sender, e); directly from the component’s mouse event handler, but that didn’t work either and also feels a little hacky.

If I modify the component’s isPickRegion to return false if the mouse is on one of the parameters the mouse events get handled by the parameters, but then of course I cannot catch my double click anymore :frowning:

Any suggestions are greatly apprechiated!

Best and thanks again,
Paul


(David Rutten) #6

Yeah that’s expected. Grasshopper finds the topmost attribute underneath the mousecursor and then lets it handle the event. There’s no fall-through.

That is somewhat surprising, I was hoping that would work.

I’ll have to think about this some more, but there may not be an answer. Not even an ugly one.


#7

Maybe I’m wrong but if I understood correctly your conversation until here, you just want a form/window to pop up when you double-click on the parameter input of a component?


(Paul Bomke) #8

Yes, your understanding is correct :slight_smile:
Did you accomplish something like that before?


#9

This is actually pretty straightforward, I think David Rutten posted this similarly in another post:
You need to create a new class which herits from GH_LinkedParamAttributes, should look somehow like this:

public class SpecialParamAttributes : GH_LinkedParamAttributes
{
    private MyComponent mainComponent;
    private MainForm mainForm;

    public SpecialParamAttributes(IGH_Param param, MyComponent  mainComponent)
        : base(param, mainComponent.Attributes)
    {
        this.mainComponent = mainComponent;
    }

    public override GH_ObjectResponse RespondToMouseDoubleClick(GH_Canvas sender, GH_CanvasMouseEvent e)
    {
        if (e.Button == MouseButtons.Left)
        {
            // get an instance of the main form, show this instance
            this.mainForm = MainForm.GetInstance(this.mainComponent);

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

Then you need to assign those attributes in your component to the respective input or output parameter.

    protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
    {
        // create custom parameter attribute: on double click create main form
        if (Attributes == null) CreateAttributes(); 
        Params.Input[0].Attributes = new SpecialParamAttributes(Params.Input[0], this);
        Params.Input[1].Attributes = new SpecialParamAttributes(Params.Input[1], this);
    }

Hope that helps,
Daniel


(Paul Bomke) #10

Hi Daniel,

yes, that works, thanks a lot :slight_smile:
I’ve tried this before but made the mistake to assign my custom attributes that inherited from GH_Attributes<GH_Param<T>> instead of GH_LinkedParamAttributes like you suggested.

Now I ran into the next problem: it seems like GH_LinkedParamAttribute's Render method is only called with the Wires channel so I can’t really draw my menu on top of the parameter. I guess that drawing should be done in the component’s Render method instead?

Thanks again!
Best,
Paul


#11

Sorry, never done that before…GH_LinkedParamAttributes does have a general Render method though where also a channel can be specified? Not sure if I understand completely what you want to do.
Anyhow, probably a question for David Rutten…


(David Rutten) #12

I think it’s clear Grasshopper really doesn’t want you to do this.

It’s true, only the component attributes are involved in drawing the component itself. I think you already have custom attributes for your component, so you could transfer the code there.

However it may make more sense to start a new interaction object when your parameters are double clicked. Interactions get dibs on handling mouse and key events, so especially if your radial menu is bigger than your object, Grasshopper might not even consider that you would be interested in a mouseclick somewhere out in the middle of nowhere.

To make a new interaction, derive a class from Grasshopper.GUI.Canvas.Interaction.GH_AbstractInteraction, handle the GH_Canvas.CanvasPostPaintWidgets event so you have a place to draw your menu.

Implement the RespondToMouseXXXX methods and please return GH_ObjectResponse.Release whenever there’s an event that means you wish to close the menu.

To start your menu interaction assign a new instance of it to the GH_Canvas.ActiveInteraction property. This is btw. how the standard Radial menu works.


(Paul Bomke) #13

OK, that’s very good advice :slight_smile:
I never really thought of handling the menu outside of the component or parameter attributes, but it makes a lot more sense and feels less hacky.

I’ve done as you suggested and it works very well.
In the parameter attributes I simply handle the double click event and create a new menu instance:

public override GH_ObjectResponse RespondToMouseDoubleClick(GH_Canvas sender, GH_CanvasMouseEvent e)
{
    if (e.Button == System.Windows.Forms.MouseButtons.Left)
    {
   	if (boundsClickRegion.Contains(e.CanvasLocation))
  	{
	    GH_RadialMenuCustom menu = new GH_RadialMenuCustom(sender, e, menuIcons);
	    menu.EventClick += new GH_RadialMenuCustom.ClickEventDelegate(menuClickHandler);
	    sender.ActiveInteraction = menu;
	}
    }
    return base.RespondToMouseDoubleClick(sender, e);
} 

I then do the drawing and mouse event stuff in my custom GH_RadialMenuCustom class.

nice

A few more question have arisen: I’m inheriting form GH_AbstractInteraction like this:

public class GH_RadialMenuCustom : Grasshopper.GUI.Canvas.Interaction.GH_AbstractInteraction
{
	public GH_RadialMenuCustom(GH_Canvas canvas, GH_CanvasMouseEvent mouseEvent, List<System.Drawing.Bitmap> icons, bool activeOnMotion = true)
		: base(canvas, mouseEvent, activeOnMotion)
	{
		// Listen to events for rendering.
		canvas.CanvasPostPaintWidgets += new GH_Canvas.CanvasPostPaintWidgetsEventHandler(Render);
	}
}

What is the activeOnMotion flag used for?

And also: I’m currently keeping the menu size independent of zoom level by using GH_Canvas.Viewport.Zoom to adjust the menu radius and icon sizes. I was wondering if this is a good way to do this or if the canvas provides methods to set the scale to 1 during drawing of my menu.

And just out of curiosity, are widgets interactions as well?

Thanks a lot for the great help so far!
Best,
Paul


(David Rutten) #14

Cool! Pretty impressive all told. Incidentally Interactions are good for dealing with mouse events, if you ever find yourself in a position where you just want to draw some additional stuff onto the canvas, have a look at the Grasshopper.GUI.Canvas.TagArtists.GH_TagArtist class.

Some interactions ‘start’ on mousedown, but don’t actually start doing stuff until after the mouse moves. Panning and Ctrl Zooming, and Window Selection for example. This flag allows the interaction to be registered with the canvas while it gives you a way to delay changing cursors and drawing gunk until the mouse actually moves.

Yeah it’s fine, I do the same myself for certain drawing operations. Sometimes when I have to draw a lot in control-space (MRU tiles, dragdrop pagecurl, control+tab doc switching) I reset the transform on the Graphics object to make my life easier. But then you have to remember to always restore the correct transform afterwards.

Nope, they are from the Grasshopper.GUI.Widgets namespace. Interactions were designed to provide a temporary override of the normal behaviour of the canvas, whereas widgets are assumed to be on the screen indefinitely. They probably could have been the same class, but due to the organic (to pick a polite word) nature of the GH development process they ended up being something different.


(Paul Bomke) #15

Thanks for the pointers :slight_smile:
I’ve played around with the viewport transform and found this to work:

// Get the current transformation matrix.
System.Drawing.Drawing2D.Matrix matrixCanvas = canvas.Viewport.XFormMatrix(GH_Viewport.GH_DisplayMatrix.ControlToCanvas);

// Set graphics object to default transformation matrix.
canvas.Graphics.Transform = new System.Drawing.Drawing2D.Matrix(1,0,0,1,0,0);

// Sophisticated drawing.
canvas.Graphics.DrawRectangle(new Pen(Brushes.Blue), new Rectangle(100, 100, 100, 100));

// Set the graphics object back to it's canvas transform.
canvas.Graphics.Transform = matrixCanvas;

zoom

Thanks again :slight_smile:


(David Rutten) #16

There’s a Graphics.ResetTransform() method that does that, means you don’t have to cook up your own identity matrix.

There’s also a mechanism in system.drawing that allows you to save and restore states of the Graphics object, which I think includes transformations and also clipping regions.


(Paul Bomke) #17

Nice to know!