OK, let's begin. The traditional ``first program'' for any programming environment prints ``Hello, world'', so let's go with that.
To start with, we load Win32::GUI.
use Win32::GUI;
We are working in a windowing environment, so we need a window. Main
windows are created using Win32::GUI::Window->new(). The parameters to
new()
are a list of key => value pairs, which define the
properties of the window. For our simple example, all we need are a width
and a height (if we don't specify the window size, the default is to make
the window as small as possible - so there is no space for the text!).
$main = Win32::GUI::Window->new(-width => 100, -height => 100);
We save the window in the variable $main
so that we can refer
to it later.
Now, what about the text? Text can be put into a window using a ``label''
control. To add a label to a window, use the AddLabel()
method. AddLabel()
takes a list of options, similar to the
new()
call above. In this case, the only option we need is -text
, which specifies the text to display. By default, the label's size is just
big enough to fit its text, and its position is in the top left of its
containing window. This is fine for us.
$main->AddLabel(-text => "Hello, world");
OK, now we need make sure the window will be displayed. By default, windows
start hidden, so they won't be visible on screen. To make them visible, we
need to use the Show()
method.
$main->Show();
Finally, we need to start up a Windows ``message loop''. This shows the application windows, and waits for user interaction. The Win32::GUI message loop is implemented in the Win32::GUI::Dialog() function. All that is needed is to call that function, and wait for it to return.
Win32::GUI::Dialog();
One thing remains. At the moment, we are displaying the window, and waiting for user interaction. However, we haven't said what to do if any user interaction (called an event) occurs! Worse still, we haven't even said what to do when the window closes, so the message loop keeps on spinning, even after the window closes - so we never return from Win32::GUI::Dialog...
So we need to react to events. How do we do that? Well, the first thing we
need is for any window or control which is to react to events to have a
name. This name is specified using the -name
option. So we change the statement which creates our main window as follows
$main = Win32::GUI::Window->new( -name => 'Main', -width => 100, -height => 100, );
Now, we define event handlers for the window. Event handlers are simply Perl subroutines with specific
names - <window name>_<event name>. For example, the event which happens when the window is
closed is the Terminate
event, so the event handler we need for our main window is Main_Terminate
.
Event handlers should return one of three specific values:
1: Proceed, taking the default action defined for the event.
0: Proceed, but do not take the default action.
-1: Terminate the message loop.
Obviously, what we want for the Main_Terminate event is to return -1 (terminate the message loop). So, we have
sub Main_Terminate { -1; }
More generally, we could do some processing before returning from the event handler, but we don't need to here.
So, let's put it all together.
use Win32::GUI; $main = Win32::GUI::Window->new( -name => 'Main', -width => 100, -height => 100, ); $main->AddLabel(-text => "Hello, world"); $main->Show(); Win32::GUI::Dialog();
sub Main_Terminate { -1; }
Put that in a file (say, hello.plx) and run it using perl hello.plx
.
Notice how you can resize and move the window, maximize and minimize it, just like any other application window.
So that's it. Ten lines of code to produce a fully working Windows application.
Now, we'll add some simple improvements to make our application look more polished.
You will notice that the application window has no title. To include a
title, all we need is to add a -text
option to the main window.
$main = Win32::GUI::Window->new( -name => 'Main', -width => 100, -height => 100, -text => 'Perl', );
Now, suppose we want the window to be just big enough to hold the label, rather than being a fixed size. This is a bit more complicated, because we need to take note of the difference between the total window size, and the client area, which is the area of the window excluding the title bar, the system, minimize, maximize, and close icons, and the window border. In other words, it is the area in which we can actually display information.
We can get the window size using the Height()
and
Width()
methods, and we can get the client area size using the
ScaleHeight()
and ScaleWidth()
methods.
We can get the label size similarly, using Height()
and
Width()
(labels do not have borders, so the label area and its
client area are the same).
To get the dimensions of the non-client area of the main window, we just need
$ncw = $main->Width() - $main->ScaleWidth(); $nch = $main->Height() - $main->ScaleHeight();
Now, we get the required size as
$w = $label->Width() + $ncw; $h = $label->Height() + $nch;
As we are getting properties of the label, we should save the return value
of the AddLabel()
call - which is a reference to the label -
in a variable $label.
And we set the main window's size using Resize()
$main->Resize($w, $h);
We have to resize after we have created the window, because we need to create the window first in order to calculate the non-client dimensions. But we do it before we show the window, to avoid any flicker.
OK, let's put all this together. We'll add the ability to specify the label text on the command line, just to be fancy (it also shows that the values of options can be set using variables, not just constant values).
use Win32::GUI;
$text = defined($ARGV[0]) ? $ARGV[0] : "Hello, world";
$main = Win32::GUI::Window->new(-name => 'Main', -text => 'Perl'); $label = $main->AddLabel(-text => $text);
$ncw = $main->Width() - $main->ScaleWidth(); $nch = $main->Height() - $main->ScaleHeight(); $w = $label->Width() + $ncw; $h = $label->Height() + $nch;
$main->Resize($w, $h); $main->Show(); Win32::GUI::Dialog();
sub Main_Terminate { -1; }
Run this version and see the results. Note that if you supply a short
string, the window will not shrink beyond a minimum size sufficient to show
the window icons.
Now, suppose we want to change the colour in which the text is displayed.
This is easy. You just specify the colour as the value of the -foreground
attribute of the label. Colours can be specified either as hex values in
the format 0xBBGGRR, or as list references in the format [ R, G, B ].
So, to make the text red, use -foreground => 0x0000FF
or -foreground
=> [ 255, 0, 0 ]
.
To change the font, you need to specify the -font
attribute. But in this case, the value of the attribute is a
Win32::GUI::Font object. To create a font object, use
$font = Win32::GUI::Font->new(...);
As usual, the parameters to new()
are a set of options. The
most important ones are
The font size in points.
The font name, such as ``Times New Roman'', ``Arial'', ``Verdana'' or ``Comic Sans MS''.
One for bold, zero (the default) for non-bold.
One for italic, zero (the default) for non-italic.
So, to change the format of our label, all we need is
$font = Win32::GUI::Font->new( -name => "Comic Sans MS", -size => 24, ); $label = $main->AddLabel( -text => $text, -font => $font, -foreground => [255, 0, 0], );
Simple, isn't it?
To centre the main window on the screen, we need to know the screen size.
We get this using the desktop window. You can get the handle of the desktop window using
Win32::GUI::GetDesktopWindow(). One point to note is that window handles
are not Win32::GUI::Window objects, so they cannot be used to call Win32::GUI
methods like Height().
However, these methods are overloaded
so that they can be called directly (as Win32::GUI::Height()) with a window
handle as an extra first parameter. See the code below for an example.
The rest of the work is just arithmetic. To reposition the main window, use
the Move()
method.
# Assume we have the main window size in ($w, $h) as before $desk = Win32::GUI::GetDesktopWindow(); $dw = Win32::GUI::Width($desk); $dh = Win32::GUI::Height($desk); $x = ($dw - $w) / 2; $y = ($dh - $h) / 2; $main->Move($x, $y);
Now, we will look at centring the label in the window (you will notice that if you increase the size of the window, the text stays at the top left of the window).
The process is similar to the calculations for centring the window above.
There are two main differences. First of all, the label needs to be
recentred every time the window changes size - so we need to do the
calculations in a Resize event handler. The second difference (which in
theory applies to the window as well, but which we ignored above), is that
we need to watch out for the case where the window is too small to contain
the label. Just to make things interesting, we'll stop the main window
getting that small, by resizing it in the event handler if that is about to
happen. As the width could be too small while the height is OK, we have to
reset the width and height individually. We can do this using variations on
the Width()
and Height()
methods, with a single
parameter which specifies the value to set.
OK, let's just do it.
sub Main_Resize { my $w = $main->Width(); my $h = $main->Height(); my $lw = $label->Width(); my $lh = $label->Height(); if ($lw > $w) { $main->Width($lw) + $ncw; # Remember the non-client width! } else { $label->Left(($w - $lw) / 2); } if ($lh > $h) { $main->Height($lh) + $nch; # Remember the non-client height! } else { $label->Top(($h - $lh) / 2); } }
This does not work in build 340 of Win32::GUI, as there is a bug in the
Left()
and Top()
methods for client windows (such
as the label used here). Hopefully, this bug will be fixed in the next
version.
Note that co-ordinates are calculated from the top left of the enclosing window.
Suppose we wanto to ensure that our main window never gets smaller than
100x100 pixels. We could include code in a resize event, like we did in the
previous section to make sure the window never got smaller than the label.
However, this can result in fairly bad flickering. A better solution is to
use the -minsize
option.
$main = Win32::GUI::Window->new( -name => 'Main', -text => 'Perl', -minsize => [100, 100], );
Simple! But in the previous example, we wanted to restrict the window size
to the size of the label. We can't do this when we create the window,
because we haven't created the label yet. But we can still do this - we
just have to set the window's -minsize
option after the label has been created. We do this with the
Change()
method. We just specify this before resizing the
window, in our initial setup.
$main->Change(-minsize => [$w, $h]);
OK, we now have a nice, fully working GUI application. Here it is, in all its glory.
use Win32::GUI;
$text = defined($ARGV[0]) ? $ARGV[0] : "Hello, world";
$main = Win32::GUI::Window->new( -name => 'Main', -text => 'Perl', ); $font = Win32::GUI::Font->new( -name => "Comic Sans MS", -size => 24, ); $label = $main->AddLabel( -text => $text, -font => $font, -foreground => [255, 0, 0], );
$ncw = $main->Width() - $main->ScaleWidth(); $nch = $main->Height() - $main->ScaleHeight(); $w = $label->Width() + $ncw; $h = $label->Height() + $nch;
$desk = Win32::GUI::GetDesktopWindow(); $dw = Win32::GUI::Width($desk); $dh = Win32::GUI::Height($desk); $x = ($dw - $w) / 2; $y = ($dh - $h) / 2;
$main->Change(-minsize => [$w, $h]); $main->Move($x, $y); $main->Show();
Win32::GUI::Dialog();
sub Main_Terminate { -1; }
sub Main_Resize { my $w = $main->ScaleWidth(); my $h = $main->ScaleHeight(); my $lw = $label->Width(); my $lh = $label->Height(); $label->Left(int(($w - $lw) / 2)); $label->Top(int(($h - $lh) / 2)); }
That's it for this tutorial. In part 2, we will start looking at some other controls.