ETO Modeless Form Close Button

Hi,

How would one add a close button to the modeless userform in the link below? This example closes with the X on top. I would like a button to close the form and do the same cleanup that OnFormClosed does when you hit the X.

Running Rhino 6 using Python.

unless i missed something in your question, this should be fairly short

import Eto.Forms as forms
class Sample(forms.Form):
    # assuming you already have a button control added
    button.Click += self.OnButtonClick

    def OnButtonClick(self, s, e):
        self.Close()

My problem was adding this to a user form, based on the modeless example, would crash Rhino immediately. I have since found showsemimodal which seems to work but I am cautiously experimenting with it to make sure it won’t cause other issues. I want to be able to instigate a Rhino command from the UF and allow the operator to select something on screen.

A little more detail:

I’ve pasted in the modified modeless form code below with a close button on it that has the exact same code in it as the function called “OnFormClosed”. If you close the form with the X the form closes normally with no problems. If you close the form with the Close button Rhino crashes. I’ve added self.close() as well and it crashes. Can someone tell me how to correct this so I can use the close button on the form to close the userform.

Imports

import Rhino
import scriptcontext as sc
import System
import Rhino.UI
import Eto.Drawing as drawing
import Eto.Forms as forms

SampleEtoRoomNumber dialog class

class SampleEtoRoomNumberDialog(forms.Form):

# Dialog box Class initializer
def __init__(self):
    # Initialize dialog box
    self.Title = 'Identify Assembly Components'
    self.Padding = drawing.Padding(10)
    self.Resizable = False
     #FormClosed event handler
    self.Closed += self.OnFormClosed

    # Create the default button
    self.DefaultButton = forms.Button(Text = 'Create Layout')
    self.DefaultButton.Click += self.OnOKButtonClick

    # Create the abort button
    self.AbortButton = forms.Button(Text = 'Cancel')
    self.AbortButton.Click += self.OnCloseButtonClick

    # Create a table layout and add all the controls
    layout = forms.DynamicLayout()
    
    #Sets spacing between controls(pixels)
    layout.Spacing = drawing.Size(5, 5)

    #Buttons
    layout.AddRow(self.DefaultButton, self.AbortButton)

    # Set the dialog content
    self.Content = layout
    print('')

# Form Closed event handler
def OnFormClosed(self, sender, e):
    print("the form has closed")
    # Dispose of the form and remove it from the sticky dictionary
    if sc.sticky.has_key('sample_modeless_form'):
        form = sc.sticky['sample_modeless_form']
        if form:
            form.Dispose()
            form = None
        sc.sticky.Remove('sample_modeless_form')

# Close button click handler
def OnCloseButtonClick(self, sender, e):
    print("the form has closed")
    # Dispose of the form and remove it from the sticky dictionary
    if sc.sticky.has_key('sample_modeless_form'):
        form = sc.sticky['sample_modeless_form']
        if form:
            form.Dispose()
            form = None
        sc.sticky.Remove('sample_modeless_form')

# OK button click handler
def OnOKButtonClick(self, sender, e):
    print("This button is OK")

## End of Dialog Class ##

def RequestRoomNumber():

# See if the form is already visible
if sc.sticky.has_key('sample_modeless_form'):
    return

# Create and show form
form = SampleEtoRoomNumberDialog()
form.Owner = Rhino.UI.RhinoEtoApp.MainWindow
form.Show()

# Add the form to the sticky dictionary so it
# survives when the main function ends.
sc.sticky['sample_modeless_form'] = form

if name == ‘main’:
RequestRoomNumber()

@Trav sorry if I’m calling on the wrong person but you’ve helped with a few of my Eto questions so i thought you might know
I’m experiencing similar crashes. Here is a shorter example that reproduced the problem on my machine. I ran this in the rhino python editor.

import Eto.Forms as forms
import Eto.Drawing as drg
import Rhino.Input as rhi
import Rhino.Commands as rhcmd
import Rhino as rh


class MyForm(forms.Form):
    def __init__(self):
        self.Size = drg.Size(500,200)
        self.panel = forms.StackLayout(Orientation = forms.Orientation.Vertical)
        self.bttn = forms.Button(Text='pick')
        self.bttn.Click += self.OnClick
        self.label = forms.Label()
        
        self.panel.Items.Add(self.bttn)
        self.panel.Items.Add(self.label)
        self.Content = self.panel
    
    def OnClick(self, s, e):
        self.Hide()
        r, oref = rhi.RhinoGet.GetOneObject(
            'pick something',
            True,
            rh.DocObjects.ObjectType.AnyObject)
        if r == rhcmd.Result.Success:
            self.label.Text = oref.ObjectId
        self.Show()
        
myform = MyForm()
myform.Owner = rh.UI.RhinoEtoApp.MainWindow
myform.Show()

Will,

I got a tip from Dale that allowed me to select items on screen using a modal form. Didn’t solve the crashing problem with modeless form but for my purposes it will work for me.

Also:

Eric

@Will_Wang Eric is on the right track here with the PushPickButton. You’ll need that to hide the modal ETO dialog during object picking.

is it by design that a modeless eto form can’t launch the RhinoGet.GetOneObject?

Hiding the dialog doesn’t mean its no longer modal. You would need to close the dialog to achieve that. PushPickButton is designed to perform this duty for you.

