Rhino Python WinForms: Right-click outside form not detected

Hi,

Need some help with a dialog window in Rhino Python (System.Windows.Forms).

The idea is simple: the window should close on Enter and Escape (this part works), and also on right mouse button (RMB) click anywhere on the screen.
The problem is that RMB just doesn’t trigger outside the form.

Current code:

import System.Windows.Forms as Forms

form = Forms.Form()
form.KeyPreview = True

def on_key_down(sender, e):
if e.KeyCode == Forms.Keys.Escape or e.KeyCode == Forms.Keys.Return:
form.Close()
e.Handled = True

form.KeyDown += on_key_down
form.Show()

Tried a few things already:
MouseDown on the form → only works inside the window
ShowDialog() instead of Show() → no difference
attaching handlers to controls → nothing
switching to Eto.Forms → same limitation

So clicks outside the form are not detected at all. What’s the correct way to handle RMB globally in this case? Is this something that requires Rhino.UI.MouseCallback, overriding WndProc, or a global mouse hook (SetWindowsHookEx)? Or is this simply not supported with WinForms in Rhino Python?

Running your code, when the form doesn’t have focus, pressing Enter or Esc also does not trigger their event.

How you detect the RMB depends on how global the detection needs to be. For example, Rhino.UI.MouseCallback only works when the pointer is over a Rhino viewport.

Hi Steven,

I’m trying to close a WinForms dialog in Rhino Python using Enter/Escape (this works) and the right mouse button (RMB). The RMB part currently works only when clicking inside the form. What I need is for it to also work when clicking on the Rhino viewport (not just inside the dialog).

Here is a minimal example:
v1

import System.Windows.Forms as Forms
from System.Drawing import Font, Point

form = Forms.Form()
form.Text = "Test Dialog"
form.Width = 400
form.Height = 200
form.StartPosition = Forms.FormStartPosition.CenterScreen
form.KeyPreview = True
form.ControlBox = True

label = Forms.Label()
label.Text = "Press ENTER, ESCAPE or RIGHT CLICK to close"
label.Font = Font("Arial", 11)
label.AutoSize = True
label.Location = Point(50, 50)
form.Controls.Add(label)

btn = Forms.Button()
btn.Text = "OK"
btn.Location = Point(150, 120)
btn.Click += lambda s, e: form.Close()
form.Controls.Add(btn)

def on_key_down(sender, e):
    if e.KeyCode == Forms.Keys.Escape or e.KeyCode == Forms.Keys.Return:
        form.Close()
        e.Handled = True

def on_mouse_down(sender, e):
    if e.Button == Forms.MouseButtons.Right:
        form.Close()

form.KeyDown += on_key_down
form.MouseDown += on_mouse_down
form.ShowDialog()

v2

import System.Windows.Forms as Forms
from System.Drawing import Font, Point
import Rhino

form = Forms.Form()
form.Text = "Test Dialog"
form.Width = 400
form.Height = 200
form.StartPosition = Forms.FormStartPosition.CenterScreen
form.KeyPreview = True
form.ControlBox = True

label = Forms.Label()
label.Text = "Press ENTER, ESCAPE or RIGHT CLICK to close"
label.Font = Font("Arial", 11)
label.AutoSize = True
label.Location = Point(50, 50)
form.Controls.Add(label)

btn = Forms.Button()
btn.Text = "OK"
btn.Location = Point(150, 120)
btn.Click += lambda s, e: form.Close()
form.Controls.Add(btn)

def on_key_down(sender, e):
    if e.KeyCode == Forms.Keys.Escape or e.KeyCode == Forms.Keys.Return:
        form.Close()
        e.Handled = True

form.KeyDown += on_key_down


class MyMouseCallback(Rhino.UI.MouseCallback):
    def OnMouseDown(self, e):
        if e.Button == Rhino.UI.MouseButtons.Right:
            form.Close()
            self.Enabled = False


mouse_cb = MyMouseCallback()
mouse_cb.Enabled = True

