You can check out a portuguese version of this post here
So you want to start out playing with GTK and Ruby but don’t know how to start? Fear not, we’re here to help you. There are a lot of examples of “Hello, World!†applications with Ruby/GTK. Well, both Ruby and GTK are great choices for building desktop applications. The first one is a dynamic language with amazing features like closures, and of course, with an extensive built-in library. The second one is a widely used GUI framework, with lots of helpful widgets and also a great library for printing, displaying videos, playing sounds, etc. Combine these two elements and you have Ruby/GTK, a great framework… uhmmm… framework?
Let me ask you a few questions. Suppose you’re starting a new desktop application in Ruby/GTK, a really big one, with lots of use cases and small details. How would you layout your directory structure? What if you needed to customize the style of a button, or change the background color of a TextView? What if your application is a kind of server, and you want some message displayed every time an user connects to it? What if you wanted to change the status bar message, and also add an icon for the client in an IconView?
Of course you could do this yourself… Or you could use RuGUI which gives you:
- A MVC approach to desktop applications
- A ready to use directory layout similar to what you’d see on a Ruby on Rails application
- An easier way of customizing widgets than in normal Ruby/GTK applications
- An implementation of the Observer pattern
- ActiveSupport—hey, you could say that this is a requirement/dependency, but I think this is more like a feature since you’ll end up having a better ruby API with ActiveSupport
Let’s see it in action with a simple Hello World application.
Generating your application skeleton
RuGUI already creates this Hello World application for us. In fact this is the starting point for every application you’ll ever make with RuGUI. Once it is installed as a gem go to a directory of your choice and type in a terminal:
rugui helloworld
This will generate an application in {YOUR_DIRECTORY}/helloworld. The directory structure will be like this:
To run the application, enter the helloworld directory and type:
./bin/helloworld
You could also have typed:
rake run
Or even, if you must:
ruby app/main.rb
The application should look like this:
If you click in the button you should see the top label changing. How did this all worked?
Hello World step by step
Let’s analyse the application step by step:
When you run the application, in any way (all three ways of starting the application end up doing the same thing), the app/main.rb script gets called. Here are the content of that file:
#! /usr/bin/ruby# # You can run this application by running this file. # Do not modify it, unless you know what you are doing. require File.join(File.dirname(__FILE__), '..', 'config', 'environment') main_controller = MainController.new main_controller.run
As you can see, the first thing we do is to load the config/environment.rb file. This will initialize the RuGUI application with the default environment—development. You could’ve specified a different environment by setting the RUGUI_ENV environment variable. This initialization process is very similar to the Rails initialization process, it was mainly copied and adapted from it.
Next we instantiate the MainController
and call its method run
. Simple isn’t?
Now to the MainController. What is it again? Unlike web applications, all desktop applications have a concept of a main loop. Basically, the application is set up before it is actually displayed. It then enters an infinite loop, which is responsible for displaying widgets and firing events as they happen (button clicks, keys pressed, network data, printing operations, etc). These events may alter the widgets visual, change data, or any other thing you might want them to do.
So RuGUI’s MainController is the actual programming starting point of your application. Something like the root of your application. Better yet, all other controllers are children of this MainController, or children of controllers which are themselves children of this MainController. This is getting a little complicated, but in time you will get it.
In this example we only have one controller, and it is the MainController. We didn’t create another controller for simplicity. Here is its code:
class MainController < RuGUI::BaseMainController # Add your stuff here.  def setup_views  register_view :main_view  end def on_main_window_delete_event(widget, event)  quit  end end
It set up the main view and add a handler for the main window delete event, which is used to quit the application. This event handler is configured in the main_view.glade file. Every controller can observe events configured for widgets in glade files of all views registered for it.
The main view uses glade file to simplify the creation an positioning of widgets. We could’ve created the widgets by hand, but we would’ve lost many features like the automatic connection of event handlers, and we would have to write a lot more code than we did:
class MainView < ApplicationView  use_glade # Add your stuff here. def on_hello_button_clicked(widget)  puts "Hello button clicked."  self.message_label.text = "You clicked me!"  end end
Here we’ve put an event handler for the clicked event of the hello_button widget. It prints a message in the console and change the text of the message_label widget. A few notes here:
- All named widgets created in glade files have read accessors for them.
- Since we are using default naming and directory conventions we didn’t need to specify the builder file for the view, but we could have done it if we wanted. It was merely deducted from the class name (MainView => main_view)
- The view itself is also an observer for event handlers of its widgets. The clicked event handler for the hello_button could have been declared in the controller, but we’ve decided to declare it here to show this.
This is basically all there is to it!
But hey, I can do this with fewer lines!
Of course you can, here it is:
#! /usr/bin/ruby require 'libglade2' class MainViewGlade include GetText attr :glade def initialize(path_or_data, root = nil, domain = nil, localedir = nil, flag = GladeXML::FILE)  bindtextdomain(domain, localedir, nil, "UTF-8")  @glade = GladeXML.new(path_or_data, root, domain, localedir, flag) {|handler| method(handler)}  end def on_main_window_delete_event(widget, arg0)  Gtk.main_quit  end def on_hello_button_clicked(widget)  puts "Hello button clicked."  @glade['message_label'].text = "You clicked me!"  end end # Main program if __FILE__ == $0  # Set values as your own application.  PROG_PATH = "main_view.glade"  PROG_NAME = "Hello World!"  MainViewGlade.new(PROG_PATH, nil, PROG_NAME)  Gtk.main end
But then, you will surely get a big headache when you need to add more functionality to the application. RuGUI tries to help you by letting you separate your application in well defined layers, each with his own responsabilities. This way its easier to make your application evolve without having maintainence problems.
Getting excited? Try it out now!
{ 3 } Comments
I’m excited with the project…
I’m waiting for more.
Thank you.
I just discovered RuGUI and I love the rails concept. I’m checking it out for my desktop app right now! I’m looking forward to a robust RuGUI community. Are there anymore tutorials to come?
Hi!
I’m wondering why I can’t run the helloworld example. I get the following error:
$ ./helloworld
(eval):1: warning: redefining `object_id’ may cause serious problems
(eval):1: warning: redefining `__send__’ may cause serious problems
/var/lib/gems/1.9.1/gems/rugui-1.6.0/lib/rugui/property_changed_support.rb:49:in `included’: undefined method `class_inheritable_accessor’ for RuGUI::BaseController:Class (NoMethodError)
from /var/lib/gems/1.9.1/gems/rugui-1.6.0/lib/rugui/property_observer.rb:37:in `include’
from /var/lib/gems/1.9.1/gems/rugui-1.6.0/lib/rugui/property_observer.rb:37:in `included’
from /var/lib/gems/1.9.1/gems/rugui-1.6.0/lib/rugui/base_controller.rb:6:in `include’
from /var/lib/gems/1.9.1/gems/rugui-1.6.0/lib/rugui/base_controller.rb:6:in `’
from /var/lib/gems/1.9.1/gems/rugui-1.6.0/lib/rugui/base_controller.rb:5:in `’
from /var/lib/gems/1.9.1/gems/rugui-1.6.0/lib/rugui/base_controller.rb:1:in `’
from :29:in `require’
from :29:in `require’
from /var/lib/gems/1.9.1/gems/activesupport-3.2.1/lib/active_support/dependencies.rb:251:in `block in require’
from /var/lib/gems/1.9.1/gems/activesupport-3.2.1/lib/active_support/dependencies.rb:236:in `load_dependency’
from /var/lib/gems/1.9.1/gems/activesupport-3.2.1/lib/active_support/dependencies.rb:251:in `require’
from /var/lib/gems/1.9.1/gems/rugui-1.6.0/lib/rugui.rb:38:in `’
from :33:in `require’
from :33:in `rescue in require’
from :29:in `require’
from /home/ewaseem/myProjects/myRuby/helloworld/config/boot.rb:59:in `load_rugui’
from /home/ewaseem/myProjects/myRuby/helloworld/config/boot.rb:54:in `load_initializer’
from /home/ewaseem/myProjects/myRuby/helloworld/config/boot.rb:40:in `run’
from /home/ewaseem/myProjects/myRuby/helloworld/config/boot.rb:13:in `boot!’
from /home/ewaseem/myProjects/myRuby/helloworld/config/boot.rb:81:in `’
from :29:in `require’
from :29:in `require’
from /home/ewaseem/myProjects/myRuby/helloworld/config/environment.rb:5:in `’
from :29:in `require’
from :29:in `require’
from /home/ewaseem/myProjects/myRuby/helloworld/app/main.rb:8:in `’
from ./helloworld:4:in `load’
from ./helloworld:4:in `’
I appreciate an hints to get it work.
/Waseem
{ 3 } Trackbacks
[...] Este post é a tradução do post intitulado Hello RuGUI! [...]
[...] you have already read this article you may wonder: “Ok, I can do a Hello World application with some new framework, so [...]
[...] to improve productivity, favoring convention over configuration, DRY and MVC patterns. « Hello RuGUI! Developing desktop applications using Ruby/GTK Getting your feet wet with RuGUI [...]
Post a Comment