Detect KeyDown and MouseDown Events from ScriptComponent

unhandled

#1

From a downstream object to a MD Slider I need to know if a user holds down the CTRL key while dragging the slider around with the Left Mouse Button. How can I detect the CTRL-Key from the downstream object? Is it even possible? I mean, I cannot override anything in the other component unless I subclass it and… no.

Is there any other way? C#.

// Rolf


#2

Hello,

there is a possibility for sure. One approach I could imagine:
You need to…

  • …get the md sliders canvas location and size,
  • get the screen coordinates of the md slider, check if this area is currently shown on the screen
  • check if the gh window has focus
  • check if the mouse is passing that screen area,if so check if the ctrl key and left mouse button is pressed.
    Of course you do the checking in a separate thread, which is running continuously .

You can do this by calling user32 (winapi) commands. However, I wouldn’t do that in script components. And even if there is a ready-to-use event to register, this can yield hard-to-debug behaviour.

In the end, no matter if win form, wpf, qt or whatever. All do have in common being a window in windows, so you can always modify programs on a low level basis.


#3

Hmpf. I’m not experienced with .NET Windows programming. In Delphi everything was so simple… :slight_smile:

Well, I guess I have to dig into it, reluctantly. Thanks anyway.

// Rolf


#4

You can subscribe the mouse events of canvas control (mouseDown, mouseUp, mouseMove) to check when is dragging and KeyDown event to check is ctrl is pressed.

Also you can use the Microsoft.VisualBasic.Devices.Keyboard class to check keyboard special buttons as Ctrl.


#5

Dani is right, you can also access this one level higher.
Its just that using user32.dll requires some learning, but once you get it, its not that complicated anymore. Its the p/invoke process, but its actually quite useful when working with unmanaged libraries. Actually you find a lot of documentation on Stack overflow for C#. Simply copy and paste. You will not be the first one asking that particular question about winapi.


#6

In VS 2015 it seems that namespace isn’t accessible. It fails on Devices.

bild

Googling.

// Rolf


#7

Add reference to Microsoft.VisualBasic.dll


#8

Ah, that’s what happens when you haven’t slept for 48 hours. :sleeping: It works just fine.

// Rolf


#9

Now I’ve been searching the Grasshopper SDK for six-seven hours, and I just can’t find such documentation which helps me hook the GH mouse events correctly.

I had no problem finding and hooking up to Rhino’s Keyboard events (and Mouse events too) but that was easy probably just because the Rhino Mouse was not what I wanted.

public class TestMouseCallback: Rhino.UI.MouseCallback

Anyway, the following two variants is the only thing I got to compile, after so many hours of searching and browsing and trying:

  private void RunScript(ref object A)
  {
    var canvas = Grasshopper.Instances.ActiveCanvas;
    if (canvas == null)
      return;

    canvas.DocumentObjectMouseDown += new GH_Canvas.DocumentObjectMouseDownEventHandler(DOBJ_Callback);

    canvas.MouseDown += new System.Windows.Forms.MouseEventHandler(Forms_Callback);
  }

//

  public void DOBJ_Callback(Object sender, GH_CanvasObjectMouseDownEventArgs e)
  {
    Print("DOBJ MouseDown: Piiip!");
  }

  public void Forms_Callback(object sender, MouseEventArgs e)
  {
    Print("Forms MouseDown: Piiip!");
  }

however, neither of them fires any events when clicking around on the Canvas. It’s probably something trivial I’m missing, but sleepy as I am I just don’t find the right way of doing this.

But now it’s time to hit the sack.

// Rolf


#10

GH canvas is a system.windows.forms.control:
https://msdn.microsoft.com/es-es/library/system.windows.forms.control(v=vs.110).aspx

Before you subscribe to an event, you must make sure you unsubscribe it before reassigning it to the same instance.

private void RunScript(bool x, object y, ref object A)
{
  if(x) Grasshopper.Instances.ActiveCanvas.MouseDown += MyMouseDown;
  else Grasshopper.Instances.ActiveCanvas.MouseDown -= MyMouseDown;
}

public void MyMouseDown(object sender, MouseEventArgs e){
  RhinoApp.WriteLine(e.Button.ToString() + " button clicked");
}

You can also do the same for KeyDown event.


#11

you can always unsubscribe before subscribing. That doesn’t yield any error and works
Grasshopper.Instances.ActiveCanvas.MouseDown -= MyMouseDown;
Grasshopper.Instances.ActiveCanvas.MouseDown += MyMouseDown;

However as said: I would not doing this in scriptcomponents, because even if you change a whitespace inside your code, you will create a different script instance, impossible to unsubscribe the MyMouseDown from that other script instance (except closing Rhino).

So doNotKnowTheCorrectNameOfThisInstance1.MyMouseDown is different to
doNotKnowTheCorrectNameOfThisInstance2.MyMouseDown.


#12

Yes but well, just unsubscribe it before you change the code,
change the code > x = true > do something > x = false > change the code.


#13

yeah in this particular case it works, just saying this because I didn’t know this before I was doing Eventhandling in script components for the first time. I had to deal with really weird behaviour and it “wasted” a lot of time and energy :slight_smile:


#14

Hi @Dani_Abalde,
Thank you for your help. Yes, I noticed that there were a difference between the two events I subscribed to, but I don’t really understand why my event din’t fire because it looks like I already had it hooked (the second alternative):

Oh well. Remains the problem pointed out by @TomTom.

Back in the days of Delphi, which didn’t (doesn’t) have GarbageCollection I used a dirty trick utilizing Interfaces to do the “GC” for me, by embedding variables in a interface (calling them Guard), then when the code would go out of scope (leaving a method) any local variables based on this interface would be disposed of, due to the reference counting mechanism. Running it would look like pictured below (if I had the correct hook to the GH_Component, that is)

In this case I cannot really “hook” any such variable to the GH_Component class (since @DavidRutten didn’t provide any “OnDispose” hook… but he sure will…?), so to illustrate the idea I made a separate class to host the event, but the idea is the same; When the class is being disposed of, the Dispose method would unregister (unhook) the MouseDown delegate.

The code:

  private void RunScript(object x, ref object A)
  {
    var canvas = Canvas;

    if (canvas == null)
      return;

    if (m_gcevents == null)
    {
      m_gcevents = new GuardedMouseDownEvent(); // implements IDisposable
      m_gcevents.m_owner = this;
      m_gcevents.MouseDown_Subscriber(true);
    }

    Reflect(m_gcevents); // Just some blah-blah code

    System.Threading.Thread.Sleep(500);

    // Garbage collector should take care of unsubscribing, but...
    m_gcevents.Dispose(); 
    // ...sigh. Calling Dispose is only my dirty thing here, since the GC doesn't 
    // seem to be polite enough to actually Dispose of the object.
    m_gcevents = null;
  }

The implementations :

  // Still in Script_Instance...
  private GuardedMouseDownEvent m_gcevents;

  public GH_Canvas Canvas {
    get  { return Grasshopper.Instances.ActiveCanvas;  }
  }

and on the follwing lines the class dealing with MouseDown event and, in the next section, implementing the IDisposable, the dirty trick of sorts:

  // ========================================
  // CLASS GuardedMouseDownEvent
  // ========================================
  public class GuardedMouseDownEvent: IDisposable
  {
    public Script_Instance m_owner; // back reference to the script component

    public GH_Canvas Canvas {
      get { return Grasshopper.Instances.ActiveCanvas; } // for convenience
    }

    public void MouseDown_Subscriber(bool subscribe) {
      if (subscribe)
      {
        Rhino.RhinoApp.WriteLine("Registering GC_MouseDown event");
        Canvas.MouseDown += GC_MouseDown;
      } else {
        Rhino.RhinoApp.WriteLine("Unregistering GC_MouseDown event (poor mouse is now very dead... )");
        Canvas.MouseDown -= GC_MouseDown;
      }
    }

    // Some dummy action on MouseDown
    public void GC_MouseDown(object sender, MouseEventArgs e) {
      Rhino.RhinoApp.WriteLine("GCEvents.GH_MouseDown: Alive & Kicking!");
    }

… and here the IDisposable implementation

    // ==================================
    // IMPLEMENTING INTERFACE IDISPOSABLE
    // ==================================
    bool disposed = false;
    System.Runtime.InteropServices.SafeHandle handle = new SafeFileHandle(IntPtr.Zero, true);

    public void Dispose()
    {
      Dispose(true);
      GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
      if (disposed)  return;

      if (disposing) { // only managed objects
        handle.Dispose();        
      }

      // Unmanaged stuff, for example hooked events
      Rhino.RhinoApp.WriteLine(string.Format("(Dispose({0}))", disposing));
      MouseDown_Subscriber(false);   // = "unsubscribe"

      disposed = true;
    }
  } // end class  
} // </Custom additional code> 

