About wxPerl

The Wx module lets you use the wxWidgets cross-platform GUI (Graphical User Interface) toolkit with Perl. A GUI program draws buttons and other widgets on the screen and interacts with the user via the keyboard and mouse.

How GUI Toolkits Work

Graphical widgets are drawn on the screen as pixels, so a button is just a picture taking up some area on the screen. To detect when the user clicks the "button", the computer needs to keep track of the mouse cursor location, wait for a click from the mouse device, and check the current cursor coordinates against the button's screen area. This all gets rather complicated with even a few buttons on the screen, but there are already several layers of abstraction between you and the mouse click. Thus, a typical GUI application "stacked" some distance from the actual hardware:

  1. Device Driver: read/write electrical signals (from the mouse)
  2. Operating System: manages resources (the mouse "device")
  3. Windowing System: juggles multiple "windows" (and mouse "focus")
  4. Window Manager: window placement, focus
  5. Widget Toolkit: draws widgets, juggles "events"
  6. Your Application: reacts to events

You might think of layers 2-5 as "the operating system". On a Linux desktop, the layers are more clearly visible (e.g. kernel, X, gnome, and GTK.) The widget toolkit talks to the window manager to allow the user to drag our application's window around, minimize it, etc. The window manager assigns a "drawing area" to each application. It keeps your web browser from drawing on your text document and keeps track of which window receives the keyboard and mouse input. But, once you have your own drawing area and the mouse focus, you still need to draw the button and figure out whether the mouse click is inside the button.

This is where the widget toolkit comes in. It gives you a way to draw and use widgets (buttons, scrollbars, etc.) which look and behave like the widgets in other applications. It also listens for mouse and keyboard input and triggers events in your application which react to those inputs. Using a toolkit, GUI applications rarely need to work directly with pixels, mouse clicks, or keypresses.

How wxPerl Works

The wxWidgets toolkit runs on Windows, Mac, and Linux platforms and creates applications which have a "native" look-and-feel on each platform. It does this by building on top of each platform's native widget toolkit. So, a wxPerl button on Windows looks just like a button from a native windows application.

The wxPerl code links to the wxWidgets libraries, and provides an object-oriented Perl interface to the C++ code underneath. Because of this, wxPerl code looks very similar to C++ wxWidgets code. There is no perl-specific Wx documentation, but the extensive wxWidgets documentation contains several notes about where wxPerl usage differs from that of C++.

Event-Driven Programming

Wx is an object-oriented, event-driven toolkit. Each widget is an object, and any input from the user is treated as an event. You construct the objects, connect their events to some handler code in your application, and call the toolkit's MainLoop method. The main loop waits for an event, calls the corresponding event handler, and goes back to waiting for the next event.

A simple console-based application can also be event-driven, where the mainloop is just a while loop with readline and the event handlers are just a dispatch table.

  my %handlers = (
    c => sub {say "you said c"},
    d => sub {say "you said d"},
    x => sub {say "you said x"; exit},
  );

  while(<>) {
    chomp;
    my $handler = $events{$_} or next;
    $handler->();
  }

Something similar goes on inside of the GUI toolkit's MainLoop, but with a more complicated dispatch table and multiple inputs. Note how the program resumes looping after running a handler, but cannot process further input until the handler completes.

Installing wxPerl

Installing the Wx module on your system should be as simple as:

  $ cpan Wx

Assuming that your CPAN client is correctly configured, this will download and install everything you need to run Wx. Its dependencies include the Alien::wxWidgets, which will download and compile the C++ wxWidgets toolkit for you. If you want to use an already-installed wxWidgets, you can manually build the Alien::wxWidgets against that (see the README file for instructions.)

Your system may have prebuilt wxPerl packages available (e.g. libwx-perl in Debian), though these may be somewhat older than the current version from the CPAN.

Your First wxPerl Application

Every wxPerl application must construct an object derived from Wx::App and call this object's MainLoop method. This object must define an OnInit method which instantiates the applications widgets. So, we always start with a subclass of Wx::App.

  1 #!/usr/bin/env perl
  2 
  3 use warnings;
  4 use strict;
  5 
  6 use Wx;
  7 
  8 package MyApp;
  9 
 10 use base 'Wx::App';
 11 
 12 sub OnInit {
 13   my $self = shift;
 14 
 15   my $frame = Wx::Frame->new(
 16     undef,
 17     -1,
 18     'A wxPerl Application',
 19     &Wx::wxDefaultPosition,
 20     &Wx::wxDefaultSize,
 21     &Wx::wxMAXIMIZE_BOX | &Wx::wxCLOSE_BOX
 22   );
 23 
 24   $frame->Show;
 25 }
 26 
 27 MyApp->new->MainLoop;

Wx Object Constructors

