Thursday, December 23, 2010

Size matters

Once upon a time (in fact, until about three months ago), the computer monitors on which my programs run were all the same size. This made it very easy for me when designing screens/forms for my programs: if they looked ok on my monitor, then they would be ok on my client's monitors.

With the advent (and purchase) of different sized monitors with different display ratios and resolutions, this state of affairs is now in the past. The client (OP) has a monitor which is capable of displaying many more pixels than my monitor displays, and she wants to make use of that extra space. So I had to start learning about resizeable forms. As it happens, these work in synergy with MDI child forms - but not with dialog boxes - and a few of my recent programs follow the MDI model, so the possibility of conversion to resizeable forms definitely exists.

The starting point of this discussion will be a simple MDI child form with a database grid and a panel hosting a few buttons. Something like this:


My first attempts at creating a resizeable form included the following steps:
  1. Change the form's border style from 'bsSingle' to 'bsSizeable'
  2. Add a status bar at the bottom of the form in order to show the 'stretchable' bitmap hint
Whilst the form could be resized, the individual components of the form stayed where they were. The next stage in the learning process was the anchor: both the grid and the panel have their anchors set to akLeft and akTop, or in English, the left and top coordinates remain at a fixed offset from the form's top and left. As resizing is generally to the bottom and to the right, the result is that the components don't resize. The key to this conundrum was to set the akBottom anchor property to true - the bottom of the component will be at a fixed offset from the form's bottom. If the form's height increases, the height of the components will increase as well.

Whilst all was good at the design stage, running the program produced different results when resizing the form, specifically regarding the width of the form. A MDI child form whose border style was 'bsSizeable' was displaying at a different width than the form's defined width and this was annoying. I programmed a work around by forcing the form to be a specific width in the FormShow and FormResize methods, but it was clear that this wasn't the solution.

Looking around on the internet, I came across the SizeGrip component; I think that this component originated here, although I actually found it on a different blog which quoted the original word for word without attribution (very bad practice). The sizegrip component allows a form to be resizeable without the need of a statusbar; for reasons which I don't understand, the form's border style can remain bsSingle yet still resize, thus solving the display width problem.

Along with anchors, one is supposed to use the constraints property which define the minimum size for the form. Once I had allowed a form to resize, it seemed only polite to store the resized height in the registry so that the form could be displayed at the same height the next time it was invoked. As a result, I started adding the following code to units
procedure TDoTables.FormCreate(Sender: TObject);
const
 mywidth = 416;
 myheight = 496;

begin
 height:= reg.ReadInteger (progname, 'dotables', myheight);
 constraints.MinWidth:= mywidth;
 constraints.MinHeight:= myheight;
end;

procedure TDoTables.FormClose(Sender: TObject;
  var Action: TCloseAction);
begin
 reg.WriteInteger (progname, 'dotables', height);
 action:= caFree;
 TrimMemory
end;
'DoTables' is the name of the form; the registry stores the height of each individual form.The 'myheight' and 'mywidth' constants were defined in the design stage - what looks good on my monitor.

But: there is still room for improvement. If I set the grid and panel's anchor.akBottom propery to true and enlarge the form, the form looks as follows


Whilst the panel has resized, the buttons have remained in the same position. Obviously the top button should remain in the same position, but the second and third buttons' position should change. The bottom button's top property can easily be calculated as it will be the panel's new height less the height of the button less 8 (the offset from the bottom of the panel), but what about the middle button? And what happens if there are four buttons on the panel? Basically, what is needed is the equivalent of the alignment palette's space equally vertically command. As I don't know how to access this at runtime, I have to mimic its action.

After a bit of muddling around, I found the following method which sits in the form's FormResize method. First of all, calculate the increment to the panel's height (the original design time height is stored as a constant), then divide this height by the number of buttons less one. The second button's top has to increase by this calculated increment, and the third button's top has to increase by this calculated increment times two. Translated into code, it looks like this:
procedure TDoTables.FormResize(Sender: TObject);
const
 ph = 441;

var
 incr: word;

begin
 closebtn.top:= panel1.Height - closebtn.Height - 8;
 incr:= panel1.height - ph;
 if rankbtn.Visible then  // five buttons on screen
  begin
   incr:= incr div 4;
   editbtn.Top:= 106 + incr;
   contactsbtn.top:= 204 + incr * 2;
   rankbtn.top:= 302 + incr * 3
  end
 else
  begin
   incr:= incr div 3;
   editbtn.Top:= 138 + incr;
   contactsbtn.top:= 269 + incr * 2;
  end;
end;
The above is probably more complicated than need be, but I thought I'd quote the form's code ad verbatim. The complication is caused by the fact that sometimes the form displays five buttons (rankbtn will be visible) and sometimes only four buttons (no rankbtn). Editbtn is the second button on the panel and contactsbtn the third; Closebtn is always the final button on the panel so its top can be calculated without knowing the increment in panel height. The magic numbers in the code (106, 204, 302, 138, 269) are the buttons' tops at design time.

I'm going to let this code sit for a while (like good wine); maybe there is room for further improvement.

No comments: