Channels ▼
RSS

Web Development

Building GUIs with Win32::GUI::XMLBuilder


November, 2004: Building GUIs with Win32::GUI::XMLBuilder

Blair is head of software development for Odey Asset Management, a London-based hedge fund. He can be reached at bsdz@numeninest.com.


I wrote Win32::GUI::XMLBuilder as a way to build simple Win32 GUI interfaces in Perl while keeping the visual design separate from the inner workings of the code. Win32::GUI::XMLBuilder makes it easy to add a Win32 user interface to whatever script or module you're writing. In this article, I will try to describe how to build a simple Win32::GUI::XMLBuilder application and also provide a simple template for doing this.

What Is It?

Win32::GUI::XMLBuilder (WGX) parses well-formed valid XML containing elements and attributes that help to describe and construct a Win32::GUI object. If you're unfamiliar with XML, I suggest you read the great introductory tutorial at http://www .w3schools.com/xml/default.asp.

Here is one way to use WGX:

use Win32::GUI::XMLBuilder;
my $gui = Win32::GUI::XMLBuilder->new(*DATA);
Win32::GUI::Dialog;
__END__
<GUI>
  <Window height='60'>
    <Button text='Push me!' onClick='sub 
                {$_[0]->Text("Thanks")}'/>
  </Window>
</GUI>

Or equivalently using two files:

# button.pl
#
use Win32::GUI::XMLBuilder;
my $gui = Win32::GUI::XMLBuilder->new({file=>
                               "button.xml"});
Win32::GUI::Dialog;

# button.xml
#
<GUI>
  <Window height='60'>
    <Button text='Push me!' onClick='sub 
                {$_[0]->Text("Thanks")}'/>
  </Window>
</GUI>

This will produce something like Figure 1, which changes to the dialog in Figure 2 when pressed.

Installing a Windows File Type Handler

If you plan to use WGX applications regularly, you could add a special file type to Windows Explorer—I personally like to use the extension WGX and configure the Run action to use the following application:

"C:\Perl\bin\wperl.exe" -MWin32::GUI::XMLBuilder 
   -e"Win32::GUI::XMLBuilder->new({file=>shift @ARGV});Win32::GUI::Dialog" "%1" %*

This will allow WGX files to be automatically executed by double clicking on them or even by typing them in a Cmd.exe shell.

Mapping Win32::GUI to Win32::GUI::XMLBuilder

If you are familiar with Win32::GUI, you probably noticed that Win32::GUI::Dialog is the function that begins the dialog phase for a Win32::GUI object, which is what makes the GUI visible to the user. WGX includes Win32::GUI and all its subclasses, so any function in Win32::GUI is accessible from the code sections of a WGX mark-up file/program.

In fact, WGX might be considered just another way to write Win32::GUI code, where Win32::GUI objects become XML elements and Win32::GUI options become XML attributes. See Table 1.

Elements that will require referencing in your code should be given a name attribute, such as:

<Button name='MyButton'/>

You can then access this widget internally within your XML as $self->{MyButton}. If you constructed your WGX using something like this:

my $gui = Win32::GUI::XMLBuilder->new(...);

then you can access it as $gui->{MyButton} in your script. If you do not explicitly specify a name, one will be created. Attributes can contain Perl code or variables, and generally any attribute that contains the variable $self or starts with exec: will be evaluated. This is useful when you want to create dynamically sizing windows:

<GUI>
  <Window name='W' width='400' height='200'>
    <StatusBar name='S'
      text='status text'
      top='$self->{W}->ScaleHeight-$self->{S}->Height
                               if defined $self->{S}'
    />
  </Window>
</GUI>