This constructs a frame and displays it on the screen. The wxFrame documentation explains the parameters in more detail. Because the API uses positional parameters, we have to pad-out the positions using the defaults (e.g. &Wx::wxDefaultPosition) to be able to set the window style. Wx provides several constants like this which correspond to the constants referenced in the wxWidgets documentation.

 15   my $frame = Wx::Frame->new(
 16     undef,                  # parent window
 17     -1,                     # window ID
 18     'A wxPerl Application', # window title
 19     &Wx::wxDefaultPosition, # window position
 20     &Wx::wxDefaultSize,     # window size
 21     &Wx::wxMAXIMIZE_BOX | &Wx::wxCLOSE_BOX   # window style
 22   );

Note how the 'style' parameter is made of two constants OR'd together with the | operator. This is common practice in C++ code (and wxPerl very closely mirrors the wxWidgets C++ API in Perl.) We'll frequently need to use combinations of these numeric constants to control objects.

If we just wanted the defaults, we could stop at the end of the required parameters.

 15   my $frame = Wx::Frame->new(
 16     undef,                  # parent window
 17     -1,                     # window ID
 18     'A wxPerl Application', # window title
 19   );

The window size parameter can be a wxSize object, but that's just an object holding two numbers. Conveniently, wxPerl allows us to use array references for this sort of thing in many places.

 15   my $frame = Wx::Frame->new(
 16     undef,                  # parent window
 17     -1,                     # window id
 18     'A wxPerl Application', # window title
 19     &Wx::wxDefaultPosition, # window position
 20     [800, 600],             # window size
 21   );

The result here is the same as if we had used Wx::Size->new(800,600) as the 'size' parameter.

Using Named Parameters

The wxPerl::Constructors module provides an alternate set of constructors for Wx::foo objects. This eliminates some of the argument-padding by using names for the optional parameters. If you install it (with `cpan wxPerl::Constructors`), our example becomes a bit simpler. The wxPerl::Frame->new method assumes that 'id' is -1 (i.e. just let Wx generate a new id) if it is not specified, so we only need the 'parent' and 'title' parameters here, plus whatever options.

  1 #!/usr/bin/env perl
  2 
  3 use warnings;
  4 use strict;
  5 
  6 use Wx;
  7 use wxPerl::Constructors;
  8 
  9 package MyApp;
 10 
 11 use base 'Wx::App';
 12 
 13 sub OnInit {
 14   my $self = shift;
 15 
 16   my $frame = wxPerl::Frame->new(undef, 'A wxPerl Application',
 17     style => &Wx::wxMAXIMIZE_BOX | &Wx::wxCLOSE_BOX
 18   );
 19 
 20   $frame->Show;
 21 }
 22 
 23 MyApp->new->MainLoop;

Or, just setting a size:

 16   my $frame = wxPerl::Frame->new(undef, 'A wxPerl Application',
 17     size => [800,600]
 18   );

Or, with just the defaults:

 16   my $frame = wxPerl::Frame->new(undef, 'A wxPerl Application');

Adding Widgets

Now we know how to create a frame (the top-level window), but we need to put some widgets on it to get a useful application. Every widget within the frame is going to be a child of the frame (or a child of a child, etc.) This allows the toolkit to keep track of every widget in terms of its top-level ancestor widget (the frame.)

  1 #!/usr/bin/env perl
  2 
  3 use warnings;
  4 use strict;
  5 
  6 use Wx;
  7 use wxPerl::Constructors;
  8 
  9 package MyApp;
 10 
 11 use base 'Wx::App';
 12 
 13 sub OnInit {
 14   my $self = shift;
 15 
 16   my $frame = wxPerl::Frame->new(undef, 'A wxPerl Application',
 17     style => &Wx::wxMAXIMIZE_BOX | &Wx::wxCLOSE_BOX
 18   );
 19 
 20   my $button = wxPerl::Button->new($frame, 'Click Me');
 21 
 22   $frame->Show;
 23 }
 24 
 25 MyApp->new->MainLoop;

Now we have a button, but it does not do anything except give visual feedback about being clicked (that is a builtin behavior.) To make it do our bidding, we need to hook a handler to the button's BUTTON event.

 20   Wx::Event::EVT_BUTTON($button, -1, sub {
 21     my ($b, $evt) = @_;
 22     $b->SetLabel('Clicked');
 23     $b->Disable;
 24   });

The wxButton documentation lists all of the events for that widget. In this case, there is only EVT_BUTTON(id, func). Note that the event usage listed in the C++ documentation is slightly different than the wxPerl usage: the first argument to an event mapping in perl is always the object ($button). So, you have to mentally transform the C++ usage to perl as: EVT_BUTTON($object, $id, $subref).

Now let's try our application with the button event connected:

  1 #!/usr/bin/env perl
  2 
  3 use warnings;
  4 use strict;
  5 
  6 use Wx;
  7 use wxPerl::Constructors;
  8 
  9 package MyApp;
 10 
 11 use base 'Wx::App';
 12 
 13 sub OnInit {
 14   my $self = shift;
 15 
 16   my $frame = wxPerl::Frame->new(undef, 'A wxPerl Application');
 17   $frame->SetMinSize([120,40]);
 18 
 19   my $button = wxPerl::Button->new($frame, 'Click Me');
 20   Wx::Event::EVT_BUTTON($button, -1, sub {
 21     my ($b, $evt) = @_;
 22     $b->SetLabel('Clicked');
 23     $b->Disable;
 24   });
 25 
 26   $frame->Show;
 27 }
 28 
 29 MyApp->new->MainLoop;

