=head1 NAME Win32::GUI::Resizer -- Makes window Resize() events easy to manage =head1 DESCRIPTION The Win32::GUI::Resizer class can help you deal with a window's Resize() event. It lets you define how your controls should move and change size along with the window or some other gadget (e.g. a Splitter bar). It provides a consistent way of thinking about these things, and an easy way of defining them with little or no code. It handles the minimize/restore of a window correctly. It can handle you manually moving controls around or resizing them at run-time. It is slightly slower than coding the Resize() event by hand. =head1 VERSION Like, pre-release. I'd appreciate feedback. Things may change, but probably nothing that would ruin your day. =head1 INSTALLATION Just copy the .pm file into the Win32/GUI directory. I'll write a proper module with a Makefile.PL and stuff some day. =head1 SYNOPSIS use strict; use Win32::GUI; #Lots of code to build the window. Just note the names of the #window and the controls as you scroll down. my $winMain = new Win32::GUI::Window( -left => 13, -top => 32, -width => 439, -height => 260, -name => "winMain", -text => "Win32::GUI::Resizer Synopsis" ); $winMain->AddTextfield( -text => "", -name => "tfName", -left => 67, -top => 6, -width => 363, -height => 20, ); $winMain->AddLabel( -text => "Your Name:", -name => "lblName", -left => 6, -top => 9, -width => 56, -height => 13, ); $winMain->AddLabel( -text => "Your cool saying:", -name => "lblSaying", -left => 6, -top => 32, -width => 81, -height => 14, ); $winMain->AddRichEdit( -text => "<>", -name => "reSaying", -left => 2, -top => 49, -width => 427, -height => 150, ); $winMain->AddButton( -text => "&Ok", -name => "btnOk", -left => 358, -top => 207, -width => 70, -height => 21, ); $winMain->Show(); sub winMain_Terminate { return -1; } #The usual window stuff is done here, now lets make it resizable use Win32::GUI::Resizer; #Create the Resizer object and connect it to the window my $objResizer = Win32::GUI::Resizer->new($winMain); #Define the relations between the window and the controls $objResizer->raRelations([ 'winWidth' => [ ['$winResize->reSaying->Width()'], ['$winResize->tfName->Width()'], ['$winResize->btnOk->Left()'], ], 'winHeight' => [ ['$winResize->reSaying->Height()'], ['$winResize->btnOk->Top()'], ], ]); #Remember the current control locations $objResizer->memorize(); #Connect the Resizer object to the Resize() event sub winMain_Resize { defined($objResizer) and $objResizer->resize(); } #Go modal Win32::GUI::Dialog(); =cut package Win32::GUI::Resizer; require Exporter; @ISA = qw(Exporter); @EXPORT_OK = qw( negResize ); use strict; =head1 PROPERTIES =head2 winResize The Win32::GUI window object to resize. =cut sub winResize { my $self = shift; my ($val) = @_; if(defined($val)) { $self->{winResize} = $val; } return($self->{winResize}); } =head2 raRelations The relations between the controls of the window. These settings define how each control will behave when the window or e.g. a Splitter changes size or position. The syntax and structure of the array ref is described below. =cut sub raRelations { my $self = shift; my ($val) = @_; if(defined($val)) { $self->{raRelations} = $val; } return($self->{raRelations}); } =head2 isMinimized Whether the window is minimized or not. Set by the resize() method when the window size is (0,0). =cut sub isMinimized { my $self = shift; my $pkg = ref($self); my ($val) = @_; if(defined($val)) { $self->{isMinimized} = $val; } return($self->{isMinimized}); } =head2 raPreMinimizedSize Array ref with two items, the w/h of the window size prior to a minimize. =cut sub raPreMinimizedSize { my $self = shift; my $pkg = ref($self); my ($val) = @_; if(defined($val)) { $self->{raPreMinimizedSize} = $val; } return($self->{raPreMinimizedSize}); } =head1 METHODS =head2 new(Win32::GUI::Window $winResize) Create new Resizer object for $winResize. =cut sub new { my $pkg = shift; $pkg = ref($pkg) || $pkg; my ($winResize) = @_; my $self = { raRelations => [], rhSpecialValue => {}, rhSpecialValueOld => {}, rhCache => {}, initDone => 0, isMinimized => 0, raPreMinimizedSize => [0,0], }; bless $self, $pkg; $self->winResize($winResize); return($self); } =head2 memorize() Init the Resizer by remembering the size of the winResize() window and the contained controls. Call this method when you have set up the entire window the way you want it. Return 1 on success, else 0. =cut sub memorize { my $self = shift; $self->createCache() if(!$self->{initDone}); my @aSize = ($self->winResize()->GetClientRect)[2..3]; #If the window is minimized (has h/w == 0/0), use #the stored window size, otherwise remember the #actual window size. if($aSize[0] == 0 && $aSize[1] == 0) { @aSize = @{$self->raPreMinimizedSize()}; } else { $self->raPreMinimizedSize(\@aSize); } $self->specialValue('winWidth', $aSize[0], 0); $self->specialValue('winHeight', $aSize[1], 0); $self->{initDone} = 1; return(1); } =head2 rememorize() Re-init the Resizer by remembering the size of the winResize() window and the contained controls. Call this method once you have changed the location or size of any control that you have under Resizer control. Return 1 on success, else 0. =cut sub rememorize { my $self = shift; $self->{rhCache} = {}; $self->createCache(); $self->memorize(); return(1); } =head2 specialValue($key [,$val] [,$resize = 1]) Get or set the $val for $key. When set, the window is resized using the resize() method, unless $resize is false. Use this to keep track of elements in the window that may change size or position, e.g. a Splitter bar (in the Release() event, update the position value by calling this method). Return the value, or undef if $key has no value. =cut sub specialValue { my $self = shift; my ($key, $val, $resize) = @_; defined($resize) or $resize = 1; if(defined($val)) { $self->{rhSpecialValue}->{$key} = $val; $self->specialValueOld($key, $val) if(! $self->{initDone}); $self->resize() if($resize); } return($self->{rhSpecialValue}->{$key}); } =head2 resize() Resize the controls of the window. It will handle a window's minimize/restore correctly. Call this method from the window's Resize() event, like so: sub winMain_Resize { defined($objResizer) and $objResizer->resize(); } =cut sub resize { my $self = shift; return(0) if(!$self->{initDone}); #Is it being minimized? my @aSize = ($self->winResize()->GetClientRect)[2..3]; if($aSize[0] == 0 && $aSize[1] == 0) { $self->isMinimized(1); return(1); } #Is it being restored? if($self->isMinimized()) { if(!($aSize[0] == 0 && $aSize[1] == 0)) { $self->raPreMinimizedSize(\@aSize); $self->isMinimized(0); } return(1); } $self->memorize(); #Lexicalize the window so we have a scope for the symbol my $winResize = $self->winResize(); #Shorten access path my $rhCache = $self->{rhCache}; ##Calculate delta values and modify my @aTemp = @{$self->raRelations()}; while(@aTemp) { my ($srcVar, $raTrgVal) = (shift @aTemp, shift @aTemp); #It's a special, get it my $scrVarValOld = $self->specialValueOld($srcVar); my $scrVarVal = $self->specialValue($srcVar); my $delta = $scrVarVal - $scrVarValOld; if($delta != 0) { for my $raValOp (@{$raTrgVal}) { my $var = ${$raValOp}[0]; my $rsModifier = ${$raValOp}[1]; #Insert modifyer my $thisDelta = (defined($rsModifier)) ? $rsModifier->($delta) : $delta; my $valOld = $rhCache->{$var}; #Compute new val $rhCache->{$var} = $valOld + $thisDelta; #Assign it if($var =~ /^(.+)\->(\w+?)\(\)$/) { $rhCache->{$1}->$2($rhCache->{$var}); } else { my $varEval = $var; $varEval =~ s|\)$|$rhCache->{$var})|; eval($varEval); die("eval($varEval) failed in Win32::GUI::Resizer->resize(): $@") if($@); } } } } #Reset "old" values $self->resetSpecialValues(); return(1); } =head1 SUBROUTINES =head2 negResize($a) Return 0 - $a. May be used in the raRelations structure to indicate that e.g. a value should shrink as the window size grows. Not exported by default. =cut sub negResize { $_[0] * -1; } =head1 PRIVATE METHODS This stuff is implementation, not interface. =head2 specialValueOld($key [,$val]) Get or set the $val for $key. Return the value, or undef if $key has no value. =cut sub specialValueOld { my $self = shift; my ($key, $val) = @_; if(defined($val)) { $self->{rhSpecialValueOld}->{$key} = $val; } return($self->{rhSpecialValueOld}->{$key}); } =head2 resetSpecialValues() Set the specialValuesOld to specialValues. Return 1 on success, else 0. =cut sub resetSpecialValues { my $self = shift; my ($val) = @_; my %hTemp = %{$self->{rhSpecialValue}}; $self->{rhSpecialValueOld} = \%hTemp; $self->memorize(); return(1); } =head2 createCache() Cache values for control dimensions. Return 1 on success, else 0. Die on fatal (config) errors. =cut # #The calls to get e.g. Height() and Width() for controls take #some time, but not a lot. Calls to resolve contained objects #take a lot of time. # #These calls are cached. By doing that I also managed to cut #down on the evals, and this keeps the resize from being 4x #slower to maybe 15% slower than a manually coded resize. # #This is probably mostly due to the fact that some operations #would be coded with Move() and Resize() rather than #two calls to Left()/Top() and Height()/Width(). # sub createCache { my $self = shift; #Lexicalize the window so we have a scope for the symbol my $winResize = $self->winResize(); #Shorten access path my $rhCache = $self->{rhCache}; eval { my @aTemp = @{$self->raRelations()}; while(@aTemp) { my ($srcVar, $raTrgVal) = (shift @aTemp, shift @aTemp); for my $raValOp (@{$raTrgVal}) { my $var = ${$raValOp}[0]; #Cache the total value $rhCache->{$var} = eval($var); die($@) if($@); #Cache the object ref used to call the final method if($var =~ /^(.+)\->(\w+?)\(\)$/) { $rhCache->{$1} = eval($1); } } } }; die("Win32::GUI::Resizer: There is an error in the rhRelations() property:\n$@") if($@); return(1); } 1; __END__ =head1 THE raRelations ARRAY REF This is the raRelations from the Synopsis example. [ 'winWidth' => [ ['$winResize->reSaying->Width()'], ['$winResize->tfName->Width()'], ['$winResize->btnOk->Left()'], ], 'winHeight' => [ ['$winResize->reSaying->Height()'], ['$winResize->btnOk->Top()'], ], As you can see, it contains a two-dimensional array ref. The first level contains values that may change, in this case the window's width and height. The second level contains properties that are dependent on the first level. Thus, when the height of the window changes, so should the height of reSaying and the top of btnOk. There are a number of predefined values you have to keep in mind when you create your array. B<$winResize> -- This is your window object, the one you specified in the new() method, and the one available in the winResize() property. This is the only way you should refer to that window in this array. B -- The interior width (the ScaleWidth() property) of $winResize. B -- The interior height. =head2 The Resizer Mindset Just to make sure you "think right" about this, consider this piece: 'winWidth' => [ ['$winResize->tfName->Width()'], ['$winResize->btnOk->Left()'], Read it like this: 'when-this-increases-its-value' => [ ['so-should-this-width'], ['and-this-left-position'], =head2 Negative Growth Sometimes a value should shrink when e.g. the window width grows. This is handled with an optional second entry in the inner array, like this: 'winWidth' => [ ['$winResize->tfName->Width()'], ['$winResize->btnOk->Left()', \&Win32::GUI::Resizer::negResize], The second entry is a subroutine ref which is used to perform a calculation on the delta value (how much the Left value should change). Having a control shrink as the window grows is fairly common, so there is a ready-made subroutine for it. It isn't exported by default though, so to avoid the lengthy symbol name you might want to do that when you use the Resizer class: use Win32::GUI::Resizer qw( negResize ); =head2 Things to Think About In the example, it is possible to refer to e.g. the reSaying control both like this: $winResize->reSaying and like this: $winResize->reSaying() Either one is fine, but B mix them within the raRelations() array. THIS IS A BIG NO-NO: [ 'winWidth' => [ ['$winResize->reSaying->Width()'], ['$winResize->reSaying()->Height()'], ], =head1 TUTORIAL use the window in uff splitter, specialValue(), negative growth move controls at runtime, rememorize() =head1 COPYRIGHT Copyright 2001.. Johan Lindström Same license as Perl. =cut