This shows how to create a simple Window with a StatusBar that automatically positions itself. The value of $self->{W}->ScaleHeight-$self->{S}->Height if defined $self->{S} is recalculated every time the user resizes the Window. (I'll come back to the topic of autoresizing later.)

Win32::GUI::XMLBuilder has an XML Schema

All WGX XML files will contain this basic structure:

<GUI>
  <Window/>
</GUI>

The <GUI> .. </GUI> (or in XML shorthand <GUI/>) elements are required and all WGX elements must be enclosed between these. In fact, WGX has a well-defined schema and we should really expand the above to the following XML:

<?xml version="1.0"?>
<GUI
xmlns="http://www.numeninest.com/Perl/WGX"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.numeninest.com/Perl/WGX 
  http://www.numeninest.com/Perl/WGX/win32-gui-xmlbuilder.xsd">
  <Window/>
</GUI>

I chose not to, allowing me to simplify the code, but there is nothing stopping you from checking that your XML is well formed and valid using a tool such as Altova's XMLSPY (http://www .altova.com/). In fact, for large WGX programs, it is probably a good idea.

Top-Level Widgets

Every GUI application must have at least one top-level widget. These are the containers of your application. WGX currently supports several, including Window, DialogBox, and MDIFrame. They differ in behavior and functionality; for example, by default, a DialogBox cannot be resized and has no maximize or minimize buttons. See Figures 3 and 4.

Event Models

WGX supports two event models—New Event Model (NEM) and Old Event Model (OEM). NEM allows you to add attributes such as onClick or onMouseOver, containing anonymous subroutines to elements. The first argument to the subroutine is always the object in question. Our example:

<Button text='Push me!' onClick='sub 
            {$_[0]->Text("Thanks")}'/>

produces a Button widget with the initial text "Push me!" An anonymous sub is called when the user triggers the onClick event. This takes $_[0] (referring to the same Button) and applies the method Text, changing the text to "Thanks."

Autoresizing

A big headache when writing Win32::GUI applications is managing onResize events. This is normally done by treading through a top-level widget's children and explicitly specifying the dimensions that they should have if a Resize event occurs.

WGX will automatically generate an onResize NEM subroutine by reading-in the values for top, left, height, and width of each child. This will work sufficiently well, provided you use values that are dynamic, such as $self->{PARENT}->Width and $self->{PARENT}->Height for width and height attributes, respectively, when creating new widget elements. In fact, WGX will assume your child objects will have the same dimensions as their parents and will place them in the top left corner relative to their parent if you do not explicitly specify any dimensions.

In our simple button example, the Button element defaults to a position of top 0 and left 0 relative to its parent Window, with the exact same width and height as its parent window. To change this default, we must specify either left, top, width, or height attributes, or all these combined and separated with commas in a single dim attribute.

In Example 1, two widgets are specified. The Label widget only overrides the height attribute, thus taking on spatial defaults for everything else. The Button attribute overrides top and height attributes. The top attribute is necessary to avoid overlapping the Label widget. Figures 5 and 6 show the resulting window in its initial state, and after resize.

You will have noticed the special value %P% being used in the aforementioned attributes. These are substituted by WGX for $self->{PARENT}, sparing us the need to name the top-level Window widget.

WGX Utility Elements

A common task when writing an application is building a simple GUI that can recall its last desktop location and can be maximized or minimized. Such GUI-specific tasks might not be integral to the rest of the application, and it can be helpful to keep those tasks isolated from the core code of the application. With WGX, one can embed GUI initialization and destruction code directly in the XML file. There are several pure WGX elements that come to aid here.

<WGXPre>, <WGXPost>, and <WGXExec>

These utility elements allow you to execute Perl code before, after, and during construction of your GUI. This allows interface-specific code to be embedded within your XML document:

<WGXPre><![CDATA[

 use Win32::TieRegistry(Delimiter=>"|", 
                        ArrayValues=>0);
 our $registry = &initRegistry();

 sub initRegistry {
  ...
 }

 sub Exit {
  ...
 }

]]></WGXPre>

WGXExec and WGXPre elements are executed in place and before GUI construction. These elements can be useful when you need to implement something not directly supported by WGX, like Toolbars:

<Toolbar name='TB' height='10'/>
<WGXExec><![CDATA[
  $self->{TB}->AddString("File");
  $self->{TB}->AddString("Edit");
  $self->{TB}->SetStyle(TBSTYLE_FLAT
                        |TBSTYLE_TRANSPARENT
                        |CCS_NODIVIDER);
  $self->{TB}->AddButtons(2,
    0, 0, TBSTATE_ENABLED, BTNS_SHOWTEXT, 0,
    0, 1, TBSTATE_ENABLED, BTNS_SHOWTEXT, 1,
  );
]]></WGXExec>

Any output returned by WGXExec and WGXPre elements will be parsed as valid XML, so

<WGXExec><![CDATA[
  return "<Button text='Go'/>"
]]></WGXData>

and

<Button text='Go'/>

are equivalent. If the return data does not contain valid XML, then it will make your WGX document invalid—so check what these elements return, and if you are unsure, then add an explicit return.

WGXPost elements are executed after all GUI elements have been constructed and before your application starts its Win32 ::GUI::Dialog phase.

<WGXMenu>

This element can be used to create menu systems that can be nested many times more deeply than using the standard Win32::GUI MakeMenu. Although you can use Item elements throughout the structure, it is more readable to use the Button attribute when a new nest begins:

<WGXMenu name="M">
 <Button name='File' text='&amp;File'>
  <Button text='New'>
   <Item text='Type One Document'/>
   ...
  </Button>
  <Item name='ExitPrompt' 
   text='Prompt on Exit'
   checked='exec:$registry->{exitprompt}'
   onClick='sub { 
    $self->{ExitPrompt}->Checked(not 
            $self->{ExitPrompt}->Checked); 
   }'
  />
  <Item text='Exit' onClick='sub { Exit($_[0]) }'/>
 </Button>
 <Button name='Help' text='&amp;Help'>
  <Item text='Contents'/>
  <Item separator='1'/>
  <Item text='About' onClick='sub {
   Win32::GUI::MessageBox(
    $_[0],
    "... some info.",
    "About ...",
    MB_OK|MB_ICONASTERISK
   );
  }'/>
 </Button>
</WGXMenu>

You can see that separator lines can be specified by setting the separator attribute to 1. You will also notice menu items can also contain check boxes; in the earlier example, the check-box state is stored in the registry.

Example Listing

I hope I have given you a good base to start developing your own applications with Win32::GUI::XMLBuilder. I have provided a complete example with this article (available online at http:// www.tpj.com/source/). It is the application template also available with the distribution, and should be a good starting point for any Win32 application.

TPJ


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 
Dr. Dobb's TV