One control that I have not seen in the line-up of new controls in the VCL for Tiburón is a size grip.
Most people may not even realise that this control exists, even though it is part of Windows itself and is really quite useful. Fortunately, implementing it ourselves is truly trivial.
What is a Size Grip?
A size grip is the small triangular control, usually in the lower right hand corner of a window that provides an indication that the window is resizable and is also a grab-point that the user can use to resize the window itself.
You will see one if you place a status-bar control on a resizable form:
If the form is not resizable, the grip does not appear in the status bar.
But what if I have a form that I want to be sizable, but which does not have a status bar, doesn’t need a status bar, and which perhaps in all other respects doesn’t even look resizable?
Imagine, for example, that I have redesigned a dialog box so that it now needs to be resizable….
If you look at the dialog box shown in that link, you will see that it is resizable but offers no immediately obvious visual cue that this is in fact the case. It clearly doesn’t need a status bar.
What would be useful in such cases is a way to add a size grip without having to use a status bar.
And this is exactly what a size grip control does for us.
The Implementation
As I mentioned, the implementation of such a control is trivial. So trivial that the entire implementation is provided below. No need for a download in this case.
interface uses Classes, Controls; type TSizeGrip = class(TWinControl) protected procedure CreateParams(var Params: TCreateParams); override; procedure CreateWnd; override; public constructor Create(aOwner: TComponent); override; end; implementation uses Windows; { TSizeGrip } constructor TSizeGrip.Create(aOwner: TComponent); begin inherited; ControlStyle := [csOpaque, csFixedWidth, csFixedHeight]; Anchors := [akRight, akBottom]; Cursor := crSizeNWSE; Height := 11; Width := 11; end; procedure TSizeGrip.CreateParams(var Params: TCreateParams); begin inherited; CreateSubClass(Params, 'SCROLLBAR'); Params.Style := Params.Style or WS_CLIPSIBLINGS or SBS_SIZEGRIP; end; procedure TSizeGrip.CreateWnd; begin inherited; Left := Parent.ClientWidth - Width - 1; Top := Parent.ClientHeight - Height - 1; SendToBack; end;
As you can see, a size grip is in fact a special form of the built in SCROLLBAR Windows control class.
This code should work in any Win32 version of Delphi. In my case since I use Delphi 7 I use dotted unit names and have it implemented in Deltics.Controls.SizeGrip. In earlier Delphi versions you will need to use a unit name with no dots.
And yes, we should probably use system metrics to size the control.
The implementation has the control place itself in the lower right hand corner of it’s parent, and stay there. But how do we ensure that we only show the control if the form is resizable?
Well, actually, we don’t need to worry about that. Simply placing the control on a form makes the form resizable (via the size grip) even if that form would normally not be resizable. That is, if the user moves their mouse over the size grip it will change to the resizing cursor and they can resize the form by grabbing the control and dragging.
So the user can see that the dialog is resizable and is provided with the means to resize the form, even if the form has no border at all, such as this one, with border style bsNone:
Getting this is as easy as adding:
with TSizeGrip.Create(self) do Parent := self;
To a form constructor or OnCreate event.
In fact, in my applications I can simply write in any form constructor or OnCreate event:
HasSizeGrip := TRUE;
Any form.
How is that possible? HasSizeGrip is not a property of TForm, so where does it come from?
I’m saving that for next time. 😉
I was actually thinking about this yesterday! Thanks for saving me from doing the research!!! 😀
Nice work! With a bit of investigation, though, I’ve found you can in fact get away with even less code *and* respect the system’s default size:
type
TSizeGrip = class(TWinControl)
protected
procedure CreateParams(var Params: TCreateParams); override;
public
constructor Create(AOwner: TComponent); override;
end;
constructor TSizeGrip.Create(AOwner: TComponent);
begin
inherited;
ControlStyle := [csOpaque, csFixedWidth, csFixedHeight];
Anchors := [akRight, akBottom];
Cursor := crSizeNWSE;
end;
procedure TSizeGrip.CreateParams(var Params: TCreateParams);
var
R: TRect;
begin
inherited;
CreateSubClass(Params, ‘SCROLLBAR’);
Params.Style := Params.Style or WS_CLIPSIBLINGS or SBS_SIZEGRIP or
SBS_SIZEBOXBOTTOMRIGHTALIGN;
if not Windows.GetClientRect(Params.WndParent, R) then
RaiseLastOSError;
Params.X := R.Left;
Params.Y := R.Top;
Params.Width := R.Right – R.Left;
Params.Height := R.Bottom – R.Top;
end;
The default VCL code will update the control’s Left, Top, Width and Height properties after it calls CreateWindowEx.
@Peter – Glad to have been of service. 🙂 Be sure to check out Chris’ very useful revisions in his comments!
@CR – Looks like more or less the same amount of code to me. 😉
But one less override and respect for system defaults is all good. Thanks for the revisions!
Slight deviation: I have always been amused by the resize grip in Microsoft Access forms. They are there, you can see them, but they are non-functional!
To resize an Access form, you have to grab the border of the form itself; something that works with the remaining three corners of ANY resizable form.
Jolyon, firstly thanks for the nice component. 🙂
Also, if you want to add this component to the component palette then the following additional functions and methods will 1. register the component; 2. allow the sizegrip to work within the IDE; and 3. overcome inadvertent moving or resizing at design time …
type
TSizeGrip = class(TWinControl)
protected
procedure CMDesignHitTest(var Message: TCMDesignHitTest); message CM_DESIGNHITTEST;
procedure CreateParams(var Params: TCreateParams); override;
public
constructor Create(aOwner: TComponent); override;
procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents(‘Samples’, [TSizeGrip]);
end;
procedure TSizeGrip.CMDesignHitTest(var Message: TCMDesignHitTest);
begin
Message.Result := 1;
end;
procedure TSizeGrip.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
if (csDesigning in ComponentState) and assigned(Parent) then
begin
ALeft := Parent.ClientWidth – width;
ATop := Parent.ClientHeight – height;
AWidth := width;
AHeight := height;
end;
inherited;
end;
//the following are implemented as per CR’s suggestions above.
constructor TSizeGrip.Create(aOwner: TComponent);
begin
…
end;
procedure TSizeGrip.CreateParams(var Params: TCreateParams);
begin
…
end;
Excellent additions Angus. Thanks.