I like WxPerl (but haven't used any other Perl GUI to compare). Whenever I start a new project, I often cut and paste a large portion of another project, to set up the initial window, set up the menu bar, tool bar, status bar, etc. I thought it would be nice to have a module to do all the basics, so that you could immediately get to the programming part.
With that in mind I have written a module WxSimple. The main window and all the sub-windows, menus, toolbar, and other gadgets are to be created in an XML resource file, using any XRC editor. The simplest program is just two lines, which displays the window, and nothing else.
To add functionality, you simple create a hash consisting of menuitem names associated with a coderef to the handler. Then simply define all the handler subs and voila, you have a working program. Even then I have added some file open/save support, which can be ignored if not needed.
I have tried to keep it absolutely minimal. I was tempted to throw in basic document-view functionality, but resisted in order to get to the common denominator.
Since I have never written modules other than for myself, I am sure it needs work. I just read the tutorials about writing modules today, and see that there is much that I need to do. So I will be improving it stylistically, but my request was for more functional comments.
Am I supposed to put the code here? Currently you can see a fully runnable sample, along with documentation on b4swine's scratchpad.
Update: I have pasted the code right here
WxSimple Tutorial
This is a tutorial for WxSimple, a package I wrote to make
throwaway WxPerl programs using XRC.
GUI programming always looks rather bulky. This package
makes the code much smaller, making it easy to write throwaway, one-time programs. This package is very useful for the absolute beginner as well as for experienced WxPerlers.
For beginners, the advantage of using XRC, is that you can use a GUI to create you menus, toolbar, and your buttons windows, panels, etc, along with sizers, and can test them live. This frees you up to concentrate on programming. Later on, when dynamic objects are needed, you can learn the internals of sizers and creation/deletion of windows at your own pace.
Getting Started
To get started, you need to get some components together,
becuase unfortuantely, Wx is not a standard package.
Since I use ActivePerl for MS Windows, I have provided complete instructions for that. For *nix users it should be easier. Use ppm to download the Wx package. Unfortunately, most repositories have very ancient binaries for WxPerl, so to get the latest version, you will need to add the repositories described in http://www.wxperl.co.uk/wxppm/.
You also need to get some kind of a XML resource compiler. I like XRCed (because it is the only one I have used), but it requires you to install Python as well. You need to download and install python (about 10 megs) from
http://www.python.org/download/, and then download and install WxPython (about 5 megs) from http://www.wxpython.org/download.php
You are now ready to use the XRCed program included with WxPython. If you open the file WxSample.xrc (file located at the end), in XRCed, you can explore how this is structured.
Sample Program: For those than know WxPerl
For those who are already familiar with WxPerl, a sample program illustrating the use of
WxSimple is provided here. It will need two other supporting files namely the package
WxSimple.pm and the resource file
WxSample.xrc that was created using
XRCed. Both of these files are given in the last section.
Here is the code for WxSample.pl
use strict;
use warnings;
use WxSimple qw(FindWindowByXid MsgBox $xrc $sbar $OpenFile $frame);
#
# set up parameters
#
$xrc = 'WxSample.xrc'; # location of xrc file
$sbar = 2; # status bar with 2 sections
$OpenFile = \&OpenFile; # funtion that opens a file
#
# create an app
#
my $app = WxSimple->new(); # create app and main frame
#
# now, find controls inside the main window
# and set up event handlers for them.
#
my ($checkbox, $id) = FindWindowByXid('CheckBox');
Wx::Event::EVT_CHECKBOX($frame, $id, \&OnCheck );
my $smalltext = FindWindowByXid('SmallText');
#
# start the main loop
#
$app->MainLoop();
#
# -------------------------------------
# here are the event handlers
# run the program. Open a file. Check the checkbox
#
sub OpenFile
{ my $file = shift;
local $/;
my $textbox = FindWindowByXid('Text');
open F, '<', $file;
$textbox->ChangeValue(<F>);
close F;
MsgBox('The file is loaded in the text box');
}
sub OnCheck
{ my ($this, $evt) = @_;
if ($evt->IsChecked()) { $smalltext->ChangeValue('checked'); }
else { $smalltext->ChangeValue('unchecked'); }
}
A Typical Sequence
We go through a typical sequence needed to create a program using WxSimple.
1. Use XRCed to create Menu and a Window Layout
First a resource file is created using
XRCed. In this , there is a main frame, and inside the frame you may have a menu and possibly a toolbar as well. You can also put any other containers, buttons, sliders, etc. as you like. Each object needs an XMLid, which is just a name, if you are going to be referring to it in the Perl program. For example, you usually do not need to give an ID for StaticText, because there is no need to refer to it. Finally save this as an
xrc file.
There are a set of default XMLid's for many of the standard menu items (you can find them in the index under "Window Identifiers"). there is an advantage in using the standard names although you can .
2. Set Parameters
A typical programs first sets up parameters. This is done by
use WxSimple qw($xrc $frmID $sbar %menu $OpenFile $SaveFile $Close
+Win $icon);
$xrc refers to the path to the file that was created by XRCed.
$frmID is the XMLid that was given to the main frame created in the xrc file.
$sbar is the number of panels in the status bar at the bottom of the window. If this is zero, no status bar is created.
%menu is a hash with the XMLids of the menu items as keys, and coderefs to subs that will be called as values. Four items in this hash are predefined, but may be overwritten. File--Open, File--Save, File--SaveAs, have predefined handlers which offer a file dialog and get a file name, after which the call the code referenced by &$OpenFile or &$SaveFile, as appropriate. You should make an entry for every one of the menu items that you want to be active. You can override the predefined items as well, by redefining them.
$OpenFile and $SaveFile are described above in %menu.
&$CloseWin is the routine called when the window is closed. It should Destroy all top level windows. If you create other top level windows, you should redefine this.
$icon is a default WxPerl icon, which you can change as you like.
3. Create an App
Creating a WxSimple app by
my $app = WxSimple->new() .
4. Add more Handlers
After that, the variables
$WxSimple::frame and
$WxSimple::xr get set to the top-level frame and the XML resource, respectively. Any other item within the frame can be located using its XML id, with
my ($window, $id) = WxSimple::FindWindowByXid('xmlid')
The
$id is a numeric ID that is associated with that control or window. It is needed if you want to respond to other events from that control.
5. Define Handler Routines
If you are going to be using files, you need to define $WxSimple::FileOpen, or $WxSimple::FileSave, as appropriate. Initially these are set to dummy routines, but you can refer them to your own routine, as illustrated above for FileOpen.
You may now define routines for each of the menu items, as well as other event handlers you have defined.
For those learning WxPerl
This module is actually a useful way to learn WxPerl, even for a beginner. You can get a very fast start, and then learn the hard way of doing things at your own pace. It is useful to be aware of the resources available to learn WxPerl.
Manual
A
manual intended for
C programmers, with one-line notes for Perl and Python is available. This is not up to date, but this is all there is.
Tutorials
There are a nice set of
tutorials here.
Samples
Along with WxPerl, two kinds of sample programs are provided. One is a very large sample program called
demo.pl which illustrates many of the various controls, in one integrated program. The other is directory
samples which contains a set of small independent programs, each of which illustrates one control.
Wiki
A
wiki devoted to wxperl, is a bit undermaintained, and out of date, but you can find many things here.
Groups
There are some groups also, where you can ask questions and get answers, but none as nice as this.
Supporting Code
In order to run, this needs two additional support files,
WxSimple.pm and
WxSample.xrc. The first is the package, while the second is an xml file that was created (and should be edited) using XRCed. The files are provided below. If you put all three in a single directory, you can run
WxSample.pl.
package WxSimple; use base qw(Wx::App Exporter);
use strict;
use Exporter;
our $VERSION = 0.10;
our @EXPORT_OK = qw(StartApp FindWindowByXid MsgBox $frame $xr
$xrc $frmID $sbar %menu $OpenFile $SaveFile $CloseWin $icon);
use Wx;
use Carp;
our $frame;
our $xr;
our $xrc = 'res/res.xrc'; # location of resource file
our $frmID = 'Frame'; # XML ID of the main frame
our $sbar = 0; # number of status bar sections
our %menu = # menu handlers,
# you can replace them with your own
# or add your own menu items
( wxID_OPEN => \&Open, # these are four readymade handl
+ers
wxID_SAVE => \&Save, # that just get a file-name for r
+eading
wxID_SAVEAS => \&SaveAs,# or writing
wxID_EXIT => \&Exit, # and quit just exits.
);
our $OpenFile = \&OpenFile; # A routine to read data from a file
our $SaveFile = \&SaveFile; # A routine to write data to a file
our $CloseWin = \&CloseWin; # this is not a menu option
# it is the routine called before the end
# it needs to Destroy() all top level dialogs
our $icon = Wx::GetWxPerlIcon();
my $file; # the name of the file used in Open/Save
sub StartApp
{ WxSimple->new()->MainLoop();
}
sub OnInit
{ my $app = shift;
#
# Load XML Resources
#
use Wx::XRC;
$xr = Wx::XmlResource->new();
$xr->InitAllHandlers();
croak "No Resource file $xrc" unless -e $xrc;
$xr->Load( $xrc );
#
# Load the main frame from resources
#
$frame = 'Wx::Frame'->new;
croak "No Frame named $frmID in $xrc" unless
$xr->LoadFrame($frame, undef, $frmID);
if ($sbar)
{ $frame->CreateStatusBar( $sbar );
$frame->SetStatusWidths(-1,200);
}
$frame->SetIcon( $icon );
#
# Set event handlers
#
use Wx::Event qw(EVT_MENU EVT_CLOSE);
while (my ($xrcid, $handler) = each %menu)
{ my $id = Wx::XmlResource::GetXRCID($xrcid, -2);
if ($id == -2)
{ carp "No MenuItem named $xrcid";
next;
}
EVT_MENU( $frame, $id, $handler );
}
EVT_CLOSE( $frame, $CloseWin );
#
# Show the window
#
$app->SetTopWindow( $frame );
$frame -> Show(1);
1;
}
sub FindWindowByXid
{ my $id = Wx::XmlResource::GetXRCID($_[0], -2);
return undef if $id == -2;
my $win = Wx::Window::FindWindowById($id, $frame);
return wantarray ? ($win, $id) : $win;
}
sub MsgBox
{ use Wx qw (wxOK wxICON_EXCLAMATION);
my @args = @_;
$args[1] = 'Message' unless defined $args[1];
$args[2] = wxOK | wxICON_EXCLAMATION unless defined $args[2];
my $md = Wx::MessageDialog->new($frame, @args);
$md->ShowModal();
}
sub Open
{ use Wx qw (wxID_CANCEL wxFD_FILE_MUST_EXIST);
my $dlg = Wx::FileDialog->new($frame,
'Select one or more Files', '', '',
'Text Files|*.txt|All Files|*.*',
wxFD_FILE_MUST_EXIST);
if ($dlg->ShowModal() == wxID_CANCEL) { return }
$file = $dlg->GetPath();
$frame->SetStatusText("Opening...$file", 0);
my $busy = Wx::BusyCursor->new();
$OpenFile->($file);
$frame->SetStatusText("Opening...$file...Done", 0);
}
sub Save
{ $frame->SetStatusText("Saving...$file", 0);
my $busy = Wx::BusyCursor->new();
$SaveFile->($file);
$frame->SetStatusText("Saving...$file...Done", 0);
}
sub SaveAs
{ use Wx qw (wxID_CANCEL wxFD_OVERWRITE_PROMPT wxFD_SAVE);
my $dlg = Wx::FileDialog->new($frame,
'Select one or more Files', '', '',
'Text Files|*.txt|All Files|*.*',
wxFD_OVERWRITE_PROMPT | wxFD_SAVE);
if ($dlg->ShowModal() == wxID_CANCEL) { return }
$file = $dlg->GetPath();
Save();
}
sub OpenFile
{ MsgBox "Add Code to Open $file";
}
sub SaveFile
{ MsgBox "Add Code to Save $file";
}
sub Exit
{ CloseWin();
}
#
# Close is not called by the menu, but is called to close all wind
+ows
# If there are any toplevel dialogs, close them here, otherwise th
+e
# program will not exit.
#
sub CloseWin
{ $frame->Destroy();
}
1;
__END__
=head1 NAME
WxSimple: A package to make throwaway WxPerl programs using C<XRC>
=head1 SYNOPSIS
use WxSimple;
# one-liner (use all default options)
WxSimple::StartApp();
...
# with modified defaults
use WxSimple qw(qw(StartApp FindWindowByXid MsgBox $frame $xr
$xrc $frmID $sbar %menu $OpenFile $SaveFile $CloseWin $icon);
$xrc = 'res/res.xrc';
$frmID = 'MainFrame';
$sbar = 1;
$menu{OpenFile} = \&::Open;
$menu{Options} = \&::OnOptions;
...
my $app = WxSimple->new();
...
$app->MainLoop();
=head1 DESCRIPTION
GUI programming always looks rather bulky. This package makes the
code much smaller, making it easy to write throwaway, one-time use
WxPerl programs. The windows, menus, toolbars are all defined usin
+g
C<XRC>.
To simplify menu event handling, just define the hash C<%WxSimple:
+:menu>
with keys that are the XML ID's for each menuitem, and values that
+ are
references to the handlers for that menuitem.
The menu events for C<wxID_OPEN>, C<wxID_SAVE>, C<wxID_SAVEAS> and
+
C<wxID_EXIT> have default definitions. The first three events get
+a
file name (using a file dialog for Open and Save As) and then call
+
the coderef pointed to by $FileOpen, or $FileSave, with that file
+name
as the argument. Quit will close the window and exit. You can over
+ride
any of these definitions, by redefining C<%WxSimple::menu>.
The XML ID for the main frame is given in C<$WxSimple::frmID>.
The number of sections in the status bar is given by C<$WxSimple::
+sbar>.
If this is C<0>, no status bar is created.
The name of the XML resource file generated by XRC is set in
C<$WxSimple::xrc>.
The variables C<$WxSimple::frame> and C<$WxSimple::xr> contain the
+ main
frame and the XML resource, once C<new> has been called
=head2 Structure
The main program usually looks like a C<use WxSimple> statement, f
+ollowed
by setting a few parameters. Then the C<%WxSimple::menu> hash usua
+lly has
to be defined, providing a handler for every menu event.
This is followed by creating a C<$app> using a call to C<WxSimple-
+>new()>.
At this point the variables C<$WxSimple::frame> and C<$WxSimple::x
+r> are
created. If further things need to be added to the frame, this is
+the time
to do that.
Finally a call to C<MainLoop()> starts the window loop.
Here is
WxSample.xrc
<?xml version="1.0" ?>
<resource>
<object class="wxFrame" name="Frame">
<title>DataProg</title>
<object class="wxToolBar" name="XRCtbar">
<bitmapsize>32</bitmapsize>
<size>32,32</size>
<object class="tool" name="wxID_NEW">
<bitmap stock_id="wxART_NEW"></bitmap>
<tooltip>New</tooltip>
<longhelp>Start a new file</longhelp>
</object>
<object class="tool" name="wxID_OPEN">
<bitmap stock_id="wxART_FILE_OPEN"></bitmap>
<tooltip>Open</tooltip>
<longhelp>Open a File</longhelp>
</object>
<object class="tool" name="wxID_SAVE">
<bitmap stock_id="wxART_FILE_SAVE"></bitmap>
<tooltip>Save currently opened File</tooltip>
<longhelp></longhelp>
</object>
<object class="tool" name="wxID_SAVEAS">
<bitmap stock_id="wxART_FILE_SAVE_AS"></bitmap>
<tooltip>Save File under a different name</tooltip>
</object>
<object class="tool" name="wxID_EXIT">
<bitmap stock_id="wxART_QUIT"></bitmap>
<tooltip>Exit Program</tooltip>
<longhelp>Exit Program</longhelp>
</object>
<object class="separator"/>
<object class="tool" name="wxID_CUT">
<bitmap stock_id="wxART_CUT"></bitmap>
</object>
<object class="tool" name="wxID_COPY">
<bitmap stock_id="wxART_COPY"></bitmap>
</object>
<object class="tool" name="wxID_PASTE">
<bitmap stock_id="wxART_PASTE"></bitmap>
</object>
<object class="tool" name="wxID_DELETE">
<bitmap stock_id="wxART_DELETE"></bitmap>
</object>
<object class="separator"/>
<object class="tool" name="wxID_HELP">
<bitmap stock_id="wxART_HELP"></bitmap>
<tooltip>Help</tooltip>
<longhelp>Usage Instructions</longhelp>
</object>
<object class="tool" name="wxID_ABOUT">
<bitmap stock_id="wxART_INFORMATION"></bitmap>
<tooltip>About</tooltip>
<longhelp>Information about this program</longhelp>
</object>
</object>
<object class="wxMenuBar" name="XRCmenu">
<object class="wxMenu" name="menuFile">
<label>File</label>
<object class="wxMenuItem" name="wxID_NEW">
<label>New</label>
<bitmap stock_id="wxART_NEW"></bitmap>
<accel>Ctrl-N</accel>
<help>Start a new file</help>
</object>
<object class="wxMenuItem" name="wxID_OPEN">
<label>Open</label>
<bitmap stock_id="wxART_FILE_OPEN"></bitmap>
<accel>Ctrl-O</accel>
<help>Open a File</help>
</object>
<object class="wxMenuItem" name="wxID_REVERT">
<label>Revert to Saved</label>
</object>
<object class="wxMenuItem" name="wxID_SAVE">
<label>Save</label>
<bitmap stock_id="wxART_FILE_SAVE"></bitmap>
<accel>ctrl-S</accel>
<help>Save currently opened File</help>
</object>
<object class="wxMenuItem" name="wxID_SAVEAS">
<label>Save As</label>
<bitmap stock_id="wxART_FILE_SAVE"></bitmap>
<accel>ctrl-S</accel>
<help>Save File under a different name</help>
</object>
<object class="wxMenuItem" name="wxID_EXIT">
<label>Exit</label>
<bitmap stock_id="wxART_QUIT"></bitmap>
<accel>Ctrl-X</accel>
<help>Exit Program</help>
</object>
</object>
<object class="wxMenu" name="menuEdit">
<label>Edit</label>
<object class="wxMenuItem" name="wxID_CUT">
<label>Cut</label>
<bitmap stock_id="wxART_CUT"></bitmap>
</object>
<object class="wxMenuItem" name="wxID_COPY">
<label>Copy</label>
<bitmap stock_id="wxART_COPY"></bitmap>
</object>
<object class="wxMenuItem" name="wxID_PASTE">
<label>Paste</label>
<bitmap stock_id="wxART_PASTE"></bitmap>
</object>
<object class="wxMenuItem" name="wxID_DELETE">
<label>Delete</label>
<bitmap stock_id="wxART_DELETE"></bitmap>
</object>
</object>
<object class="wxMenu" name="menuHelp">
<label>Help</label>
<object class="wxMenuItem" name="wxID_HELP">
<label>Help</label>
<bitmap stock_id="wxART_HELP"></bitmap>
<accel>F1</accel>
<help>Usage instructions</help>
</object>
<object class="wxMenuItem" name="wxID_ABOUT">
<label>About</label>
<bitmap stock_id="wxART_INFORMATION"></bitmap>
<help>Information about this program</help>
</object>
</object>
</object>
<object class="wxSplitterWindow">
<object class="wxTextCtrl" name="Text">
<style>wxTE_MULTILINE</style>
</object>
<object class="wxPanel">
<object class="wxBoxSizer">
<orient>wxVERTICAL</orient>
<object class="sizeritem">
<object class="wxCheckBox" name="CheckBox">
<label>A little checkbox</label>
</object>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="SmallText"/>
</object>
</object>
</object>
<orientation>vertical</orientation>
<sashpos>100</sashpos>
</object>
<style></style>
</object>
</resource>