by Clinton R. Johnson - xepol@poboxes.com
Last week, there was an interesting offer from Matt Hamilton on how to make virtually any component moveable or resizable. Since then I have received quite a bit of excellent suggestions on how to extend these capabilities. Clinton Johnson offers this use of the technique in his Sizeable/Moveable Panel demo.
In a nutshell, he did the following... First, he added AllowMove and AllowResize properties to control whether the panel could be moved or sized. In the event that a move area needed to be established, he added a CaptionHeight property. This property controls the size of the region where you can click and drag to move the component. Next, he included a set property called ResizeDirection, allowing the user to control which edges of the control could be resized. He then added events that could be overridden for OnCanMove or OnCanResize. Finally, he implemented resizing restrictions using the WM_GETMINMAXINFO message. This allows the user to specify the maximum or minimum size that the panel can be.
The code is well commented, and will illustrate well the steps you can take to add resizing or moving control to your own components.
All-in-all a very complete solution. Well done Clinton!
unit SMPanel;
interface
//*****************************************************************************
// Sizable/Movable Panel Demo prepared for UNDO
//
// Written by Clinton R. Johnson - xepol@poboxes.com
//
// Based on an article by M. Hamilton - MHamilton@bunge.com.au
(and a little extra
// by Robert Vivrette)
//
// Fully extends the concept of resizing, and provides
the fundamentals required to
// include this code in any component. Minimum
and maximum window sizes are enforced.
// Moving the mouse over the regions which cause
resizing also cause the cursor to
// change to the appropriate cursor shape.
//
// This code is released into the public domain.
//*****************************************************************************
uses
Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs,
ExtCtrls;
Type
TResizeDirection = (rdNone,rdTop,rdBottom,rdLeft,rdRight,rdTopLeft,rdTopRight,rdBottomLeft,rdBottomRight);
TResizeDirectionSet = Set Of TResizeDirection;
Type
TNotifyMoveEvent = Procedure (Sender : TObject;
X: Integer; Y : Integer; Var CanMove : Boolean) Of Object;
TNotifyResizeEvent = Procedure (Sender : TObject;
ResizeDirection : TResizeDirection; Var CanResize : Boolean) Of Object;
type
TSMPanel = class(TPanel)
private
{ Private declarations }
fAllowMove
: Boolean;
fAllowResize
: Boolean;
fCaptionHeight
: Integer;
fMaxHeight
: Integer;
fMaxWidth
: Integer;
fMinHeight
: Integer;
fMinWidth
: Integer;
fOnCanMove
: TNotifyMoveEvent;
fOnCanResize
: TNotifyResizeEvent;
fResizeDirection
: TResizeDirectionSet;
Procedure WMGetMinMaxInfo(Var Msg
: TWMGetMinMaxInfo); Message WM_GetMinMaxInfo;
Procedure WMNCHitTest(var Msg
: TWMNCHitTest); Message WM_NCHITTEST;
Procedure WMMouseMove(Var Msg
: TWMMouseMove); Message WM_MouseMove;
Procedure TestHit(XPos : INteger;
YPos : INteger; Var Result : INteger);
protected
{ Protected declarations }
Procedure DoCanMove(X : Integer;
Y : Integer; Var CanMove : Boolean); Virtual;
Procedure DoCanResize(ResizeDirection
: TResizeDirection; Var CanResize : Boolean); Virtual;
public
{ Public declarations }
Constructor Create(AnOwner : TComponent);
OverRide;
published
{ Published declarations }
Property AllowMove
: Boolean
Read fAllowMove Write fAllowMove;
Property AllowResize
: Boolean
Read fAllowResize Write fAllowResize;
Property CaptionHeight
: Integer
Read fCaptionHeight Write fCaptionHeight;
Property MaxHeight
: Integer
Read fMaxHeight Write fMaxHeight;
property MaxWidth
: Integer
Read fMaxWidth Write fMaxWidth;
Property MinHeight
: Integer
Read fMinHeight Write fMinHeight;
property MinWidth
: Integer
Read fMinWidth Write fMinWidth;
Property ResizeDirection : TResizeDirectionSet
Read fResizeDirection Write fResizeDirection;
Property OnCanMove : TNotifyMoveEvent
Read fOnCanMove Write fOnCanMove;
Property OnCanResize : TNotifyResizeEvent
Read fOnCanResize Write fOnCanResize;
end;
procedure Register;
implementation
Constructor TSMPanel.Create(AnOwner : TComponent);
Begin
Inherited Create(AnOwner);
fAllowMove := True;
fAllowResize := True;
fCaptionHeight := 0;
fMaxHeight := 0;
fMaxWidth := 0;
fMinHeight := 1;
fMinWidth := 1;
fResizeDirection := [rdTop,rdBottom,rdLeft,rdRight,rdTopLeft,rdTopRight,rdBottomLeft,rdBottomRight];
End;
Procedure TSMPanel.DoCanMove(X : Integer; Y : Integer;
Var CanMove : Boolean);
Begin
If Assigned(FOnCanMove) Then fOnCanMove(Self,X,Y,CanMove);
End;
Procedure TSMPanel.DoCanResize(ResizeDirection : TResizeDirection;
Var CanResize : Boolean);
Begin
If Assigned(fOnCanResize) Then fOnCanResize(Self,ResizeDirection,CanResize);
End;
Procedure TSMPanel.WMGetMinMaxInfo(Var Msg : TWMGetMinMaxInfo);
Var
P
: TPoint;
Begin
Inherited;
Msg.MinMaxInfo^.ptMinTrackSize := Point(MinWidth,MinHeight);
P := Msg.MinMaxInfo^.ptMaxTrackSize;
If (MaxWidth<>0) Then P.X := MaxWidth;
If (MaxHeight<>0) Then P.Y := MaxHeight;
Msg.MinMaxInfo^.ptMaxTrackSize := P;
End;
Procedure TSMPanel.TestHit(XPos : INteger; YPos :
INteger; Var Result : INteger);
Var
BottomB
: Boolean;
CanMove
: Boolean;
CanResize
: Boolean;
LeftB
: Boolean;
RightB
: Boolean;
TopB
: Boolean;
BWidth
: Integer;
Pt
: TPoint;
Client
: TRect;
Direction
: TResizeDirection;
Begin
//*****************************************************************************
// We don't want to interfere with the IDE, no design
time.
//*****************************************************************************
If Not (csDesigning In ComponentState) Then
Begin
//*****************************************************************************
// X&Y come in as screen co-ordinates.
Switch to Client for easier
// manipulation.
//*****************************************************************************
Pt := ScreenToClient(Point(XPos,YPos));
//*****************************************************************************
// We'll consider the bevel & border areas the
area for resizing here.
//*****************************************************************************
BWidth := BorderWidth;
If (BevelInner<>bvNone) Then
BWidth := BWidth+BevelWidth;
If (BevelOuter<>bvNone) Then
BWidth := BWidth+BevelWidth;
//*****************************************************************************
// Set the client area so we can test later for dragging.
//*****************************************************************************
Client := Rect(0,0,Width,Height);
If AllowResize Then
Begin
//*****************************************************************************
// See where the mouse is in the resizing areas.
//*****************************************************************************
TopB := (Pt.Y<BWidth);
LeftB := (Pt.X<BWidth);
RightB := (Pt.X>=(Width-BWidth));
BottomB := (Pt.Y>=(Height-BWidth));
//*****************************************************************************
// Adjust the client area for dragging to NOT include
any valid direction
// for resizing.
//*****************************************************************************
If ((fResizeDirection
* [rdTop,rdTopLeft,rdTopRight])<>[]) Then
Begin
Client.Top
:= Client.Top+BWidth;
End;
If ((fResizeDirection
* [rdBottom,rdBottomLeft,rdBottomRight])<>[]) Then
Begin
Client.Bottom
:= Client.Bottom-BWidth;
End;
If ((fResizeDirection
* [rdLeft,rdTopLeft,rdBottomLeft])<>[]) Then
Begin
Client.Left:=
Client.Left+BWidth;
End;
If ((fResizeDirection
* [rdRight,rdTopRight,rdBottomRight])<>[]) Then
Begin
Client.Right
:= Client.Right-BWidth;
End;
//*****************************************************************************
// Determine exactly what direction we need to resize
in.
// Bottom & right are given preference here.
This is so that if the client
// area is shrunk to its smallest, the object will
always grow down and to the
// right.
//
// rdNone is primarily as semaphore for choosing
a direction here, but if
// it is included, then all resizing is turned off,
regardless of how all the
// rest of the flags are set.
//
//*****************************************************************************
Direction := rdNone;
If Not (rdNone IN
fResizeDirection) Then
Begin
If (rdTop
In fResizeDirection) And TopB Then Direction := rdTop;
If (rdLeft
In fResizeDirection) And LeftB Then Direction := rdLeft;
If (rdBottom
In fResizeDirection) And BottomB Then Direction := rdBottom;
If (rdRight
In fResizeDirection) And RightB Then Direction := rdRight;
If (rdTopLeft
In fResizeDirection) And (TopB And LeftB) Then Direction := rdTopLeft;
If (rdTopRight
In fResizeDirection) And (TopB And RightB) Then Direction := rdTopRight;
If (rdBottomLeft
In fResizeDirection) And (BottomB And LeftB) Then Direction := rdBottomLeft;
If (rdBottomRight
In fResizeDirection) And (BottomB And RightB) Then Direction := rdBottomRight;
End;
//*****************************************************************************
// If the resize direction is not blank, then ask
the code using this
// component to validate the resize - this allows
the component's
// resizing to be controlled in a more flexible manner.
//*****************************************************************************
CanResize := (Direction<>rdNone);
If CanResize Then
DoCanResize(Direction,CanResize);
//*****************************************************************************
// Test the resizing. Only allow resizing in
allowed directions.
// Bottom & right are given preference here again.
//*****************************************************************************
If CanResize Then
Begin
If (rdTop=Direction)
Then Result := HTTOP;
If (rdLeft=Direction)
Then Result := HTLEFT;
If (rdBottom=Direction)
Then Result := HTBOTTOM;
If (rdRight=Direction)
Then Result := HTRight;
If (rdTopLeft=Direction)
Then Result := HTTOPLEFT;
If (rdTopRight=Direction)
Then Result := HTTOPRight;
If (rdBottomLeft=Direction)
Then Result := HTBOTTOMLEFT;
If (rdBottomRight=Direction)
Then Result := HTBOTTOMRIGHT;
End;
End;
//*****************************************************************************
// If the CaptionHeight is non-zero, the user only
wants part of the height
// of the control to be used as a drag area.
//*****************************************************************************
If (CaptionHeight<>0) Then
Begin
Client.Bottom := Client.Top+CaptionHeight;
End;
//*****************************************************************************
// Test if Moving is permitted, and the point is
in the client area for moving.
//*****************************************************************************
CanMove := AllowMove And PtInRect(Client,Pt);
//*****************************************************************************
// Always leave the Final decision to move to the
code using the component.
// It can change a false to a true, and vice versa.
This allows people
// to refind the caption area to a greater detail.
//*****************************************************************************
DoCanMove(Pt.X,Pt.Y,CanMove);
If CanMove Then Result := HTCAPTION;
End;
End;
procedure TSMPanel.WMNCHitTest(var Msg: TWMNCHitTest);
Begin
Inherited;
TestHit(Msg.XPos,Msg.YPos,Msg.Result);
End;
procedure TSMPanel.WMMouseMove(var Msg : TWMMouseMove);
Begin
Inherited;
TestHit(Msg.XPos,Msg.YPos,Msg.Result);
End;
procedure Register;
begin
RegisterComponents('Samples', [TSMPanel]);
end;
end.