CSC/ECE 517 Fall 2010/ch1 S10 MM: Difference between revisions
m (→Introduction) |
|||
(137 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
= Introduction = | = Introduction = | ||
A GUI toolkit is a set of widgets and APIs that can be used and extended to construct a graphical user interface application. At a minimum a GUI toolkit will provide generic graphical widgets to display to the screen, and an eventing system to tie interactive widget pieces with other widgets, or to a data model. Most toolkits will provide widget containers for easy layouts, abstract data model types and | A GUI toolkit is a set of [http://en.wikipedia.org/wiki/GUI_widget widgets] and [http://en.wikipedia.org/wiki/Application_programming_interface APIs] that can be used and extended to construct a [http://en.wikipedia.org/wiki/Graphical_user_interface graphical user interface] application. At a minimum a GUI toolkit will provide generic graphical widgets to display to the screen (e.g. [http://doc.trolltech.com/latest/widget-classes.html Qt widgets]), and an [http://en.wikipedia.org/wiki/Event_loop eventing system] to tie interactive widget pieces with other widgets, or to a data model. Most toolkits will provide widget containers for easy layouts (e.g. [http://ruby.about.com/od/shoes/ss/shoes2.htm Shoes stacks and flows]), abstract data model types and structures (e.g. [http://doc.trolltech.com/latest/model-view-programming.html Qt models]), and an easy to use mechanism for implementing an [http://en.wikipedia.org/wiki/Observer_pattern observer pattern] for linking widgets and data structures. Advanced toolkits, such as Qt, will even offer convenience widgets for [http://doc.trolltech.com/latest/model-view-programming.html advanced data display], [http://doc.trolltech.com/latest/threads-qtconcurrent.html concurrent programming abstractions], and even a whole [http://doc.trolltech.com/latest/qtwebkit.html web browser backend]. Ideally a toolkit would work across several different operating systems with very minimal changes to the application code or compilation process. | ||
Most GUI toolkits are natively composed in C++. Object-oriented language features are all but required for the level of base functionality extension required in a large GUI systems. In many cases, though, [http://en.wikipedia.org/wiki/Language_binding | Most GUI toolkits are natively composed in [http://en.wikipedia.org/wiki/C%2B%2B C++]. Object-oriented language features are all but required for the level of base functionality extension required in a large GUI systems. In many cases, though, [http://en.wikipedia.org/wiki/Language_binding bindings] for the toolkit will spring up in different languages, and usually the most useful bindings will be in a dynamic scripting language such as [http://en.wikipedia.org/wiki/Python_%28programming_language%29 Python], or [http://en.wikipedia.org/wiki/Ruby_%28programming_language%29 Ruby]. This page will talk about several GUI toolkit bindings in the Ruby language. | ||
[http://en.wikipedia.org/wiki/Dynamic_programming_language Dynamic programming languages] offer many different advantages for GUI development. Since GUIs typically work with many different simple and composite data types, and the toolkit itself adds a large number of class types on top of that (e.g. [http://docs.wxwidgets.org/2.8/wx_classref.html wxWidgets]), defining class interfaces and usable data structures, and making them available to the right widgets at the right time within the application can be tedious in statically typed languages. Dynamic languages offer the ability to define, and redefine these things very quickly, allowing for [http://en.wikipedia.org/wiki/Rapid_application_development rapid application development]. | |||
The only downside to coding with the dynamic languages is that | The only downside to coding with the dynamic languages is that application performance will usually suffer [http://shootout.alioth.debian.org/], but even for large applications this may be acceptable if the application does not require a particularly high frame rate under heavy load. Additionally, logistically speaking, in some cases there are problems with the level of support and maintenance in a particular binding project, including delays between the release of a new native toolkit implementation, and accompanying bindings. It is always useful to look at when the last version control check-in was made for a project if that is available, and try to compare the latest binding release date with the latest native implementation release date. Also, it is usually harder to find documentation for the specific dynamic language that you are using, and you may be required to read C++ API documentation, and potentially source code for the binding project when you hit something you did not expect. There are also usually slight complications with deployment, and there may be licensing discrepancies between the C++ and Python/Ruby versions as well. | ||
= Overview of Ruby GUI Toolkits = | = Overview of Ruby GUI Toolkits = | ||
== Ruby/Tk == | == Ruby/Tk == | ||
Tk is an older GUI toolkit, originally written in Tcl. Tk offers a good set of generic widgets, widget containers for relatively easy layouts, a Canvas, and event binding. Unfortunately none of these features are very advanced, and they're generally quirky to use once you've | [http://en.wikipedia.org/wiki/Tk_%28framework%29 Tk] is an older GUI toolkit, originally written in [http://en.wikipedia.org/wiki/Tcl Tcl]. Tk offers a good set of generic widgets, widget containers for relatively easy layouts, a Canvas, and event binding [http://docs.activestate.com/activetcl/8.4/tcl/tk_contents.htm]. Unfortunately none of these features are very advanced, and they're generally quirky to use once you've worked with either a wxWidgets-based project, or a Qt project. For example, Tk appears to only give you a single callback or variable binding for widget output and events, instead of providing a more robust mechanism for observing data changes through events or signals. You could roll your own mechanism with those features, but that could get tedious in a hurry. Also, the widgets are drawn in a distinctly Tk-style, instead of in the native style of the OS. It is highly desirable for a production GUI application to offer the standard idioms that a user expects in a native OS application, so Tk probably should never be used for a globally deployed application. | ||
=== Example === | === Example === | ||
==== Hello World ==== | ==== Hello World ==== | ||
This is a simple hello world application. It demonstrates how to create a root-level frame, and add a single label to it with the given text. | This is a simple hello world application [http://www.tutorialspoint.com/ruby/ruby_tk_guide.htm]. It demonstrates how to create a root-level frame, and add a single label to it with the given text. | ||
<pre> | <pre> | ||
Line 32: | Line 32: | ||
==== Hello World button ==== | ==== Hello World button ==== | ||
This short example replaces the label from above with a TkButton, and connects the button to the "printHello" function via the "command" argument. | This short example replaces the label from above with a TkButton, and connects the button to the "printHello" function via the "command" argument. It looks similar to the example above, but with a button instead of a label, and it will print "Hello, world!" to the terminal when the button is pressed. | ||
<pre> | <pre> | ||
require 'tk' | require 'tk' | ||
Line 48: | Line 48: | ||
Tk.mainloop | Tk.mainloop | ||
</pre> | </pre> | ||
When executed, this example will appear as such: | |||
[[Image:Rubytk-hello_world_button.png]] | |||
Output from clicking the "Hello, world!" button: | |||
<pre> | |||
Hello, world! | |||
</pre> | |||
Notice that the "command" argument to the TkButton allows a generic codeblock, and any callable may be returned from this block. That is useful to do something like in the following example. Also notice that, unfortunately, that button does not look like the native operating system's button (in this case, Ubuntu's Ambiance style). See the section below on button closures for more examples of how to use these buttons, and a good explanation of one dynamic language technique as it relates to rapid GUI development. | |||
=== Pros and Cons === | === Pros and Cons === | ||
==== Pros ==== | ==== Pros ==== | ||
* | * Supported across many operating systems. | ||
* Tk has an implementation in a great many languages | * Tk has an implementation in a great many languages | ||
==== Cons ==== | ==== Cons ==== | ||
Line 61: | Line 71: | ||
== QtRuby == | == QtRuby == | ||
Qt is well-supported open source GUI toolkit that is available under the LGPL license. QtRuby attempts to offer the vast array of functionality provided by Qt in Ruby form. One useful and distinguishing characteristic of Qt is the signal/slot mechanism, which allows you to register an arbitrary number of "slots" to receive a particular "signal." Signals are generally emitted through interacting with widgets on the screen, but can be emitted from anywhere in the code. This allows a very powerful method | [http://qt.nokia.com/products/ Qt] is a well-supported open source GUI toolkit that is available under the [http://www.gnu.org/licenses/lgpl.html LGPL license]. [http://rubyforge.org/projects/korundum/ QtRuby] attempts to offer the vast array of functionality provided by Qt in Ruby form. One useful and distinguishing characteristic of Qt is the [http://doc.trolltech.com/latest/signalsandslots.html signal/slot] mechanism, which allows you to register an arbitrary number of "slots" to receive a particular "signal." Signals are generally emitted through interacting with widgets on the screen (e.g. [http://doc.trolltech.com/latest/qabstractbutton.html#clicked clicked()], or [http://doc.qt.nokia.com/latest/qgraphicsscene.html#selectionChanged selectionChanged()]), but can be emitted from anywhere in the code. This allows a very powerful method for many widgets to "observe" the behavior of any other widget, commonly referred to simply as the [http://en.wikipedia.org/wiki/Observer_pattern observer pattern]. | ||
Qt is very robust, and works across all three major operating systems. QtRuby strives to achieve the same feat. | Qt is very robust, and works across all three major operating systems. QtRuby strives to achieve the same feat. | ||
=== Examples === | === Examples === | ||
==== Hello World! ==== | ==== Hello World! ==== | ||
This is a very easy way to get started, and to make sure you've installed QtRuby correctly. Simply paste the code below into a file "hello_world.rb", and execute `ruby hello_world.rb`. It's just that easy. | This is a very easy way to get started, and to make sure you've installed QtRuby correctly.[http://www.darshancomputing.com/qt4-qtruby-tutorial/chapter_01] Simply paste the code below into a file "hello_world.rb", and execute `ruby hello_world.rb`. It's just that easy. | ||
<pre> | <pre> | ||
Line 82: | Line 90: | ||
</pre> | </pre> | ||
When run, this code shows this: | |||
[[Image:Qtruby-hello_world.png]] | [[Image:Qtruby-hello_world.png]] | ||
The example is relatively straightforward. The first line includes the Qt4 module. The second line instantiates an Qt "application" instance, for which they will only ever be one instance for the duration of the Qt portion of your application. The application contains the main event loop, some event handling mechanism, and just a few global variables that can be accessed from within your application. However, the application is relatively useless without some widgetry added. This program simply instantiates a "QPushButton" instance, and sets the size, and then tells it to show itself. Here is what it looks like: | |||
==== Signal/Slot setup ==== | ==== Signal/Slot setup ==== | ||
Line 138: | Line 148: | ||
[[Image:Qtruby-three_buttons.png]] | [[Image:Qtruby-three_buttons.png]] | ||
As you may expect, it prints out "Button X pressed!" whenever the button X is pressed. Unfortunately, QtRuby | Output from clicking each of the three buttons: | ||
<pre> | |||
Button 1 pressed!" | |||
Button 2 pressed!" | |||
Button 3 pressed!" | |||
</pre> | |||
As you may expect, it prints out "Button X pressed!" whenever the button X is pressed. Unfortunately, QtRuby forces you declare your slots upfront as a list of strings, instead of allowing any method to be used as a slot. This prevents you from executing code when creating the slot, which means that closures are very difficult, if not impossible. | |||
==== QtRuby slider ==== | ==== QtRuby slider ==== | ||
This is a more complex QtRuby application.[http://www.darshancomputing.com/qt4-qtruby-tutorial/chapter_05] | |||
<pre> | <pre> | ||
require 'Qt4' | require 'Qt4' | ||
Line 175: | Line 193: | ||
</pre> | </pre> | ||
When executed, this code looks like this: | |||
[[Image:Qtruby-slider.png]] | [[Image:Qtruby-slider.png]] | ||
This example illustrates how to create an [http://doc.trolltech.org/latest/qlcdnumber.html LCDNumber widget], and a [http://doc.trolltech.com/latest/qslider.html slider widget], add them to a vertical layout, then connect the slider "valueChanged" signal to the lcd "display" slot. When the slider is moved, it fires a "valueChanged(int)" signal, which includes the integer that the value was changed to. The "connect" method is used to link this signal, with the LCDNumber widget's "display(int)" slot, which changes the display to given integer. The layout is a very interesting widget container. The [http://doc.trolltech.com/4.6/qvboxlayout.html VBoxLayout] will arrange all widgets vertically. Inside the layout, each widget will have [http://doc.qt.nokia.com/4.6/qwidget.html#sizeHint-prop sizeHints] and a [http://doc.qt.nokia.com/4.6/qwidget.html#sizePolicy-prop sizePolicy], which the layout will query in order to size and resize the widget properly. This means that as the main window grows, the widgets inside will expand logically. In this example, the LCDNumber widget's size policy says to expand vertically as much as possible, the PushButton's policy says to expand vertically only if nobody else is expanding, and the slider has a maximum height; thus, when this window is expanded, only the LCDNumber display will grow vertically. Since this is a vertical layout, all widgets will expand horizontally as much as possible. | |||
=== Pros and Cons === | === Pros and Cons === | ||
==== Pros ==== | ==== Pros ==== | ||
* Excellent documentation | * Excellent C++ documentation [http://doc.qt.nokia.com/] | ||
* Easy to use | * Easy to use, relatively intuitive [http://doc.trolltech.com/latest/how-to-learn-qt.html] | ||
* Extensive widget collection and default signals/slots | * Extensive widget collection and default signals/slots | ||
* | * Very good native OS look and feel reproduction | ||
* Qt Designer for rapid layout creation.[http://doc.trolltech.com/4.6/designer-manual.html] | |||
* Latest release date as of this writing: July 29, 2010.[http://en.wikipedia.org/wiki/QtRuby] | |||
==== Cons ==== | ==== Cons ==== | ||
* Requires you to declare slots upfront, instead of being able to use any method, or generic callable as a slot. | * Requires you to declare slots upfront, instead of being able to use any method, or generic callable as a slot. | ||
* | * Generally do have to rely on C++ documentation, but the reproduction appears faithful. | ||
== Shoes == | |||
[http://shoes.heroku.com/ Shoes] is a very popular, native Ruby GUI toolkit. It does not use the typical [http://en.wikipedia.org/wiki/Language_binding language bindings] that makeup almost all dynamic language toolkits. Shoes also has its own interpreter, but it does use the operating system's native widgets pretty well. | |||
=== Examples === | |||
==== Hello, World! ==== | |||
This is a simple program to great the world. Here is all the code you need: | |||
<pre> | |||
Shoes.app :height => 60, :width => 200 do | |||
@note = para "Hello, world!" | |||
end | |||
</pre> | |||
Notice that there is now "require" statement. That is because you have to execute this app with the Shoes interpreter by typing `shoes hello_world.rb`. When executed, this application looks like this: | |||
[[Image:Hello_world.png]] | |||
The Shoes app instance intelligently populates itself based on widgets that are declared inside of it. The "height" and "width" arguments specify those values for the initial startup window. | |||
==== Stacks and Flows ==== | |||
Shoes answers to the widget layout problem are stacks and flows. Stacks, as you may expect, arrange widgets vertically. Flows arrange them horizontally. Here is a simple example of a stack, which places widgets vertically [http://ruby.about.com/od/shoes/ss/shoes2.htm]: | |||
===== Simple Stacks and Flows ===== | |||
<pre> | |||
Shoes.app :width => 200, :height => 140 do | |||
stack do | |||
button "Button 1" | |||
button "Button 2" | |||
button "Button 3" | |||
end | |||
end | |||
</pre> | |||
When executed with the shoes interpreter, this example will look like this: | |||
[[Image:Shoes-stack.png]] | |||
As you can see, a stack simply lays the three buttons out vertically. A flow will lay them out horizontally [http://ruby.about.com/od/shoes/ss/shoes2_2.htm]: | |||
<pre> | |||
Shoes.app :width => 400, :height => 140 do | |||
flow do | |||
button "Button 1" | |||
button "Button 2" | |||
button "Button 3" | |||
end | |||
end | |||
</pre> | |||
[[Image:Shoes-flow.png]] | |||
===== Nested Stacks and Flows ===== | |||
As you may expect, stacks and flows can be nested inside each other to create complex layouts. Each stack and flow can also be given a percentage of the screen that they may expand to. Here is an example of nested stacks and flows that also specify a screen space percentage: [http://ruby.about.com/od/shoes/ss/shoes2_6.htm] | |||
<pre> | |||
Shoes.app :width => 400, :height => 140 do | |||
flow do | |||
stack :width => '33%' do | |||
button "Button 1" | |||
button "Button 2" | |||
button "Button 3" | |||
button "Button 4" | |||
end | |||
stack :width => '33%' do | |||
para "This is the paragraph " + | |||
"text, it will wrap around and fill the column." | |||
end | |||
stack :width => '33%' do | |||
button "Button 1" | |||
button "Button 2" | |||
button "Button 3" | |||
button "Button 4" | |||
end | |||
end | |||
end | |||
</pre> | |||
When executed, this example will look like this: | |||
[[Image:Shoes-nested.png]] | |||
As you can see, each vertical stack it meant to take up 33% of the outside horizontal flow. This allows relatively quick layouts to be created in Shoes. Also, as in all of the examples, the buttons look exactly like the expected buttons in the native OS (Ubuntu, Ambiance style). | |||
=== Pros and Cons === | |||
==== Pros ==== | |||
* Very Ruby-esque. | |||
* Relatively popular.[http://www.rubyinside.com/ruby-gui-programming-survey-results-1552.html] | |||
==== Cons ==== | |||
* Last stable release was Dec. 5th 2008.[http://en.wikipedia.org/wiki/Shoes_%28GUI_toolkit%29] | |||
* Since it is purely Ruby, you will not be able to easily port the ideas to a native language. | |||
== wxRuby == | == wxRuby == | ||
wxWidgets is a very popular GUI toolkit as it has been under an open license resembling the LGPL for much longer than Qt. It is entirely open source and never developed exclusively by a for-profit company. Documentation for wxWidgets is generally very good, although a lot of documentation for wxPython is generally found when hitting google. | [http://www.wxwidgets.org/ wxWidgets] is a very popular GUI toolkit as it has been under an open license resembling the LGPL for much longer than Qt. It is entirely open source and never developed exclusively by a for-profit company. Documentation for wxWidgets is generally very good, although a lot of documentation for wxPython is generally found when hitting google. | ||
=== Examples === | === Examples === | ||
==== Hello World ==== | ==== Hello World ==== | ||
This is a simple "hello world" application in wxRuby. It demonstrates a way to create an "App" instance, and display a frame. | This is a simple "hello world" application in wxRuby.[http://wxruby.rubyforge.org/wiki/wiki.pl?Hello_World] It demonstrates a way to create an "App" instance, and display a frame. | ||
<pre> | <pre> | ||
require "wx" | require "wx" | ||
Line 214: | Line 329: | ||
</pre> | </pre> | ||
wxRuby requires you to inherit and instantiate a WX::App, and create widgetry from there. A "Frame" is | wxRuby requires you to inherit and instantiate a WX::App, and create widgetry from there. A "Frame" is the generic top-level container for more widgets, and here it is being made to be the parent of the StaticText widget, which is just a label, or sorts. | ||
==== More Complex ==== | ==== More Complex ==== | ||
This application demonstrates a more typical implementation of wxRuby.[http://rubyonwindows.blogspot.com/2007/11/getting-started-with-wxruby-gui-toolkit.html] | |||
<pre> | <pre> | ||
require 'wx' | require 'wx' | ||
Line 257: | Line 373: | ||
</pre> | </pre> | ||
When executed, this example looks like this: | |||
[[Image:WxForm1.jpg]] | [[Image:WxForm1.jpg]] | ||
In this example, the "App" instance is created and executed, but all it does is create a new top-level frame from different class. This is a much better code structure than the "Hello, World!" example. The "MyFrame" class creates "Panel," which is a generic Wx container. That container is eventually given a BoxSizer, which is similar to a layout in Qt. Prior to that, a label, combobox, TextCtrl, and a Button are created, and all four widgets are added to that sizer. Also, you can see one of the easier ways to connect the button to a randomly specified function, by using a code block, in the "evt_button" method call. This feature allows useful, arbitrary closures, which greatly facilitates rapid GUI development, as you do not have to keep up with event numbers, or slots. | |||
=== Pros and cons === | === Pros and cons === | ||
==== Pros ==== | ==== Pros ==== | ||
* Very good C++ documentation | * Very good C++ documentation | ||
* | * Well-maintained. Latest release date: September 8th, 2009[http://rubyforge.org/frs/?group_id=35] | ||
* Longer time under the LGPL means it's generally used more often in the industry for rapid prototyping | * Longer time under the LGPL means it's generally used more often in the industry for rapid prototyping [http://eli.thegreenplace.net/2009/01/19/moving-to-pyqt/] | ||
* Works on Windows, Linux, Mac, and BSD. | * Works on Windows, Linux, Mac, and BSD.[http://wxruby.rubyforge.org/wiki/wiki.pl?WxRubyFAQs] | ||
* Generic code blocks may be connected to default widget event emitters. | * Generic code blocks may be connected to default widget event emitters. | ||
* [http://wxglade.sourceforge.net/ wxGlade] for rapid layout creation | |||
==== Cons ==== | ==== Cons ==== | ||
* Proper event management and usage can get tricky with large applications, though see above about connections with generic code blocks. | * Proper event management and usage can get tricky with large applications, though see above about connections with generic code blocks. | ||
* Not as many native widgets as Qt | * Not as many native widgets as Qt. [http://eli.thegreenplace.net/2009/01/19/moving-to-pyqt/] | ||
* Generally not as intuitive as Qt. | * Generally not as intuitive as Qt. [http://eli.thegreenplace.net/2009/01/19/moving-to-pyqt/] | ||
* wxGlade is relatively outdated.[http://wxglade.sourceforge.net/] | |||
= Other GUI toolkits = | = Other GUI toolkits = | ||
* FXRuby | * [http://www.fxruby.org/ FXRuby] | ||
* Ruby-GNOME2 | * [http://ruby-gnome2.sourceforge.jp/ Ruby-GNOME2] | ||
* | |||
= Ruby Closures with GUI Toolkit Widgets = | |||
There are many different things that are easier to do in dynamic languages than they are in natively compiled languages like C++, and some of these easy techniques can make GUI programming more dynamic and easy to implement. For example, sometimes it is desirable to connect multiple buttons to the same exact method, but to provide that method with slightly different arguments. However, in many toolkits the button's "click" event does not provide any useful information about which button was actually clicked (i.e., it does not call the connected function with any parameters), so you may end up tediously creating one function for each button, and having that function call the much more desirable function with the parameter that you really wanted. Here is a quick example of three buttons hooked to three functions, using Ruby/Tk: | |||
=== Naive === | |||
<pre> | |||
require 'tk' | |||
def buttonPressed(number) | |||
puts "Button #{number} pressed!" | |||
end | |||
def buttonPress1() | |||
buttonPressed(1) | |||
end | |||
def buttonPress2() | |||
buttonPressed(2) | |||
end | |||
def buttonPress3() | |||
buttonPressed(3) | |||
end | |||
root = TkRoot.new { title "Hello, World!" } | |||
button1 = TkButton.new(root) do | |||
text "Button 1" | |||
command proc { buttonPress1 } | |||
pack { padx 15 ; pady 15; side 'left' } | |||
end | |||
button2 = TkButton.new(root) do | |||
text "Button 2" | |||
command proc { buttonPress2 } | |||
pack { padx 15 ; pady 15; side 'left' } | |||
end | |||
button3 = TkButton.new(root) do | |||
text "Button 3" | |||
command proc { buttonPress3 } | |||
pack { padx 15 ; pady 15; side 'left' } | |||
end | |||
Tk.mainloop | |||
</pre> | |||
This application will look like this, and will print the expected "Button X pressed!" message from above: | |||
[[Image:Rubytk-three_buttons.png]] | |||
Output from clicking each of the three buttons: | |||
<pre> | |||
Button 1 pressed! | |||
Button 2 pressed! | |||
Button 3 pressed! | |||
</pre> | |||
The thing to note here is that "buttonPress*" is repeated 10 times in various contexts. There is the "buttonPressed(number)" function that we really wanted, three "buttonPressX" functions that all call "buttonPressed(X)," and then each of those functions is passed in as the command to its corresponding button. That's a lot of seemingly repeated code. Doesn't that go against one of our main coding principles, [http://en.wikipedia.org/wiki/Don%27t_repeat_yourself DRY]? Can't we do better than that? | |||
=== Better === | |||
It would be far better to make use of Ruby's ability to create [http://en.wikipedia.org/wiki/Closure_%28computer_science%29 closures], if possible. The button will only ever call a method that has zero required parameters, but Ruby can save a method context with parameters in it to be called later, like this: | |||
<pre> | |||
require 'tk' | |||
= | def buttonPressed(number) | ||
lambda {puts "Button #{number} pressed!"} | |||
end | |||
root = TkRoot.new { title "Hello, World!" } | |||
button1 = TkButton.new(root) do | |||
text "Button 1" | |||
command buttonPressed(1) | |||
pack { padx 15 ; pady 15; side 'left' } | |||
end | |||
button2 = TkButton.new(root) do | |||
text "Button 2" | |||
command buttonPressed(2) | |||
pack { padx 15 ; pady 15; side 'left' } | |||
end | |||
button3 = TkButton.new(root) do | |||
text "Button 3" | |||
command buttonPressed(3) | |||
pack { padx 15 ; pady 15; side 'left' } | |||
end | |||
Tk.mainloop | |||
</pre> | |||
From this, you will get the same look, and the same output as above. | |||
Notice "buttonPressed(number)" returns an anonymous function that takes no parameters. The "command buttonPressed(X)" lines actually execute the buttonPressed method, which then stores "X" in the anonymous method's context so that it can be used later. This is a common idea when mixing GUI toolkits and dynamic languages. The toolkit may provide rigid [http://en.wikipedia.org/wiki/Callback_%28computer_science%29 callback] facilities that, at best, can be used in the first "naive" manner above. However, since Ruby (and Python) offer the flexibility of closures, we can augment a callback with some context when we pass it to the widget that will eventually call it. You will notice below that QtRuby, unfortunately, makes this hard by requiring each "slot" to be registered as a string at startup (unless the author has missed something), but both Ruby/Tk and wxRuby offer this facility. The ability to do this can quickly become addictive when building out a quick GUI application. | |||
It is worth noting that you are not limited to just buttons to make use of this idea. You are not limited to one extra parameter, either, and this method can be used to augment callbacks that will be called with one or more arguments by the widget to which it was passed, as well. You are also not limited to static arguments; you could pass a variable, or even another method, or a whole object instance as the argument to buttonPressed, and that will stick around to be used later when the widget executes its command. As long as the method signature matches what the widget will call, you are fine. | |||
=== Best...or maybe just different === | |||
The closure method above is useful to know, however in that situation we created a function that just returns another function. What if you really wanted the more desirable function to just go ahead and do what it says it will do? Maybe you would like to use the "buttonPressed" functionality where there is no button involved? If you tried that with the function above, you would have to do something funny like this: "buttonPressed(1).call()". That is not very desirable, so a slight better way to accomplish the exact same thing as above is to do something like this: | |||
<pre> | |||
require 'tk' | |||
def buttonPressed(number) | |||
puts "Button #{number} pressed!" | |||
end | |||
root = TkRoot.new { title "Hello, World!" } | |||
button1 = TkButton.new(root) do | |||
text "Button 1" | |||
command proc { buttonPressed(1) } | |||
pack { padx 15 ; pady 15; side 'left' } | |||
end | |||
button2 = TkButton.new(root) do | |||
text "Button 2" | |||
command proc { buttonPressed(2) } | |||
pack { padx 15 ; pady 15; side 'left' } | |||
end | |||
button3 = TkButton.new(root) do | |||
text "Button 3" | |||
command proc { buttonPressed(3) } | |||
pack { padx 15 ; pady 15; side 'left' } | |||
end | |||
Tk.mainloop | |||
</pre> | |||
Once again, you will look the same, and print the same output as the first "buttons" example. | |||
Notice that now the "buttonPressed(number)" function simply prints a message, and it is actually the anonymous "proc { buttonPressed(X) }" function that is storing the parameter in its context to use later. It's probably worthwhile to point out again that Ruby/Tk is not the best GUI toolkit available for Ruby. However, this technique can generally be used with any toolkit in Ruby, or any toolkit in any dynamic language, or just any dynamic language application period. | |||
= Conclusion = | |||
Dynamic languages are very good for quick GUI prototyping, and Ruby is one shining example of that. While Qt may be a more robust GUI toolkit on its own, it seems like QtRuby has some restrictions that would make rapid [http://en.wikipedia.org/wiki/Prototype-based_programming prototyping] slower than it really should be, since slots have to be managed in a long list, and cannot be dynamic instance methods or code blocks. wxRuby appears to be well-supported, well-documented, and easy to use for prototyping. wxRuby may even be good enough for large team usage, if not for production release software. Ruby/Tk is only suitable for individual or small team usage. Nevertheless, if Ruby is a well-understood language on a given team, any of these three Ruby toolkit implementations would be better for rapid prototyping, or for small helper applications than heavier languages like C++ and Java. | |||
= References = | = References = | ||
1. The Computer Language Benchmarks Game [http://shootout.alioth.debian.org/] | |||
2. QtRuby Wikipedia [http://en.wikipedia.org/wiki/QtRuby] | |||
= External Links = | = External Links = | ||
Line 297: | Line 541: | ||
[http://wxruby.rubyforge.org/wiki/wiki.pl?Hello_World wxRuby Hello World] | [http://wxruby.rubyforge.org/wiki/wiki.pl?Hello_World wxRuby Hello World] | ||
[http://shoes.heroku.com/ Shoes homepage] | |||
[http://wiki.github.com/shoes/shoes/ Shoes wiki] | |||
[http://ruby.about.com/od/shoes/ss/shoes2.htm Shoes Stacks and Flows] | |||
[http://qt.nokia.com/products/ Qt Homepage] | [http://qt.nokia.com/products/ Qt Homepage] |
Latest revision as of 03:37, 18 September 2010
Introduction
A GUI toolkit is a set of widgets and APIs that can be used and extended to construct a graphical user interface application. At a minimum a GUI toolkit will provide generic graphical widgets to display to the screen (e.g. Qt widgets), and an eventing system to tie interactive widget pieces with other widgets, or to a data model. Most toolkits will provide widget containers for easy layouts (e.g. Shoes stacks and flows), abstract data model types and structures (e.g. Qt models), and an easy to use mechanism for implementing an observer pattern for linking widgets and data structures. Advanced toolkits, such as Qt, will even offer convenience widgets for advanced data display, concurrent programming abstractions, and even a whole web browser backend. Ideally a toolkit would work across several different operating systems with very minimal changes to the application code or compilation process.
Most GUI toolkits are natively composed in C++. Object-oriented language features are all but required for the level of base functionality extension required in a large GUI systems. In many cases, though, bindings for the toolkit will spring up in different languages, and usually the most useful bindings will be in a dynamic scripting language such as Python, or Ruby. This page will talk about several GUI toolkit bindings in the Ruby language.
Dynamic programming languages offer many different advantages for GUI development. Since GUIs typically work with many different simple and composite data types, and the toolkit itself adds a large number of class types on top of that (e.g. wxWidgets), defining class interfaces and usable data structures, and making them available to the right widgets at the right time within the application can be tedious in statically typed languages. Dynamic languages offer the ability to define, and redefine these things very quickly, allowing for rapid application development.
The only downside to coding with the dynamic languages is that application performance will usually suffer [1], but even for large applications this may be acceptable if the application does not require a particularly high frame rate under heavy load. Additionally, logistically speaking, in some cases there are problems with the level of support and maintenance in a particular binding project, including delays between the release of a new native toolkit implementation, and accompanying bindings. It is always useful to look at when the last version control check-in was made for a project if that is available, and try to compare the latest binding release date with the latest native implementation release date. Also, it is usually harder to find documentation for the specific dynamic language that you are using, and you may be required to read C++ API documentation, and potentially source code for the binding project when you hit something you did not expect. There are also usually slight complications with deployment, and there may be licensing discrepancies between the C++ and Python/Ruby versions as well.
Overview of Ruby GUI Toolkits
Ruby/Tk
Tk is an older GUI toolkit, originally written in Tcl. Tk offers a good set of generic widgets, widget containers for relatively easy layouts, a Canvas, and event binding [2]. Unfortunately none of these features are very advanced, and they're generally quirky to use once you've worked with either a wxWidgets-based project, or a Qt project. For example, Tk appears to only give you a single callback or variable binding for widget output and events, instead of providing a more robust mechanism for observing data changes through events or signals. You could roll your own mechanism with those features, but that could get tedious in a hurry. Also, the widgets are drawn in a distinctly Tk-style, instead of in the native style of the OS. It is highly desirable for a production GUI application to offer the standard idioms that a user expects in a native OS application, so Tk probably should never be used for a globally deployed application.
Example
Hello World
This is a simple hello world application [3]. It demonstrates how to create a root-level frame, and add a single label to it with the given text.
require 'tk' root = TkRoot.new { title "Hello, World!" } TkLabel.new(root) do text 'Hello, World!' pack { padx 15 ; pady 15; side 'left' } end Tk.mainloop
When run, this program will display the following:
Hello World button
This short example replaces the label from above with a TkButton, and connects the button to the "printHello" function via the "command" argument. It looks similar to the example above, but with a button instead of a label, and it will print "Hello, world!" to the terminal when the button is pressed.
require 'tk' def printHello puts "Hello, world!" end root = TkRoot.new { title "Hello, World!" } helpButton = TkButton.new(root) do text "Hello, world!" command proc { printHello } pack { padx 15 ; pady 15; side 'left' } end Tk.mainloop
When executed, this example will appear as such:
Output from clicking the "Hello, world!" button:
Hello, world!
Notice that the "command" argument to the TkButton allows a generic codeblock, and any callable may be returned from this block. That is useful to do something like in the following example. Also notice that, unfortunately, that button does not look like the native operating system's button (in this case, Ubuntu's Ambiance style). See the section below on button closures for more examples of how to use these buttons, and a good explanation of one dynamic language technique as it relates to rapid GUI development.
Pros and Cons
Pros
- Supported across many operating systems.
- Tk has an implementation in a great many languages
Cons
- Very old
- Does not use native OS widgets
- Generally considered quirky and hard to use.
QtRuby
Qt is a well-supported open source GUI toolkit that is available under the LGPL license. QtRuby attempts to offer the vast array of functionality provided by Qt in Ruby form. One useful and distinguishing characteristic of Qt is the signal/slot mechanism, which allows you to register an arbitrary number of "slots" to receive a particular "signal." Signals are generally emitted through interacting with widgets on the screen (e.g. clicked(), or selectionChanged()), but can be emitted from anywhere in the code. This allows a very powerful method for many widgets to "observe" the behavior of any other widget, commonly referred to simply as the observer pattern.
Qt is very robust, and works across all three major operating systems. QtRuby strives to achieve the same feat.
Examples
Hello World!
This is a very easy way to get started, and to make sure you've installed QtRuby correctly.[4] Simply paste the code below into a file "hello_world.rb", and execute `ruby hello_world.rb`. It's just that easy.
require 'Qt4' app = Qt::Application.new(ARGV) hello = Qt::PushButton.new('Hello World!') hello.resize(100, 30) hello.show() app.exec()
When run, this code shows this:
The example is relatively straightforward. The first line includes the Qt4 module. The second line instantiates an Qt "application" instance, for which they will only ever be one instance for the duration of the Qt portion of your application. The application contains the main event loop, some event handling mechanism, and just a few global variables that can be accessed from within your application. However, the application is relatively useless without some widgetry added. This program simply instantiates a "QPushButton" instance, and sets the size, and then tells it to show itself. Here is what it looks like:
Signal/Slot setup
As mentioned, Qt's signal/slot mechanism is relatively unique in the GUI toolkit world, and is extremely easy to use. This is a quick demonstration showing how to create a new custom widget by subclassing the QWidget class, and then creating a vertical box layout, add three buttons to it, and connect those buttons to three different slots:
require 'Qt4' class MyWidget < Qt::Widget slots 'button1()', 'button2()', 'button3()' def initialize(parent = nil) super setFixedSize(200, 120) button1 = Qt::PushButton.new(tr('Button1'), self) button2 = Qt::PushButton.new(tr('Button2'), self) button3 = Qt::PushButton.new(tr('Button3'), self) buttonLayout = Qt::VBoxLayout.new() buttonLayout.addWidget(button1) buttonLayout.addWidget(button2) buttonLayout.addWidget(button3) setLayout(buttonLayout) connect(button1, SIGNAL('clicked()'), self, SLOT('button1()')) connect(button2, SIGNAL('clicked()'), self, SLOT('button2()')) connect(button3, SIGNAL('clicked()'), self, SLOT('button3()')) end def button1() puts "Button 1 pressed!" end def button2() puts "Button 2 pressed!" end def button3() puts "Button 3 pressed!" end end app = Qt::Application.new(ARGV) widget = MyWidget.new() widget.show() app.exec()
Here is what this application looks like when you run it:
Output from clicking each of the three buttons:
Button 1 pressed!" Button 2 pressed!" Button 3 pressed!"
As you may expect, it prints out "Button X pressed!" whenever the button X is pressed. Unfortunately, QtRuby forces you declare your slots upfront as a list of strings, instead of allowing any method to be used as a slot. This prevents you from executing code when creating the slot, which means that closures are very difficult, if not impossible.
QtRuby slider
This is a more complex QtRuby application.[5]
require 'Qt4' class MyWidget < Qt::Widget def initialize() super() quit = Qt::PushButton.new('Quit') quit.setFont(Qt::Font.new('Times', 18, Qt::Font::Bold)) lcd = Qt::LCDNumber.new(2) slider = Qt::Slider.new(Qt::Horizontal) slider.setRange(0, 99) slider.setValue(0) connect(quit, SIGNAL('clicked()'), $qApp, SLOT('quit()')) connect(slider, SIGNAL('valueChanged(int)'), lcd, SLOT('display(int)')) layout = Qt::VBoxLayout.new() layout.addWidget(quit) layout.addWidget(lcd) layout.addWidget(slider) setLayout(layout) end end app = Qt::Application.new(ARGV) widget = MyWidget.new() widget.show() app.exec()
When executed, this code looks like this:
This example illustrates how to create an LCDNumber widget, and a slider widget, add them to a vertical layout, then connect the slider "valueChanged" signal to the lcd "display" slot. When the slider is moved, it fires a "valueChanged(int)" signal, which includes the integer that the value was changed to. The "connect" method is used to link this signal, with the LCDNumber widget's "display(int)" slot, which changes the display to given integer. The layout is a very interesting widget container. The VBoxLayout will arrange all widgets vertically. Inside the layout, each widget will have sizeHints and a sizePolicy, which the layout will query in order to size and resize the widget properly. This means that as the main window grows, the widgets inside will expand logically. In this example, the LCDNumber widget's size policy says to expand vertically as much as possible, the PushButton's policy says to expand vertically only if nobody else is expanding, and the slider has a maximum height; thus, when this window is expanded, only the LCDNumber display will grow vertically. Since this is a vertical layout, all widgets will expand horizontally as much as possible.
Pros and Cons
Pros
- Excellent C++ documentation [6]
- Easy to use, relatively intuitive [7]
- Extensive widget collection and default signals/slots
- Very good native OS look and feel reproduction
- Qt Designer for rapid layout creation.[8]
- Latest release date as of this writing: July 29, 2010.[9]
Cons
- Requires you to declare slots upfront, instead of being able to use any method, or generic callable as a slot.
- Generally do have to rely on C++ documentation, but the reproduction appears faithful.
Shoes
Shoes is a very popular, native Ruby GUI toolkit. It does not use the typical language bindings that makeup almost all dynamic language toolkits. Shoes also has its own interpreter, but it does use the operating system's native widgets pretty well.
Examples
Hello, World!
This is a simple program to great the world. Here is all the code you need:
Shoes.app :height => 60, :width => 200 do @note = para "Hello, world!" end
Notice that there is now "require" statement. That is because you have to execute this app with the Shoes interpreter by typing `shoes hello_world.rb`. When executed, this application looks like this:
The Shoes app instance intelligently populates itself based on widgets that are declared inside of it. The "height" and "width" arguments specify those values for the initial startup window.
Stacks and Flows
Shoes answers to the widget layout problem are stacks and flows. Stacks, as you may expect, arrange widgets vertically. Flows arrange them horizontally. Here is a simple example of a stack, which places widgets vertically [10]:
Simple Stacks and Flows
Shoes.app :width => 200, :height => 140 do stack do button "Button 1" button "Button 2" button "Button 3" end end
When executed with the shoes interpreter, this example will look like this:
As you can see, a stack simply lays the three buttons out vertically. A flow will lay them out horizontally [11]:
Shoes.app :width => 400, :height => 140 do flow do button "Button 1" button "Button 2" button "Button 3" end end
Nested Stacks and Flows
As you may expect, stacks and flows can be nested inside each other to create complex layouts. Each stack and flow can also be given a percentage of the screen that they may expand to. Here is an example of nested stacks and flows that also specify a screen space percentage: [12]
Shoes.app :width => 400, :height => 140 do flow do stack :width => '33%' do button "Button 1" button "Button 2" button "Button 3" button "Button 4" end stack :width => '33%' do para "This is the paragraph " + "text, it will wrap around and fill the column." end stack :width => '33%' do button "Button 1" button "Button 2" button "Button 3" button "Button 4" end end end
When executed, this example will look like this:
As you can see, each vertical stack it meant to take up 33% of the outside horizontal flow. This allows relatively quick layouts to be created in Shoes. Also, as in all of the examples, the buttons look exactly like the expected buttons in the native OS (Ubuntu, Ambiance style).
Pros and Cons
Pros
- Very Ruby-esque.
- Relatively popular.[13]
Cons
- Last stable release was Dec. 5th 2008.[14]
- Since it is purely Ruby, you will not be able to easily port the ideas to a native language.
wxRuby
wxWidgets is a very popular GUI toolkit as it has been under an open license resembling the LGPL for much longer than Qt. It is entirely open source and never developed exclusively by a for-profit company. Documentation for wxWidgets is generally very good, although a lot of documentation for wxPython is generally found when hitting google.
Examples
Hello World
This is a simple "hello world" application in wxRuby.[15] It demonstrates a way to create an "App" instance, and display a frame.
require "wx" include Wx # a new class which derives from the Wx::App class class HelloWorld < App # we're defining what the application is going to do when it starts def on_init # it's going to make a frame entitled "Hello World" helloframe = Frame.new(nil, -1, "Hello World") # it's going to put the text "Hello World" in that frame StaticText.new(helloframe,-1,"Hello World") # and then it's going to make the window appear helloframe.show() end end # and this line makes it actually do it! HelloWorld.new.main_loop
wxRuby requires you to inherit and instantiate a WX::App, and create widgetry from there. A "Frame" is the generic top-level container for more widgets, and here it is being made to be the parent of the StaticText widget, which is just a label, or sorts.
More Complex
This application demonstrates a more typical implementation of wxRuby.[16]
require 'wx' include Wx class MyFrame < Frame def initialize() super(nil, -1, 'My Frame Title') # First create the controls @my_panel = Panel.new(self) @my_label = StaticText.new(@my_panel, -1, 'My Label Text', DEFAULT_POSITION, DEFAULT_SIZE, ALIGN_CENTER) @my_textbox = TextCtrl.new(@my_panel, -1, 'Default Textbox Value') @my_combo = ComboBox.new(@my_panel, -1, 'Default Combo Text', DEFAULT_POSITION, DEFAULT_SIZE, ['Item 1', 'Item 2', 'Item 3']) @my_button = Button.new(@my_panel, -1, 'My Button Text') # Bind controls to functions evt_button(@my_button.get_id()) { |event| my_button_click(event)} # Now do the layout @my_panel_sizer = BoxSizer.new(VERTICAL) @my_panel.set_sizer(@my_panel_sizer) @my_panel_sizer.add(@my_label, 0, GROW|ALL, 2) @my_panel_sizer.add(@my_textbox, 0, GROW|ALL, 2) @my_panel_sizer.add(@my_combo, 0, GROW|ALL, 2) @my_panel_sizer.add(@my_button, 0, GROW|ALL, 2) show() end def my_button_click(event) puts "Button clicked!" end end class MyApp < App def on_init MyFrame.new end end MyApp.new.main_loop()
When executed, this example looks like this:
In this example, the "App" instance is created and executed, but all it does is create a new top-level frame from different class. This is a much better code structure than the "Hello, World!" example. The "MyFrame" class creates "Panel," which is a generic Wx container. That container is eventually given a BoxSizer, which is similar to a layout in Qt. Prior to that, a label, combobox, TextCtrl, and a Button are created, and all four widgets are added to that sizer. Also, you can see one of the easier ways to connect the button to a randomly specified function, by using a code block, in the "evt_button" method call. This feature allows useful, arbitrary closures, which greatly facilitates rapid GUI development, as you do not have to keep up with event numbers, or slots.
Pros and cons
Pros
- Very good C++ documentation
- Well-maintained. Latest release date: September 8th, 2009[17]
- Longer time under the LGPL means it's generally used more often in the industry for rapid prototyping [18]
- Works on Windows, Linux, Mac, and BSD.[19]
- Generic code blocks may be connected to default widget event emitters.
- wxGlade for rapid layout creation
Cons
- Proper event management and usage can get tricky with large applications, though see above about connections with generic code blocks.
- Not as many native widgets as Qt. [20]
- Generally not as intuitive as Qt. [21]
- wxGlade is relatively outdated.[22]
Other GUI toolkits
Ruby Closures with GUI Toolkit Widgets
There are many different things that are easier to do in dynamic languages than they are in natively compiled languages like C++, and some of these easy techniques can make GUI programming more dynamic and easy to implement. For example, sometimes it is desirable to connect multiple buttons to the same exact method, but to provide that method with slightly different arguments. However, in many toolkits the button's "click" event does not provide any useful information about which button was actually clicked (i.e., it does not call the connected function with any parameters), so you may end up tediously creating one function for each button, and having that function call the much more desirable function with the parameter that you really wanted. Here is a quick example of three buttons hooked to three functions, using Ruby/Tk:
Naive
require 'tk' def buttonPressed(number) puts "Button #{number} pressed!" end def buttonPress1() buttonPressed(1) end def buttonPress2() buttonPressed(2) end def buttonPress3() buttonPressed(3) end root = TkRoot.new { title "Hello, World!" } button1 = TkButton.new(root) do text "Button 1" command proc { buttonPress1 } pack { padx 15 ; pady 15; side 'left' } end button2 = TkButton.new(root) do text "Button 2" command proc { buttonPress2 } pack { padx 15 ; pady 15; side 'left' } end button3 = TkButton.new(root) do text "Button 3" command proc { buttonPress3 } pack { padx 15 ; pady 15; side 'left' } end Tk.mainloop
This application will look like this, and will print the expected "Button X pressed!" message from above:
Output from clicking each of the three buttons:
Button 1 pressed! Button 2 pressed! Button 3 pressed!
The thing to note here is that "buttonPress*" is repeated 10 times in various contexts. There is the "buttonPressed(number)" function that we really wanted, three "buttonPressX" functions that all call "buttonPressed(X)," and then each of those functions is passed in as the command to its corresponding button. That's a lot of seemingly repeated code. Doesn't that go against one of our main coding principles, DRY? Can't we do better than that?
Better
It would be far better to make use of Ruby's ability to create closures, if possible. The button will only ever call a method that has zero required parameters, but Ruby can save a method context with parameters in it to be called later, like this:
require 'tk' def buttonPressed(number) lambda {puts "Button #{number} pressed!"} end root = TkRoot.new { title "Hello, World!" } button1 = TkButton.new(root) do text "Button 1" command buttonPressed(1) pack { padx 15 ; pady 15; side 'left' } end button2 = TkButton.new(root) do text "Button 2" command buttonPressed(2) pack { padx 15 ; pady 15; side 'left' } end button3 = TkButton.new(root) do text "Button 3" command buttonPressed(3) pack { padx 15 ; pady 15; side 'left' } end Tk.mainloop
From this, you will get the same look, and the same output as above.
Notice "buttonPressed(number)" returns an anonymous function that takes no parameters. The "command buttonPressed(X)" lines actually execute the buttonPressed method, which then stores "X" in the anonymous method's context so that it can be used later. This is a common idea when mixing GUI toolkits and dynamic languages. The toolkit may provide rigid callback facilities that, at best, can be used in the first "naive" manner above. However, since Ruby (and Python) offer the flexibility of closures, we can augment a callback with some context when we pass it to the widget that will eventually call it. You will notice below that QtRuby, unfortunately, makes this hard by requiring each "slot" to be registered as a string at startup (unless the author has missed something), but both Ruby/Tk and wxRuby offer this facility. The ability to do this can quickly become addictive when building out a quick GUI application.
It is worth noting that you are not limited to just buttons to make use of this idea. You are not limited to one extra parameter, either, and this method can be used to augment callbacks that will be called with one or more arguments by the widget to which it was passed, as well. You are also not limited to static arguments; you could pass a variable, or even another method, or a whole object instance as the argument to buttonPressed, and that will stick around to be used later when the widget executes its command. As long as the method signature matches what the widget will call, you are fine.
Best...or maybe just different
The closure method above is useful to know, however in that situation we created a function that just returns another function. What if you really wanted the more desirable function to just go ahead and do what it says it will do? Maybe you would like to use the "buttonPressed" functionality where there is no button involved? If you tried that with the function above, you would have to do something funny like this: "buttonPressed(1).call()". That is not very desirable, so a slight better way to accomplish the exact same thing as above is to do something like this:
require 'tk' def buttonPressed(number) puts "Button #{number} pressed!" end root = TkRoot.new { title "Hello, World!" } button1 = TkButton.new(root) do text "Button 1" command proc { buttonPressed(1) } pack { padx 15 ; pady 15; side 'left' } end button2 = TkButton.new(root) do text "Button 2" command proc { buttonPressed(2) } pack { padx 15 ; pady 15; side 'left' } end button3 = TkButton.new(root) do text "Button 3" command proc { buttonPressed(3) } pack { padx 15 ; pady 15; side 'left' } end Tk.mainloop
Once again, you will look the same, and print the same output as the first "buttons" example.
Notice that now the "buttonPressed(number)" function simply prints a message, and it is actually the anonymous "proc { buttonPressed(X) }" function that is storing the parameter in its context to use later. It's probably worthwhile to point out again that Ruby/Tk is not the best GUI toolkit available for Ruby. However, this technique can generally be used with any toolkit in Ruby, or any toolkit in any dynamic language, or just any dynamic language application period.
Conclusion
Dynamic languages are very good for quick GUI prototyping, and Ruby is one shining example of that. While Qt may be a more robust GUI toolkit on its own, it seems like QtRuby has some restrictions that would make rapid prototyping slower than it really should be, since slots have to be managed in a long list, and cannot be dynamic instance methods or code blocks. wxRuby appears to be well-supported, well-documented, and easy to use for prototyping. wxRuby may even be good enough for large team usage, if not for production release software. Ruby/Tk is only suitable for individual or small team usage. Nevertheless, if Ruby is a well-understood language on a given team, any of these three Ruby toolkit implementations would be better for rapid prototyping, or for small helper applications than heavier languages like C++ and Java.
References
1. The Computer Language Benchmarks Game [23]
2. QtRuby Wikipedia [24]