Python eto custom event

Does anyone know of a way to make custom events with python and eto? Is there an eto-specific way to do this? I’ve not been able to find any examples or ‘hints’. I’ve found an ironpython book that seems to discuss this, but it is from 2010 dealing with windows forms, and I’m not sure it is applicable to eto.

Thanks,
Nathan

Hi @nathancoatney,

What do you mean by custom event? What are you trying to do and why?

Thanks,

– Dale

Hello Dale,

For example:

I would like to make a reusable panel containing a text box in which the user can specify comma separated rotations such as 0, 45, 90, 270-360 (like print options where you can specify specific pages or range of pages). Making the text box and parsing the text on .TextChanged or .TextChanging events is straight forward, the handler splits and cleans up the input the way I want it. What I would like to do is make an event that emits from the reusable panel when the handler is successful and has valid output, not on every change that is made to the text.

So I would have a main form, which contains instances of my reusable panel (one for x, y and z axis). Each instance would have an event called something like rotation_input_valid which I could handle in the main form to use elsewhere.

I guess I’m trying to make self contained panels of controls that gather user input, process it, and produce an output, without having to expose all the inner workings of the panel.

Is that a clear description? It seems common to make custom events in c#, but I haven’t been able to translate that to ironpython (and maybe eto is another layer of confusion).

Hi @nathancoatney,

You might take a look at this code:

https://github.com/jdhardy/ironpython/blob/master/Languages/IronPython/Public/Tutorial/pyevent.py

Here is an example of its usage.

http://opensimulator.org/git/opensim-libs-save/IronPython/IronPython-1.1.1/Tutorial/Tutorial.htm#T2.1.3

– Dale

Thanks for the hints. I had seen pyevent previously but didn’t understand/grasp any examples. After some experiments It seems to be working. I copied the pyevent.py to the scripts folder for import. Here is what is working here, in case anyone else is interested:

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

import pyevent  # copied to rhino scripts folder for import

class EventArgs:
    """A container object for the event arguments."""
    def __init__(self):
        self.default_arg = 'default arg'


class ButtonPanel(forms.Panel):
    """Self contained form with button and custom event."""
    
    # Create custom events out here.
    custom_button_event, _custom_button_event_emmiter = pyevent.make_event()
    
    def __init__(self):
        # layout
        self.layout = forms.DynamicLayout()
        self.layout.DefaultSpacing = drawing.Size(5, 5)
        self.layout.Padding = drawing.Padding(10)
        
        # button
        self.event_button = forms.Button()
        self.event_button.Text = 'Test Event'
        # Hook the button click event to our logic.
        self.event_button.Click += self.button_clean_up_function
        
        self.layout.Add(self.event_button)
        
        self.Content = self.layout
        
    def button_clean_up_function(self, sender, e):
        # This is where we intercept the button click.
        
        # Make our own event argument object.
        event_args = EventArgs()
        # Can just add things like this:
        event_args.complicated_data = 'complicated'
        
        # Now we can call our custom event emitter.
        # I think the emitter will pass along anything we pass to it.
        # This example we pass self and our event_args, but I think it can be anything.
        # It seems we can use self. or ButtonPanel. - not sure what implications are.
        # self. makes more sense to me, so I'll go with that one.
        self._custom_button_event_emmiter(self, event_args)


class InfoPanel(forms.Panel):
    """Self contained info panel"""
    
    def __init__(self):
        # layout
        self.layout = forms.DynamicLayout()
        self.layout.DefaultSpacing = drawing.Size(5, 5)
        self.layout.Padding = drawing.Padding(10)
        
        # label
        self.info_label = forms.Label()
        self.info_label.Text = 'No event yet'
        
        self.layout.Add(self.info_label)
        
        self.Content = self.layout
        
    # This will be attached to our custom event, so args need to match
    def update_label(self, sender, e):
        # make a string to display in the label
        # shows the sender object, the EventArgs object, and our data from the EventArgs object
        info = 'sender: {}\n' \
               'e: {}\n' \
               'e.complicated_data: {}'.format(sender, e, e.complicated_data)
        self.info_label.Text = info


class CustomEventTestDialog(forms.Dialog):
    def __init__(self):
        self.Title = 'Custom Event Test'
        self.Padding = drawing.Padding(10)
        self.Resizable = True
        self.MinimumSize = drawing.Size(600, 200)
        self.create()
        
    def create(self):
        # make the button panel instance
        self.button_panel = ButtonPanel()
        
        # make the info panel instance
        self.info_panel = InfoPanel()
        
        # hook up the custom event
        self.button_panel.custom_button_event += self.info_panel.update_label
        
        # layout
        self.layout = forms.DynamicLayout()
        self.layout.Add(self.button_panel)
        self.layout.Add(self.info_panel)
        self.layout.Add(None)
        
        self.Content = self.layout
        
        
def custom_event_test_dialog():
    dialog = CustomEventTestDialog()
    try:
        dialog.ShowModal(Rhino.UI.RhinoEtoApp.MainWindow)
    except Exception as exp:
        print('Could not load dialog:')
        print(exp)
        
        
if __name__ == '__main__':
    custom_event_test_dialog()
1 Like

Found a gotcha:

Code above makes the events class methods (I think). If you make multiple instances then they all share the event.

To make the events isolated to each instance put the in the init method and a self. on the front of them, like normal. So:

class MyClass:
    def __init__(self):
        self.custom_button_event, self._custom_button_event_emitter = pyevent.make_event()
        ...

So far no problems with this.

1 Like

@nathancoatney

Could you tell me how and what you exactly copied to the scripts folder to be able to use pyevent ?
When I view the zip it contains several folders and files.
(I installed with pip, but Rhino Python tells me pyevent is not installed …)
When I copy pyevent.py to the scripts folder, then running your script shows an error:
Message: ‘module’ object has no attribute ‘make_event’

Kind regards, Dick

I just copied the pyevent.py file to Rhino’s scripts folder, so it is beside your script. I suppose it could be placed anywhere in Rhino’s search paths, but I just treated it as another script. If you go to the python editor’s options:

You can just copy and paste the code from the github link Dale posted into the editor and then save it as pyevent.py.

If you are doing a plugin structure then it may be a better idea to put it in the plugin’s folder, but I haven’t tested it.

I’ve never tried to use pip with RhinoPython; not sure it is possible. If you just command line pip install… then it probably got installed to a system install of IronPython or CPython. It does appear there is a pipy module called pyevent but it doesn’t have anything to do with the .net / ironpython pyevent, so CPython’s pip could have thought it was doing what you wanted.

Hope that helps.

Hi Nathan, thanks for your quick reply!
It helped me indeed: I did use the wrong pyevent … :grimacing:
Thanks again for clearing this up!