Hello, World! Simple Widgets Binding Events More Widgets Scrollbars Menus Canvas Make things look Beautiful More Information

We are moving to this wiki

Ruby/Tk: Events

Let's get some Action!
root = TkRoot.new() { title "Click the Button" }
button = TkButton.new(root) {
  text "Hello..."
  command proc {
    p "...world!"
  }
}
button.pack()
Which does exactly what you expect. As command expects a block, the word proc is not necessary. Note that a proc like here is a closure, so will take its environment with it, knowing all (local) variables that were visible for it, when it was created.

Binding Events

Let's couple a key to the button (its action, really):
button.bind("Any-Key-h") {
  p "...world! (key)"
}
Guess what? No go. Since the button does not have focus it does not receive any events (OK, it does receive "Enter" and "Leave"). Press TAB once and try again. That works. As alternative, button.focus() tries to get focus, as well.

However, we do not wish (as user) to TAB our way to the button that we need before hitting the key on the keyboard. One way to achieve that is with:

root.bind("Any-Key-h") {
  p "...world! (root)"
}
because root receives all events its subwidgets receive! Very convenient!

Description of Events

Now let's look at the event-string passed to bind. It is composed of several parts, being one or two modifiers, a type and a detail.

Modifiers

The list of modifiers is:

Any ShiftControlAlt Meta, M
Mod1, M1 Mod2, M2Mod3, M3 Mod4, M4 Mod5, M5
Button1, B1 Button2, B2 Button3, B3 Button4, B4 Button5, B5 Double Triple

of which two can be combined. Thus, "Any-B1" and "Control-Alt" are possible, "Shift-Double-Button3" and "Control-Shift-Alt" are not.

"Any" stands for shift/control/alt/mets/modX may be pressed, but need not. Without "Any", control and co may or may not be pressed, too. "ModX" are modifier keys. "ButtonX" are mouse buttons.

Types

The Types encompass:

Activate Enter KeyPress, Key ButtonPress, Button FocusIn Map
Deactivate Leave KeyRelease ButtonRelease FocusOut Unmap
Circulate Colormap Configure Destroy Expose
Gravity Motion Property Reparent Visibility

Of which you may guess as many as we do...

Details

For buttons, 1-5 (so "Shift-Double-Button-3" is possible)
For keys, a keysym in /usr/include/X11/keysymdef.h, e.g. "h", "KP_Enter", "Shift_R", "rightarrow".

Hint to the User

Of course, it is nice to tell the user that a keystroke will actually work. Most people think that underlining the character which is used in combination with Control or Alt is a good way to do that. So we stick with that:
button.configure('underline'=>0)
underlines the single character in the text as indexed.

Hello revisited

What happened in our first Ruby/Tk program when we clicked on the destroy-window button of the window-manager? It passed the even Destroy to the window, which has a prescribed action connected to it, namely to grant the destroy-request (Tk destroys the window, not the window-manager).

Evading default Tk event-bindings

Use Tk.callback_break and Tk.callback_continue. More explanation will follow when I found out how the latter works.

Peculiarities

For some odd reason, the binding "Any-Key-h" will not accept Shift-h; you need to specify a second binding "Any-Key-H" for that (note the difference between lower- and uppercase 'h'). This restriction only holds for alphanumeric, not for other keysyms. Test before you trust.

"Shift-Key-Tab" works fine under windows, but you should use "Key-ISO_Left_Tab" under unices (Thanks to Albert Wagner).

When two bindings from the same widget claim an event, only one of them receives it. If the events are effectively the same (like "Any-Key" and "Key"), the last one registered gets the event. If they differ (like "Alt-Key" and "Key"), the most specific one gets the event ("Alt-Key" in this case).

Event information

For some events, you need more information, like (x, y) for mouse-clicks. Let's get it:
root.bind("Any-Key") { |event|
  p event
  p event.methods()
}
and press a key. *splash* what a wealth of information! Take your time to look through it. The methods event.x() and event.y() exist, even for key-events.

You might consider using

root.bind("Motion", proc{|x, y| p "(#{x}, #{y})"}, "%x %y")
but for most fields, the string obscures what it is about. Look the list up in the Perl/Tk bindings.

Text, Focus and Value

A nice way to grab input from a user in TkEntrys and TkTexts is binding "FocusOut", which will catch about everything a user might expect. "FocusOut" is generated when closing a dialog that you popped up, when the user TABs to the next field and when the users passes focus to another window.

It is not called when the user presses ENTER (another key often used to traverse through entries) or when the mouse clicks a button. The first one should be bound explicitly. For obtaining the value of a TkEntry or a TkText, we need something slightly inconvenient:

entry = TkEntry.new(root)
var = TkVariable.new("f")
entry.textvariable(var)
entry.pack()
entry.bind("FocusOut") {
  p "contains #{var.value()}"
}
but then we can grab it when the button is clicked, just as easily with var.value or set it with var.value=

Defining your own Events

You can throw your own events and you can even define new types of events.

widget.event_generate("Expose")
Act as if widget generated an "Expose". Typically, the bindings of this event on a widget will cause it to redraw itself.

widget.bind("<Mine>") { p "My Event!" }
widget.event_generate("<Mine>")
The `<' and `>' are required to tell Tk that it is not one of its own events. See also the events thrown from TkOptionMenubutton.
Hello, World! Simple Widgets Binding Events More Widgets Scrollbars Menus Canvas Make things look Beautiful More Information

Kero