form.ShowDialog()

mouse_cb.Enabled = False

How can RMB be detected when clicking on the Rhino viewport while the dialog is open? Is Rhino.UI.MouseCallback the right approach for this, or is there another recommended way?

Thanks for any suggestions.

First post:

Last post:

For the latter,

here is Gemini 3.1 Pro's solution as a revision to your v2 script with comments on the changes.
import System.Windows.Forms as Forms
from System.Drawing import Font, Point
import Rhino

form = Forms.Form()
form.Text = "Test Dialog"
form.Width = 400
form.Height = 200
form.StartPosition = Forms.FormStartPosition.CenterScreen
form.KeyPreview = True
form.ControlBox = True
form.TopMost = True # Added so the non-modal form doesn't disappear behind Rhino when clicking

label = Forms.Label()
label.Text = "Press ENTER, ESCAPE or RIGHT CLICK to close"
label.Font = Font("Arial", 11)
label.AutoSize = True
label.Location = Point(50, 50)
form.Controls.Add(label)

btn = Forms.Button()
btn.Text = "OK"
btn.Location = Point(150, 120)
btn.Click += lambda s, e: form.Close()
form.Controls.Add(btn)

def on_key_down(sender, e):
    if e.KeyCode == Forms.Keys.Escape or e.KeyCode == Forms.Keys.Return:
        form.Close()
        e.Handled = True

# Added to handle RMB clicks directly over the form background
def on_form_mouse_down(sender, e):
    if e.Button == Forms.MouseButtons.Right:
        form.Close()

form.KeyDown += on_key_down
form.MouseDown += on_form_mouse_down # Re-added from v1 to detect clicks on the form itself

# Added loop to ensure child controls (Label, Button) also pass the RMB click to close the form
for ctrl in form.Controls:
    ctrl.MouseDown += on_form_mouse_down

class MyMouseCallback(Rhino.UI.MouseCallback):
    def OnMouseDown(self, e):
        # Changed from Rhino.UI.MouseButtons.Right (which doesn't exist and throws an error) to Forms.MouseButtons.Right
        if e.Button == Forms.MouseButtons.Right:
            e.Cancel = True # Added to swallow the click so it doesn't trigger background actions in the Rhino viewport
            if not form.IsDisposed: # Added to prevent crash if callback fires while the form is already in the process of closing
                form.Close()
            # Removed 'self.Enabled = False' from here to let the FormClosed event handle all cleanup reliably

mouse_cb = MyMouseCallback()
mouse_cb.Enabled = True

# Added event to safely disable the callback only when the form actually closes; required because Show() is non-blocking
def on_form_closed(sender, e):
    mouse_cb.Enabled = False

form.FormClosed += on_form_closed

form.Show() # Changed from ShowDialog() to Show() to allow underlying viewport interaction

# Removed 'mouse_cb.Enabled = False' from the end because Show() doesn't block the script, which would instantly kill the callback

Hi Steven,

Thanks for the idea of separating click handling between the form and Rhino.UI.MouseCallback.
Really helped simplify the logic.

Here is my current compact version:

from System.Windows.Forms import Form,Label
from System.Drawing import Point,Size
import Rhino,scriptcontext as sc

f=Form(Text=‘v4’); f.Size=Size(420,140); f.TopMost=True
f.Controls.Add(Label(Text=‘Click L/R to close’,AutoSize=True,Location=Point(8,8)))
f.MouseDown += lambda s,e: f.Close() if str(e.Button).lower().startswith((‘left’,‘right’)) else None

class CB(Rhino.UI.MouseCallback):
def init(self,d): self.d=d
def OnMouseDown(self,e):
if str(e.Button).lower().startswith((‘left’,‘right’)):
try: self.Enabled=False
except: pass
if self.d and not self.d.IsDisposed:
try: self.d.Close()
except: pass
return False

cb=CB(f); cb.Enabled=True
sc.sticky[‘_d4’]={‘f’:f,‘cb’:cb}
f.Show()