Me and the .NET GC have not become freinds as of yet, so the GC doesn’t really dispose of the “GuardedMouseDownEvent” instance, but you guys may know better how to force .NET do dump the object without resorting to that ugly call to Dispose();

In any case, @DavidRutten should consider adding more hooks to GH so we can have more fun. Cough, cough.

// Rolf


#15

If any of you .NET experts can make the “Guarded Event” class to actually get disposed of, here’s the component:

GH_MouseTest.gh (4.5 KB)

// Rolf


#16

This looks more complicated than it should be. Or I don’t understand what you’re trying to do.
.Net is in charge of releasing the memory that is no longer needed.
If you want to unsubscribe the event with m_gcevents = null, (which seems like a whim to me) you can declare a deconstructor (and you will need a constructor too) and unsubscribe the event there:

public GuardedMouseDownEvent(){}
~GuardedMouseDownEvent(){ //this will be called before complete m_gcevents = null;
  MouseDown_Subscriber(false);
}

As you have structured the code, you do not allow invoking the mouse event, because before the code is executed (and then the user can launch an event) you are already unsubscribing the event.


#17

The idea is to have a mechanism that unsubscribes when a variable goes out of scope, meaning, “Create and forget”.

This approach is as perfect a solution as reference counting is perfect (well, that has been providing a poor man’s “pseudo Garbage Collection” for decades in non-GC languages). I used this technique in Delphi Pascal a lot.

One would of course make a generic “Guard” class which takes any System.Object (or delegate) as an argument in the constructor so as to not have to design such a class for each use case.

// Rolf


#18

I’m sorry, I still don’t understand. If you need to specify to release the memory where a variable is pointed, using m_gcevents = null or dispose, just to unsubscribe the event, why not replace m_gcevents = null with a call to the unsubscribe method, directly? .Net will be in charge of freeing up the resources when it deems it appropriate. By replacing a line you avoid superfluous (at least in my opinion) code.


#19

This wasn’t meant to be working, it was meant to illustrate the Guard concept.

Quoting myself:

Edit:

Problem is of course that Script_Instance is the wrong class to deal with the lifetime of the event. It should follow the liftime of the GH_Component (hence the poking on @DavidRutten).

// Rolf


#20

actually the problem could be solved quite easily if there would be a possibility to define a method outside the local scope, being a true global method. You can do that in CPython. I also do alot with VRED Professional and in there I script eventbased. The difference to Grasshopper is that my functionality can be declared global. So whenever my script changes, the method is overridden. However this still can produce weird behaviour, because I have to make sure that every object gets destroyed after rerunning the solution. This could work with ease if I wouldn‘t access unmanaged c++ objects in there. So I encountered really hard to debug behaviour. I did a lot of manual disposing, but as long there is one referenced living object, you cannot kill an instance. You even need to kill in the right order.So what happends is, that there are multiple instances, with different implentations running at the same time. You can see this by printing all active objects out. So I think its not that you cannot work with events in a script context, its just that you really need be aware of whats going on and being very careful. This requires knowledge of how the gc works, and I bet the casual scripter here doesn‘t know anything about it except that it manages memory for you.So thats why I keep saying:Don‘t do this dave, unless you really know whats going on. For .net I don‘t know a good workaround, except to ensure unsubscribing before loosing reference to the subscriping method.