Building GUIs with Win32::GUI::XMLBuilder

Blair 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. In this article, he describes how to build a simple Win32::GUI::XMLBuilder application and also provides a simple template for such applications.


November 01, 2004
URL:http://www.drdobbs.com/web-development/building-guis-with-win32guixmlbuilder/184416163

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 [email protected].


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='&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='&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

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

<GUI>
 <Window height='90'>
  <Label
   text='Push the button below several times'
   height='%P%->ScaleHeight/2'
   align='center'
  />
  <Button
   text='Push me!'
   top='%P%->ScaleHeight/2'
   height='%P%->ScaleHeight/2'
   onClick='sub {
    $_[0]->Text($_[0]->Text eq "Thanks" ? "Push Me!"
                : "Thanks")
   }'
  />
 </Window>
</GUI>

Example 1: A window with Label and Button widgets overriding default values.

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

Figure 1: A simple dialog built with Win32::GUI::XMLBuilder.

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

Figure 2: The dialog from Figure 1 after the button is pressed.

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

Figure 3: A window created with <Window width='100' height='50'/>.

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

Figure 4: A dialog created with <DialogBox width='100' height='50'/>.

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

Figure 5: Window from Example 1 in its initial state.

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

Figure 6: Window from Example 1 after resize.

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

Win32::GUI Win32::GUI::XMLBuilder
Elements my $W = new Win32::GUI::Window(); <Window>
$W->AddButton(...) <Button .../>
</Window>
Attributes ..->AddSomeWidget(-name => "SW", -text => "Go") <SomeWidget name='SW' text='Go'/>

Table 1: Mapping between Win32::GUI elements and Win32::GUI::XMLBuilder elements.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.