Note that the event handler (our anonymous subref) is called with the arguments $object, $event. In this case, $event does not contain anything of interest, though it will for other sorts of events.

Sizers

If you try to add another button to the frame in this example, it gets drawn on top of the first button and makes a mess of the layout. To organize the layout, wxWidgets uses sizers, which are stretchable containers that tell the toolkit how to layout a group of widgets and how to behave when the window is resized.

To put our new button below the other, we can use a wxBoxSizer with the wxVERTICAL orientation.

 18   my $sizer = Wx::BoxSizer->new(&Wx::wxVERTICAL);

Then, we need to add each button to the sizer as it is created. The 'proportion' argument to Add determines how the available space is split between each widget. In this case, the first button will be twice as big as the second.

 20   my $button = wxPerl::Button->new($frame, 'Click Me');
 21   $sizer->Add($button, 2, &Wx::wxEXPAND);
 22   my $button2 = wxPerl::Button->new($frame, 'DO NOT CLICK');
 23   $sizer->Add($button2, 1, &Wx::wxEXPAND);

Aside: You might have noticed that the Add method is not listed in the wxBoxSizer documentation. But, wxSizer is the first link in the 'Derived from' section at the top of the page. This means that anything not listed in the wxBoxSizer documentation is implemented (and documented) by its parent class. You'll frequently need to navigate up through the inheritance like this to find all of the methods for a given widget.

Finally, we need to connect this sizer to the frame.

 33   $frame->SetSizer($sizer);
 34   $frame->Show;

This adjusts the sizer whenever the frame changes. Putting this all together with an event handler hooked to our new button gives:

  1 #!/usr/bin/env perl
  2 
  3 use warnings;
  4 use strict;
  5 
  6 use Wx;
  7 use wxPerl::Constructors;
  8 
  9 package MyApp;
 10 
 11 use base 'Wx::App';
 12 
 13 sub OnInit {
 14   my $self = shift;
 15 
 16   my $frame = wxPerl::Frame->new(undef, 'A wxPerl Application');
 17   $frame->SetMinSize([120,80]);
 18   my $sizer = Wx::BoxSizer->new(&Wx::wxVERTICAL);
 19 
 20   my $button = wxPerl::Button->new($frame, 'Click Me');
 21   $sizer->Add($button, 2, &Wx::wxEXPAND);
 22   my $button2 = wxPerl::Button->new($frame, 'DO NOT CLICK');
 23   $sizer->Add($button2, 1, &Wx::wxEXPAND);
 24   Wx::Event::EVT_BUTTON($button, -1, sub {
 25     my ($b, $evt) = @_;
 26     $b->SetLabel('Clicked');
 27     $b->Disable;
 28   });
 29   Wx::Event::EVT_BUTTON($button2, -1, sub {
 30     &Wx::wxTheApp->ExitMainLoop;
 31   });
 32 
 33   $frame->SetSizer($sizer);
 34   $frame->Show;
 35 }
 36 
 37 MyApp->new->MainLoop;

Try resizing the window and see what happens. Experiment with the 'proportion' and 'flags' parameters to $sizer->Add to get a feel for how they change the layout.

Note that the code in OnInit is getting somewhat long and mostly involves children of the wxFrame object. To keep things manageable, you would typically write a MyMainFrame.pm and subclass Wx::Frame. This would allow you to reduce the code in OnInit to simply:

 16   my $frame = MyMainFrame->new(undef, 'A wxPerl Application');
 17   $frame->init;
 18   $frame->Show;

Then, you need to implement the init method in your MyMainFrame package and put the code to layout widgets and connect events in there. Whenever you are constructing a widget with some complex children, consider subclassing the widget and organizing your code into maintainable components.

GUI Builders

Once you know how to create widgets, connect them to events and use sizers to organize them, all you have to do is build up from there. You should find your way around the wxWidgets documentation and learn how the documented C++ API differs from the wxPerl API. Then, you can simply code-in all of your widgets, but this can lead to a lot of Perl code just to create and layout the widgets.

GUI builder applications like wxGlade or XRCed allow you to graphically layout and preview your application. With XRCed, you generate an XRC xml document and need to add code to your application to load-in the widget definitions and connect them to event handlers. With wxGlade, you can generate XRC or Perl code (but you still need to setup the event handlers.)

The graphical GUI builders are great for seeing what widgets are available and prototyping even if you do not directly use their outputs in your application. You may find the generated code mechanism to be difficult to maintain (this is what led me to create FreeTUIT as an alternative way to organize widget construction and layout.)

Further Reading

The Wx::Demo distribution contains several demo programs which show what widgets are available and how to use them. Browsing the wxWidgets alphabetical class reference is also a good way to learn about the widgets.

For perl-specific aspects, see the wxPerl home page.