I thought the hiding of the userform was a nice bonus. The modeless version was always in your face and I found myself having to move it around when I was wanting to select something on screen. It was a good find for sure.

Eric

Found my problem. Cross fire between winforms and eto forms in my brain. The crash was because Eto.Forms.Form doesn’t have a Hide() method, as I have written in my snippet.

So OP, you can use a modeless Eto.Forms.Form and programmatically “hide” it when you need. Something like this should work

import Eto.Forms as forms
import Eto.Drawing as drg
import Rhino.Input as rhi
import Rhino.Commands as rhcmd
import Rhino as rh

class MyForm(forms.Form):
    def __init__(self):
        self.Size = drg.Size(500,200)
        self.panel = forms.StackLayout(Orientation = forms.Orientation.Vertical)
        self.bttn = forms.Button(Text='pick')
        self.bttn.Click += self.OnSelectClick
        self.label = forms.Label()
        
        self.panel.Items.Add(self.bttn)
        self.panel.Items.Add(self.label)
        self.Content = self.panel
    
    def OnSelectClick(self, s, e):
        self.Minimize()
        try: 
            r, oref = rhi.RhinoGet.GetOneObject(
                'pick something',
                True,
                rh.DocObjects.ObjectType.AnyObject)
            if r == rhcmd.Result.Success:
                self.label.Text = oref.ObjectId
        except Exception as e: self.label.Text = str(e)
        self.BringToFront()
        
myform = MyForm()
myform.Owner = rh.UI.RhinoEtoApp.MainWindow
myform.Show()
1 Like

PushPickButton makes all of this insanity go away. You should really use it. It’s what we use for all of Rhino’s ETO dialogs.

Thanks Travis! When I do use Dialog it will come in handy. I was trying to get at Form so rhino UI thread doesn’t lock up behind it other than a pick call.

Will,

I tried out your code and it works well. Works more like a Windows Form with the Show and HIde. (I come from a Solidworks/VBA background) If I ever need full control of Rhino while the form is running I could use this.

One problem though. I picked up on this with other modeless forms I tried out.:

I can’t pass in variables to the Class. My only work around for this was declaring global variables in the Class and assign a class variable to the global variable. Then I could load them from outside the Class. Kind of a pain. See code below that crashes:

import Eto.Forms as forms
import Eto.Drawing as drg
import Rhino.Input as rhi
import Rhino.Commands as rhcmd
import Rhino as rh

class MyForm(forms.Form):
    def __init__(self, testVar):
        
        self._testVar = testVar
        
        self.Size = drg.Size(500,200)
        self.panel = forms.StackLayout(Orientation = forms.Orientation.Vertical)
        self.bttn = forms.Button(Text='pick')
        self.bttn.Click += self.OnSelectClick
        self.label = forms.Label()
        
        self.panel.Items.Add(self.bttn)
        self.panel.Items.Add(self.label)
        self.Content = self.panel
    
    def OnSelectClick(self, s, e):
        self.Minimize()
        try: 
            r, oref = rhi.RhinoGet.GetOneObject(
                'pick something',
                True,
                rh.DocObjects.ObjectType.AnyObject)
            if r == rhcmd.Result.Success:
                self.label.Text = oref.ObjectId
        except Exception as e: self.label.Text = str(e)
        self.BringToFront()

testVar = "Some Value"
myform = MyForm(testVar)
myform.Owner = rh.UI.RhinoEtoApp.MainWindow
myform.Show()

Here’s the error:

Message: expected IHandler, got str

Traceback:
line 35, in , “C:\Users\bunner\OneDrive - Sealed Air Corporation\Computer\Desktop\Rhino Scripts\Python Routines\Example Code\Userforms\ETO\Rhino ETO Example Modeless with HideShow.py”

Thanks,

Eric

this is a bit beyond me but it seems you can’t override the constructor
when you instantiate, the constructor with one argument is still called from super
I’d assign class variables with a class method

def AssignVar(self, arg0, arg1....):
    self.testVar0 = arg0
    self.textVar1 = arg1
...

That kind of makes sense. Thanks for the reply. I’ll try it.

Eric

Will,

That class function will work for me. Thanks for the tip.

Eric

import Eto.Forms as forms
import Eto.Drawing as drg
import Rhino.Input as rhi
import Rhino.Commands as rhcmd
import Rhino as rh

class MyForm(forms.Form):
    def __init__(self):
        self.Size = drg.Size(500,200)
        self.panel = forms.StackLayout(Orientation = forms.Orientation.Vertical)
        self.bttn = forms.Button(Text='pick')
        self.bttn.Click += self.OnSelectClick
        self.label = forms.Label()
        
        self.panel.Items.Add(self.bttn)
        self.panel.Items.Add(self.label)
        self.Content = self.panel
    
    def OnSelectClick(self, s, e):
        self.Minimize()
        try: 
            r, oref = rhi.RhinoGet.GetOneObject(
                'pick something',
                True,
                rh.DocObjects.ObjectType.AnyObject)
            if r == rhcmd.Result.Success:
                self.label.Text = oref.ObjectId
        except Exception as e: self.label.Text = str(e)
        self.BringToFront()
        
    def AssignGlobalVar(self, testVar):
        self.testVar = testVar
        print(testVar)

myform = MyForm()
myform.Owner = rh.UI.RhinoEtoApp.MainWindow
myform.Show()
myform.AssignGlobalVar("This is a test")