GUI programming is sometimes described as being “event-driven”. Typically, a wxRuby application will set up an initial state within the App’s on_init method, showing a starting Frame or Dialog. After this, the application’s behaviour will be determined by its responses to the user’s interaction with the GUI elements.
These interactions with an application are modelled as Events. Events include pressing a key, clicking a button, typing text in a text control, moving the mouse, selecting a drop-down menu item, resizing a window and so on. In wxRuby, all these user actions are dealt with using event handlers
Event handlers are pieces of code that are run when an event occurs. There are two parts to setting up an event handler:
The basic way to set up an event handler in WxRuby combines these two
steps in a single line of code. This is to call a method named
evt_xxx, where xxx is the type of event that should be handled, and
pass it a block that should be run when the event occurs. For example:
evt_size { puts "I was resized" }
Some types of event handlers, those that deal with events arising from a user operating UI controls like buttons, checkboxes and choices, require that the relevant control be identified in the handler:
my_button = Wx::Button.new(self, :label => 'Press me')
evt_button(my_button) { puts "Button was pressed" }
This is discussed in more detail below
The evt_xxx methods are instance methods of the wxRuby class EvtHandler. All GUI classes that are displayed on the screen (that is, subclasses of Window) inherit from this class, as does App. Therefore, event handling can be done by Frames, Dialogs or within individual controls.
As there are very many different types of evt_xxx method, they are described alongside the type of control or event that they belong to. Therefore, the events generated by a TextCtrl are given in the documentation for that class. Similarly, available event handlers relating to the activation and disactivation of a Window are listed in the documentation for ActivateEvent.
Whenever an event occurs, an Event object is created containing additional information about the event. For example, for a mouse event, this might be where on the screen it took place; for a key event, what key was pressed; for a activation event event, whether the Frame became active or inactive.
To access this additional information within an event handler, simply set up the block to accept a single parameter, the Event object.
evt_size { | event | puts "Width is now #{event.size.width}" }
The event object will be of the appropriate class for the type of event being handled – for example, a SizeEvent for a evt_size handler, or a TreeEvent from a TreeCtrl. All the event classes are listed on the home page.
Note that Events are short-lived objects and are destroyed by wxWidgets once all the relevant handlers have been called. Therefore you shouldn’t attempt to store an event object passed into a handler in a variable referenced outside the handler block.
As an application develops, more event handlers are used, and each contains more code. It then becomes easier to organise this code into methods, for example:
def initialize
..
evt_size { | event | on_size(event) }
evt_button(my_button) { | event | on_button_pressed(event) }
end
def on_size(event)
...
def on_button_pressed(event)
...
For convenience, wxRuby permits the block here to be replaced simply with the name of the method that should handle the event.
evt_size :on_size
evt_button my_button, :on_button_pressed
Obviously, this can give considerably clearer and shorter code. The method may still choose whether or not to receive the Event object.
As mentioned above, some event handlers, for example, evt_button
require that the control which generates the event be identified. This
is one aspect of a distinction between two broad groups of events in
wxRuby. The first is CommandEvents, which are
generated by user interaction with controls. The second includes all
other events.
CommandEvents are generated by user interface controls such as buttons, text boxes, radio buttons, lists, menus and so on. These controls enable particular types of interaction, such as typing in a text box, selecting a radio item, or choosing an item from a drop-down list or menu. Such actions can be thought of as “commands”, hence the name. It’s often desirable to manage the event handlers for a related group of such controls within a container window, such as a Frame or Panel.
To facilitate this, CommandEvents ‘bubble’ up to parent windows, so parent windows can set up handlers to listen to events generated by child controls. This also means that you have to explicitly tell WxRuby the source of events you want to handle events from. In C++ this is done by referring to the integer id of the control whose events are being listened to, and this is permitted in wxRuby too:
MY_BUTTON_ID = 1001
my_button = Wx::Button.new(panel, MY_BUTTON_ID, 'Press me')
evt_button(MY_BUTTON_ID) { 'my_button was pressed' }
However, this use of explicit constants is rather cumbersome and unnecessary in a dynamic language like Ruby. Therefore, it is typically easier to allow wxRuby to assign ids to new windows, and then simply pass the control itself as the parameter to the event handler:
my_button = Wx::Button.new(panel, :label => 'Press me')
evt_button(my_button) { 'my_button was pressed' }
If there are many similar controls within a Frame or Dialog, it can be
easier to set up a global event handler, then work out what specific
action should be taken within the event handling code itself, perhaps
using the event_object method in Event
to identify the control. To set up a catch-all event handler for
CommandEvent types, use the special Wx::ID_ANY identifier:
evt_button(Wx::ID_ANY) { 'some button was pressed' }
Other events in WxRuby include sizing, activating, closing and moving
windows, changing layouts with splitters and sashes, and moving through
multi-pane organisers like Wizards and
Notebooks. Importantly, this also includes generic
mouse movement and keyboard presses
that are not associated with any particular command action. All these
kind of events are only visible to the window itself. They are not
visible to parent windows. The practical effect of this is that the
evt_xxx method must be set up the window that generates the event, and
no id parameter is needed.
evt_size { "I was resized" }
The fact that non-CommandEvents are only sent to the window that generated them is not, in practice, a serious restriction, given wxRuby’s dynamic nature. It is entirely permitted to call the evt_xxx method on the widget whose events should be listened for, but keep the handling code in another instance
class MyFrame < Wx::Frame
def initialize
...
my_panel = Wx::Panel.new(self)
my_panel.evt_size { on_panel_sized }
end
def on_panel_sized
end
Just note that in this case it is not possible to use the shortcut method-name notation shown above; the following will not work
my_panel.evt_size :on_panel_sized
As well events that are triggered by UI actions, wxRuby is also able to use event handling to carry out timed or regular actions. The Timer class can be used to trigger one-off or repeating TimerEvents events which can be handled as with other events. Note that use of this approach is preferred to ruby’s own ‘timeout’ library as a way to handle timed events in wxRuby. This is because Ruby’s interpreter threads do not co-operate well with C and C++ based toolkits like WxWidgets.
There is also an evt_idle handler which specifies actions to be done
when the application is otherwise doing nothing. See
IdleEvent for more information.
Sometimes a given event should not be permitted. For example, a user has tried to close a dialog, but there is an invalid value in a text box, so the application should prevent the dialog being closed until the value is corrected. The action can be blocked by calling the veto method:
if not ready
event.veto
end
Sometimes you want the opposite – to ensure that the event’s original aim is completed, or passed on upwards to a parent window for handling. In this case, the skip method should be called.
event.skip
This is important, for example, in evt_close handlers. If skip is
not called, the Frame or Dialog will not be closed at all.
Although it is not a frequent need, it is possible to disable any event handler dynamically within program code. This is achieved by calling the disconnect method of EvtHandler. An example of this can be found in the “events” sample included with wxRuby.
wxRuby permits completely custom event types and handlers to be defined. This is useful when writing custom control types. The central method for doing this is the EvtHandler class method register_class. The documentation there describes how to integrate user-defined Event classes with the wxRuby event system; example code can be found in the “events” sample.
[This page automatically generated from the Textile source at Thu Aug 28 20:29:08 +0100 2008]