lazarus/lcl/include/wincontrol.inc

7324 lines
251 KiB
PHP

{%MainUnit ../controls.pp}
{ $Id$ }
{******************************************************************************
TWinControl
******************************************************************************
*****************************************************************************
* *
* This file is part of the Lazarus Component Library (LCL) *
* *
* See the file COPYING.modifiedLGPL, included in this distribution, *
* for details about the copyright. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
* *
*****************************************************************************
}
{$IFOPT C-}
// Uncomment for local trace
// {$C+}
// {$DEFINE ASSERT_IS_ON}
{$ENDIF}
{off $DEFINE VerboseAutoSizeCtrlData}
{off $DEFINE VerboseMouseBugfix}
{off $DEFINE CHECK_POSITION}
{$IFDEF CHECK_POSITION}
const CheckPostionClassName = 'xxTPage';
const CheckPostionName = 'Edit2';
const CheckPostionParentName = 'EnvVarsPage';
function CheckPosition(AControl: TControl): boolean;
begin
Result:=(CompareText(AControl.ClassName,CheckPostionClassName)=0)
or (CompareText(AControl.Name,CheckPostionName)=0)
or ((AControl.Parent<>nil)
and (CompareText(AControl.Parent.Name,CheckPostionParentName)=0));
end;
{$ENDIF}
{------------------------------------------------------------------------------
Autosizing Helper classes
-------------------------------------------------------------------------------}
type
TAutoSizeBoxOrientation = (asboHorizontal, asboVertical);
PAutoSizeBox = ^TAutoSizeBox;
{ TAutoSizeBox
A TAutoSizeBox is a node in a tree.
A TAutoSizeBox can be a cell. Then it is a leaf in the tree and can have a
Control.
A TAutoSizeBox can be a row or column. Then it has only one Childs array.
A TAutoSizeBox can be a table. Then it has both Childs arrays.
}
TAutoSizeBox = class
public
Control: TControl; // the Control of a leaf node
MinimumSize: array[TAutoSizeBoxOrientation] of integer;
MaximumSize: array[TAutoSizeBoxOrientation] of integer; // 0 means inifinte
PreferredSize: array[TAutoSizeBoxOrientation] of integer;// without theme space
LeftTop: array[TAutoSizeBoxOrientation] of integer;
BorderLeftTop: array[TAutoSizeBoxOrientation] of integer;
BorderRightBottom: array[TAutoSizeBoxOrientation] of integer;
Parent: array[TAutoSizeBoxOrientation] of TAutoSizeBox;
Index: array[TAutoSizeBoxOrientation] of Integer; // index in parent or grandparent
ChildCount: array[TAutoSizeBoxOrientation] of Integer;
Childs: array[TAutoSizeBoxOrientation] of PAutoSizeBox;
NewControlBounds: TRect;
// for nodes
destructor Destroy; override;
procedure Clear;
procedure SetControl(AControl: TControl);
procedure ApplyChildsizingBorders(ChildSizing: TControlChildSizing);
// for rows and columns
procedure AllocateChildsArray(Orientation: TAutoSizeBoxOrientation;
NewChildCount: Integer);
procedure InitSums;
procedure SumLine(Orientation: TAutoSizeBoxOrientation;
DoInit: boolean);
procedure ComputeLeftTops(Orientation: TAutoSizeBoxOrientation);
procedure ResizeChilds(ChildSizing: TControlChildSizing;
Orientation: TAutoSizeBoxOrientation;
TargetSize: integer);
// for tables
procedure AllocateTable(ColCount, RowCount: Integer);
procedure SetTableControls(ListOfControls: TFPList;
ChildSizing: TControlChildSizing);
procedure SumTable;
procedure ResizeTable(ChildSizing: TControlChildSizing;
TargetWidth, TargetHeight: integer);
procedure ComputeTableControlBounds(ChildSizing: TControlChildSizing);
function SetTableControlBounds(ChildSizing: TControlChildSizing
): boolean;// true if changed
function AlignControlsInTable(ListOfControls: TFPList;
ChildSizing: TControlChildSizing;
TargetWidth, TargetHeight: integer;
Apply: boolean): boolean;// true if changed
// debugging
procedure WriteDebugReport(const Title: string);
end;
{ TAutoSizeCtrlData
This class is used by the auto size algorithm, to
1. compute the preferred size of a control given the preferred sizes of
its childs.
Hints about the algorithm:
First it builds a graph of dependencies. That means, for every side
(Left,Top,Right,Bottom) of each child control the anchor control and
space is calculated. Anchor means here direct and indirect anchors.
Indirect anchors are defined by the Align property.
For example a control with Align=alTop is anchored left to the parent,
right to the parent and top to either the parent or another alTop control.
Then it searches for circles and other invalid combinations and repairs
them.
}
TAutoSizeCtrlData = class;
TAutoSizeSideDataState = (
assdfInvalid,
assdfComputing,
assdfUncomputable,// e.g. if [akLeft,akRight]*Anchors = []
assdfValid
);
TAutoSizeSideDistDirection = (
assddLeftTop,
assddRightBottom
);
TAutoSizeSideData = record
CtrlData: TAutoSizeCtrlData;
Side: TAnchorSideReference;
Space: integer;
Distance: array[TAutoSizeSideDistDirection] of integer;
DistanceState: array[TAutoSizeSideDistDirection] of TAutoSizeSideDataState;
end;
TAutoSizeCtrlData = class
private
FChilds: TAvgLvlTree;// tree of TAutoSizeCtrlData
function GetChilds(AControl: TControl): TAutoSizeCtrlData;
procedure DoMoveNonAlignedChilds(Side: TAnchorKind;
var MoveDiff: integer; FindMinimum: boolean);
procedure SetupNonAlignedChilds(MoveNonAlignedChilds: boolean);
procedure AlignChilds;
procedure SetupSpace;
function ComputePositions: boolean;// false if recomputation is needed (a property changed)
public
Control: TControl; // the Control of a leaf node
WinControl: TWinControl;// the Control as TWinControl (else nil)
ChildCount: integer;
Visible: boolean;//= Control.IsControlVisible
PreferredSize: array[TAutoSizeBoxOrientation] of integer;// without theme space
Borders: array[TAnchorKind] of integer;
AdjustedClientBorders: array[TAnchorKind] of integer;// the borderspace created by WinControl.AdjustClientRect
Sides: array[TAnchorKind] of TAutoSizeSideData;
constructor Create(AControl: TControl);
destructor Destroy; override;
procedure Clear;
procedure ComputePreferredClientArea(MoveNonAlignedChilds: boolean;
out PreferredClientWidth, PreferredClientHeight: integer);
procedure FixControlProperties(Child: TControl);
procedure ClearSides;
procedure SetFixedLeftTop(ChildData: TAutoSizeCtrlData; Side: TAnchorKind;
NewLeftTop: integer);
property Childs[AControl: TControl]: TAutoSizeCtrlData read GetChilds; default;
procedure WriteDebugReport(const Title, Prefix: string);
end;
const
SizeBoxOrthogonal: array[TAutoSizeBoxOrientation] of TAutoSizeBoxOrientation
= (asboVertical,asboHorizontal);
{AutoSizeSideDataStateNames: array[TAutoSizeSideDataState] of shortstring = (
'assdfInvalid',
'assdfComputing',
'assdfUncomputable',
'assdfValid'
);}
AutoSizeSideDistDirectionNames: array[TAutoSizeSideDistDirection] of shortstring = (
'assddLeftTop',
'assddRightBottom'
);
function CompareAutoSizeCtrlDatas(Data1, Data2: Pointer): integer;
var
Control1: TControl;
Control2: TControl;
begin
Control1:=TAutoSizeCtrlData(Data1).Control;
Control2:=TAutoSizeCtrlData(Data2).Control;
if Pointer(Control1)>Pointer(Control2) then
Result:=1
else if Pointer(Control1)<Pointer(Control2) then
Result:=-1
else
Result:=0;
end;
function CompareControlWithAutoSizeCtrlData(AControl, AData: Pointer): integer;
var
Control1: TControl;
Control2: TControl;
begin
Control1:=TControl(AControl);
Control2:=TAutoSizeCtrlData(AData).Control;
if Pointer(Control1)>Pointer(Control2) then
Result:=1
else if Pointer(Control1)<Pointer(Control2) then
Result:=-1
else
Result:=0;
end;
{ TAutoSizeCtrlData }
function TAutoSizeCtrlData.GetChilds(AControl: TControl): TAutoSizeCtrlData;
var
AVLNode: TAvgLvlTreeNode;
begin
if AControl=nil then exit(nil);
if AControl=Control then RaiseGDBException('TAutoSizeCtrlData.GetChilds');
if FChilds=nil then
FChilds:=TAvgLvlTree.Create(@CompareAutoSizeCtrlDatas);
AVLNode:=FChilds.FindKey(AControl,@CompareControlWithAutoSizeCtrlData);
if AVLNode<>nil then
Result:=TAutoSizeCtrlData(AVLNode.Data)
else begin
Result:=TAutoSizeCtrlData.Create(AControl);
FChilds.Add(Result);
end;
end;
procedure TAutoSizeCtrlData.AlignChilds;
var
AlignList: TFPList;
AlignBoundaryControls: array[TAnchorKind] of TAutoSizeCtrlData;
procedure DoAlign(TheAlign: TAlign);
var
Child: TControl;
i: Integer;
ChildData: TAutoSizeCtrlData;
a: TAnchorKind;
begin
WinControl.CreateControlAlignList(TheAlign,AlignList,nil);
for i := 0 to AlignList.Count - 1 do begin
Child:=TControl(AlignList[i]);
ChildData:=Childs[Child];
//DebugLn('DoAlign ',DbgSName(Child),' ',dbgs(Child.Align));
for a:=Low(TAnchorKind) to High(TAnchorKind) do
if a in AnchorAlign[TheAlign] then begin
ChildData.Sides[a].CtrlData:=AlignBoundaryControls[a];
if (a in [akLeft,akTop]) = (ChildData.Sides[a].CtrlData=Self) then
ChildData.Sides[a].Side:=asrLeft
else
ChildData.Sides[a].Side:=asrRight;
//DebugLn('DoAlign ',DbgSName(Child),' ',dbgs(a),' ',dbgs(a,ChildData.Sides[a].Side));
end;
case TheAlign of
alTop: AlignBoundaryControls[akTop]:=ChildData;
alBottom: AlignBoundaryControls[akBottom]:=ChildData;
alLeft: AlignBoundaryControls[akLeft]:=ChildData;
alRight: AlignBoundaryControls[akRight]:=ChildData;
alClient: AlignBoundaryControls[akLeft]:=ChildData;
end;
end;
end;
var
a: TAnchorKind;
begin
if ChildCount=0 then exit;
AlignList := TFPList.Create;
try
// align and anchor child controls
for a:=Low(TAnchorKind) to High(TAnchorKind) do
AlignBoundaryControls[a]:=Self;
DoAlign(alTop);
DoAlign(alBottom);
DoAlign(alLeft);
DoAlign(alRight);
DoAlign(alClient);
finally
AlignList.Free;
end;
end;
procedure TAutoSizeCtrlData.SetupSpace;
var
i: Integer;
Child: TControl;
ChildData: TAutoSizeCtrlData;
a: TAnchorKind;
SiblingData: TAutoSizeCtrlData;
NewSpace: LongInt;
begin
for i:=0 to ChildCount-1 do begin
Child:=WinControl.Controls[i];
ChildData:=Childs[Child];
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
if ChildData.Sides[a].CtrlData=Self then begin
// aligned or anchored to parent
if a in [akLeft,akRight] then begin
ChildData.Sides[a].Space:=Max(WinControl.ChildSizing.LeftRightSpacing,
ChildData.Borders[a]);
end else begin
ChildData.Sides[a].Space:=Max(WinControl.ChildSizing.TopBottomSpacing,
ChildData.Borders[a]);
end;
if (Child.Align in [alLeft,alTop,alRight,alBottom,alClient])
and (a in AnchorAlign[Child.Align]) then begin
inc(ChildData.Sides[a].Space,AdjustedClientBorders[a]);
end;
end else if ChildData.Sides[a].CtrlData<>nil then begin
SiblingData:=ChildData.Sides[a].CtrlData;
// aligned or anchored to a sibling
if a in [akLeft,akTop] then begin
NewSpace:=ChildData.Borders[a];
if ChildData.Sides[a].Side=asrRight then begin
NewSpace:=Max(NewSpace,WinControl.ChildSizing.HorizontalSpacing);
if a=akLeft then
NewSpace:=Max(NewSpace,SiblingData.Borders[akRight])
else
NewSpace:=Max(NewSpace,SiblingData.Borders[akBottom]);
end else if ChildData.Sides[a].Side=asrLeft then
else if ChildData.Sides[a].Side=asrCenter then
NewSpace:=0;
ChildData.Sides[a].Space:=NewSpace;
end else begin
NewSpace:=ChildData.Borders[a];
if ChildData.Sides[a].Side=asrTop then begin
NewSpace:=Max(NewSpace,WinControl.ChildSizing.VerticalSpacing);
if a=akRight then
NewSpace:=Max(NewSpace,SiblingData.Borders[akLeft])
else
NewSpace:=Max(NewSpace,SiblingData.Borders[akTop]);
end else if ChildData.Sides[a].Side=asrBottom then
else if ChildData.Sides[a].Side=asrCenter then
NewSpace:=0;
ChildData.Sides[a].Space:=NewSpace;
end;
end else begin
// dangling side
if a in [akLeft,akTop] then begin
ChildData.Sides[a].Side:=asrRight;
end else begin
ChildData.Sides[a].Side:=asrLeft;
end;
if a in [akLeft,akRight] then begin
ChildData.Sides[a].Space:=
Max(WinControl.ChildSizing.LeftRightSpacing,
ChildData.Borders[a]);
end else begin
ChildData.Sides[a].Space:=
Max(WinControl.ChildSizing.TopBottomSpacing,
ChildData.Borders[a]);
end;
end;
end;
end;
end;
function TAutoSizeCtrlData.ComputePositions: boolean;
type
TComputeResult = (
crSuccess,
crCircle,
crFixedCircled
);
function ComputePosition(ChildData: TAutoSizeCtrlData; Side: TAnchorKind;
Direction: TAutoSizeSideDistDirection): TComputeResult;
var
OppositeSide: TAnchorKind;
NewDist: LongInt;
SiblingData: TAutoSizeCtrlData;
NeededSiblingSides: TAnchors;
a: TAnchorKind;
Child: TControl;
IsSideLeftTop, IsOutwards, IsParentInwards: boolean;
CurAnchors: TAnchors;
CurSize: LongInt;
begin
if ChildData.Sides[Side].DistanceState[Direction]
in [assdfValid,assdfUncomputable]
then
exit(crSuccess); // already computed
if ChildData.Sides[Side].DistanceState[Direction]=assdfComputing then begin
DebugLn(['TAutoSizeCtrlData.ComputePositions.ComputePosition CIRCLE detected ',DbgSName(ChildData.Control),' ',dbgs(Side),' ',AutoSizeSideDistDirectionNames[Direction]]);
exit(crCircle); // there is a circle
end;
if ChildData.Sides[Side].DistanceState[Direction]<>assdfInvalid then
raise Exception.Create('TAutoSizeCtrlData.ComputePositions.ComputePosition <>assdfInvalid');
// mark as computing
ChildData.Sides[Side].DistanceState[Direction]:=assdfComputing;
OppositeSide:=OppositeAnchor[Side];
// try to find good distances to the client area for this side
Child:=ChildData.Control;
CurAnchors:=Child.Anchors;
if Child.Align in [alLeft,alTop,alRight,alBottom] then
CurAnchors:=CurAnchors+AnchorAlign[Child.Align];
if (Side in CurAnchors) then begin
// this side is anchored
SiblingData:=ChildData.Sides[Side].CtrlData;
if (SiblingData=nil) or (SiblingData=Self) then begin
// this side is anchored to parent
// Note: SiblingData=nil can happen, if the reference control
// is not visible => use parent as default anchor
case ChildData.Sides[Side].Side of
asrLeft,asrRight: // asrTop=asrLeft,asrBottom=asrRight
begin
IsSideLeftTop:=(Side in [akLeft,akTop]);
IsOutwards:=(Direction=assddLeftTop)=IsSideLeftTop;
IsParentInwards:=(SiblingData=nil)
or ((ChildData.Sides[Side].Side=asrLeft)=IsSideLeftTop);
if not IsParentInwards then begin
// for example: left side is anchored to right side of parent
//DebugLn(['ComputePosition ',DbgSName(Child),' Side=',dbgs(Side),' parent outside anchored, Direction=',AutoSizeSideDistDirectionNames[Direction],' => assdfUncomputable']);
ChildData.Sides[Side].DistanceState[Direction]:=assdfUncomputable;
end else if IsOutwards then begin
// for example: left side is anchored to left side of parent
// and left distance is needed
ChildData.Sides[Side].Distance[Direction]:=ChildData.Sides[Side].Space;
ChildData.Sides[Side].DistanceState[Direction]:=assdfValid;
end else begin
// for example: left side is anchored to left side of parent,
// right distance is needed
if OppositeSide in CurAnchors then begin
// compute opposite side first
Result:=ComputePosition(ChildData,OppositeSide,Direction);
if Result<>crSuccess then begin
DebugLn(['ComputePosition FAILED opposite side: ',DbgSName(Child),' ',dbgs(Side),' ',AutoSizeSideDistDirectionNames[Direction]]);
exit;
end;
if ChildData.Sides[OppositeSide].DistanceState[Direction]<>assdfValid
then begin
ChildData.Sides[Side].DistanceState[Direction]:=assdfUncomputable;
exit;
end;
NewDist:=ChildData.Sides[OppositeSide].Distance[Direction];
end else begin
NewDist:=ChildData.Sides[OppositeSide].Space;
end;
if Side in [akLeft,akRight] then
inc(NewDist,ChildData.PreferredSize[asboHorizontal])
else
inc(NewDist,ChildData.PreferredSize[asboVertical]);
ChildData.Sides[Side].Distance[Direction]:=NewDist;
ChildData.Sides[Side].DistanceState[Direction]:=assdfValid;
end;
end;
asrCenter:
begin
//DebugLn(['ComputePosition ',DbgSName(Child),' Side=',dbgs(Side),' parent anchored, Direction=',AutoSizeSideDistDirectionNames[Direction],' => assdfUncomputable']);
ChildData.Sides[Side].DistanceState[Direction]:=assdfUncomputable;
end;
else
RaiseGDBException('');
end;
end else begin
// this side is anchored to a sibling
// first compute needed sides of sibling
NeededSiblingSides:=[];
case ChildData.Sides[Side].Side of
asrLeft: // asrLeft=asrTop
if Side in [akLeft,akRight] then begin
Include(NeededSiblingSides,akLeft);
end else begin
Include(NeededSiblingSides,akTop);
end;
asrRight: // asrRight=asrBottom
if Side in [akLeft,akRight] then begin
Include(NeededSiblingSides,akRight);
end else begin
Include(NeededSiblingSides,akBottom);
end;
asrCenter:
if Side in [akLeft,akRight] then begin
NeededSiblingSides:=NeededSiblingSides+[akLeft,akRight];
end else begin
NeededSiblingSides:=NeededSiblingSides+[akTop,akBottom];
end;
end;
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
if a in NeededSiblingSides then begin
Result:=ComputePosition(SiblingData,a,Direction);
if (Result=crCircle)
and ((Child.Align in [alNone,alCustom])
or (not (Side in AnchorAlign[Child.Align]))) then
begin
// there is a circle and it can be broken => break it
DebugLn(['ComputePosition breaking CIRCLE ',DbgSName(Child),' - ',DbgSName(SiblingData.Control),' ',dbgs(a),' ',AutoSizeSideDistDirectionNames[Direction]]);
Child.Anchors:=Child.Anchors-[Side];
Result:=crFixedCircled;
end;
if Result<>crSuccess then begin
DebugLn(['ComputePosition FAILED sibling dependency: ',DbgSName(Child),' - ',DbgSName(SiblingData.Control),' Side=',dbgs(Side),' a=',dbgs(a),' ',AutoSizeSideDistDirectionNames[Direction]]);
exit;
end;
if SiblingData.Sides[a].DistanceState[Direction]<>assdfValid then
begin
ChildData.Sides[Side].DistanceState[Direction]:=assdfUncomputable;
exit;
end;
end;
end;
// this side is anchored to a sibling and all needed sibling sides are valid
case ChildData.Sides[Side].Side of
asrLeft,asrRight: // asrLeft=asrTop,asrRight=asrBottom
begin
if ChildData.Sides[Side].Side=asrLeft then begin
if Side in [akLeft,akRight] then
NewDist:=SiblingData.Sides[akLeft].Distance[Direction]
else
NewDist:=SiblingData.Sides[akTop].Distance[Direction];
end else begin
if Side in [akLeft,akRight] then
NewDist:=SiblingData.Sides[akRight].Distance[Direction]
else
NewDist:=SiblingData.Sides[akBottom].Distance[Direction];
end;
if (Direction=assddLeftTop)=(Side in [akLeft,akTop]) then
inc(NewDist,ChildData.Sides[Side].Space)
else
dec(NewDist,ChildData.Sides[Side].Space);
//DebugLn(['ComputePosition ',DbgSName(Child),' - ',DbgSName(SiblingData.Control),' Side=',dbgs(Side),' ',AutoSizeSideDistDirectionNames[Direction],' NewDist=',NewDist]);
end;
asrCenter:
if Side in [akLeft,akRight] then begin
NewDist:=((SiblingData.Sides[akLeft].Distance[Direction]
+SiblingData.Sides[akRight].Distance[Direction]) div 2)
-(ChildData.PreferredSize[asboHorizontal] div 2);
end else begin
NewDist:=((SiblingData.Sides[akTop].Distance[Direction]
+SiblingData.Sides[akBottom].Distance[Direction]) div 2)
-(ChildData.PreferredSize[asboVertical] div 2);
end;
end;
ChildData.Sides[Side].Distance[Direction]:=NewDist;
ChildData.Sides[Side].DistanceState[Direction]:=assdfValid;
end;
end else if (OppositeSide in CurAnchors)
and ((Direction=assddLeftTop) <> (Side in [akLeft,akTop])) then begin
// this side is not anchored, but the opposite is
// e.g. control is anchored to the right
// => compute the opposite side first
Result:=ComputePosition(ChildData,OppositeSide,Direction);
if Result<>crSuccess then begin
//DebugLn(['ComputePosition FAILED computing opposite side: ',DbgSName(Child),' - ',DbgSName(SiblingData.Control),' Side=',dbgs(Side),' ',AutoSizeSideDistDirectionNames[Direction]]);
exit;
end;
case ChildData.Sides[OppositeSide].DistanceState[Direction] of
assdfValid:
begin
// opposite side +- preferred size
NewDist:=ChildData.Sides[OppositeSide].Distance[Direction];
if Side in [akLeft,akRight] then
CurSize:=ChildData.PreferredSize[asboHorizontal]
else
CurSize:=ChildData.PreferredSize[asboVertical];
inc(NewDist,CurSize);
ChildData.Sides[Side].Distance[Direction]:=NewDist;
ChildData.Sides[Side].DistanceState[Direction]:=assdfValid;
end;
assdfUncomputable:
ChildData.Sides[Side].DistanceState[Direction]:=assdfUncomputable;
else
raise Exception.Create('TAutoSizeCtrlData.ComputePositions.ComputePosition assdfValid,assdfUncomputable');
end;
end else begin
// not anchored
if (Direction=assddLeftTop) = (Side in [akLeft,akTop]) then begin
NewDist:=ChildData.Sides[Side].Space;
ChildData.Sides[Side].Distance[Direction]:=NewDist;
ChildData.Sides[Side].DistanceState[Direction]:=assdfValid;
end else begin
//DebugLn(['ComputePosition ',DbgSName(Child),' Side=',dbgs(Side),' not anchored, Direction=',AutoSizeSideDistDirectionNames[Direction],' => assdfUncomputable']);
ChildData.Sides[Side].DistanceState[Direction]:=assdfUncomputable;
end;
end;
if not (ChildData.Sides[Side].DistanceState[Direction]
in [assdfUncomputable,assdfValid])
then begin
DebugLn(['TAutoSizeCtrlData.ComputePositions.ComputePosition ',DbgSName(Child),' Side=',dbgs(Side),' Direction=',AutoSizeSideDistDirectionNames[Direction]]);
raise Exception.Create('TAutoSizeCtrlData.ComputePositions.ComputePosition assdfValid,assdfUncomputable');
end;
Result:=crSuccess;
end;
var
i: Integer;
Child: TControl;
ChildData: TAutoSizeCtrlData;
a: TAnchorKind;
begin
Result:=false;
// for every side try to find a good distance to the client area
for i:=0 to ChildCount-1 do begin
Child:=WinControl.Controls[i];
ChildData:=Childs[Child];
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
if ComputePosition(ChildData,a,assddLeftTop)<>crSuccess then begin
DebugLn(['TAutoSizeCtrlData.ComputePositions Failed to compute LeftTop ',DbgSName(Child),' ',dbgs(a)]);
exit;
end;
if ComputePosition(ChildData,a,assddRightBottom)<>crSuccess then begin
DebugLn(['TAutoSizeCtrlData.ComputePositions Failed to compute RightBottom ',DbgSName(Child),' ',dbgs(a)]);
exit;
end;
end;
end;
Result:=true;
end;
constructor TAutoSizeCtrlData.Create(AControl: TControl);
const
BigInteger = High(Integer) div 4;
var
CurBorders: TRect;
a: TAnchorKind;
AdjustedClientRect: TRect;
begin
//DebugLn(['TAutoSizeCtrlData.Create ',DbgSName(AControl)]);
Control:=AControl;
if Control is TWinControl then begin
WinControl:=TWinControl(Control);
ChildCount:=WinControl.ControlCount;
end else
ChildCount:=0;
Visible:=Control.IsControlVisible;
Control.BorderSpacing.GetSpaceAround(CurBorders);
Borders[akLeft]:=CurBorders.Left;
Borders[akTop]:=CurBorders.Top;
Borders[akRight]:=CurBorders.Right;
Borders[akBottom]:=CurBorders.Bottom;
if WinControl<>nil then begin
AdjustedClientRect:=Rect(0,0,BigInteger,BigInteger);
WinControl.AdjustClientRect(AdjustedClientRect);
AdjustedClientBorders[akLeft]:=AdjustedClientRect.Left;
AdjustedClientBorders[akTop]:=AdjustedClientRect.Top;
AdjustedClientBorders[akRight]:=BigInteger-AdjustedClientRect.Right;
AdjustedClientBorders[akRight]:=BigInteger-AdjustedClientRect.Bottom;
end else begin
for a:=low(TAnchorKind) to high(TAnchorKind) do
AdjustedClientBorders[a]:=0;
end;
end;
destructor TAutoSizeCtrlData.Destroy;
begin
Clear;
FreeAndNil(FChilds);
inherited Destroy;
end;
procedure TAutoSizeCtrlData.Clear;
begin
ClearSides;
if FChilds<>nil then
FChilds.FreeAndClear;
end;
procedure TAutoSizeCtrlData.DoMoveNonAlignedChilds(Side: TAnchorKind;
var MoveDiff: integer; FindMinimum: boolean);
var
i: Integer;
Child: TControl;
MoveDiffValid: Boolean;
ChildData: TAutoSizeCtrlData;
AddSpace: LongInt;
Position: Integer;
begin
MoveDiffValid:=false;
for i:=0 to ChildCount-1 do begin
Child:=WinControl.Controls[i];
ChildData:=Childs[Child];
if (ChildData.Visible)
and (Child.Align=alNone)
and (Side in Child.Anchors) and (Child.AnchorSide[Side].Control=nil)
then begin
// this is a non aligned control
//DebugLn(['TAutoSizeCtrlData.DoMoveNonAlignedChilds Child=',DbgSName(Child),' Side=',dbgs(Side)]);
if FindMinimum then begin
AddSpace:=Child.BorderSpacing.GetSpace(Side);
if Side=akLeft then
AddSpace:=Max(AddSpace,WinControl.ChildSizing.LeftRightSpacing)
else
AddSpace:=Max(AddSpace,WinControl.ChildSizing.TopBottomSpacing);
Position:=Child.GetSidePosition(Side)
-AddSpace
-AdjustedClientBorders[Side];
if (not MoveDiffValid) or (MoveDiff>Position) then
begin
MoveDiff:=Position;
MoveDiffValid:=true;
end;
end else begin
SetFixedLeftTop(ChildData,Side,Child.GetSidePosition(Side)-MoveDiff);
end;
end;
end;
end;
procedure TAutoSizeCtrlData.SetupNonAlignedChilds(MoveNonAlignedChilds: boolean);
var
ChildSizing: TControlChildSizing;
Box: TAutoSizeBox;
y: Integer;
RowBox: TAutoSizeBox;
x: Integer;
ControlBox: TAutoSizeBox;
Child: TControl;
NewBounds: TRect;
ChildData: TAutoSizeCtrlData;
MoveDiff: Integer;
AlignList: TFPList;
r: TRect;
begin
if ChildCount=0 then exit;
if WinControl.ChildSizing.Layout=cclNone then begin
// move the non-aligned controls (i.e. not aligned or fixed anchored)
// Find the leftmost and topmost of those controls
MoveDiff:=0;
DoMoveNonAlignedChilds(akLeft,MoveDiff,true);
//DebugLn(['TAutoSizeCtrlData.ComputePreferredClientArea akLeft MoveDiff=',MoveDiff]);
if not MoveNonAlignedChilds then MoveDiff:=0;
DoMoveNonAlignedChilds(akLeft,MoveDiff,false);
MoveDiff:=0;
DoMoveNonAlignedChilds(akTop,MoveDiff,true);
//DebugLn(['TAutoSizeCtrlData.ComputePreferredClientArea akTop MoveDiff=',MoveDiff]);
if not MoveNonAlignedChilds then MoveDiff:=0;
DoMoveNonAlignedChilds(akTop,MoveDiff,false);
end else begin
// there is an automatic layout for non aligned childs
// use the layout engine, but with static values
ChildSizing:=nil;
Box:=nil;
AlignList:=TFPList.Create;
try
WinControl.CreateControlAlignList(alNone,AlignList,nil);
if AlignList.Count=0 then exit;
ChildSizing:=TControlChildSizing.Create(nil);
Box:=TAutoSizeBox.Create;
// copy current ChildSizing ...
ChildSizing.Assign(WinControl.ChildSizing);
// ... and change it to static layout (i.e. independent of parent size)
ChildSizing.ShrinkHorizontal:=crsAnchorAligning;
ChildSizing.EnlargeHorizontal:=crsAnchorAligning;
ChildSizing.ShrinkVertical:=crsAnchorAligning;
ChildSizing.EnlargeVertical:=crsAnchorAligning;
// compute static layout
r:=WinControl.GetLogicalClientRect;
Box.AlignControlsInTable(AlignList,ChildSizing,r.Right,r.Bottom,false);
//Box.WriteDebugReport('TAutoSizeCtrlData.SetupNonAlignedChilds');
// transfer the coords of the layout
for y:=0 to Box.ChildCount[asboVertical]-1 do begin
RowBox:=Box.Childs[asboVertical][y];
for x:=0 to RowBox.ChildCount[asboHorizontal]-1 do begin
ControlBox:=RowBox.Childs[asboHorizontal][x];
Child:=ControlBox.Control;
if Child=nil then continue;
NewBounds:=ControlBox.NewControlBounds;
//DebugLn(['TAutoSizeCtrlData.SetupNonAlignedChilds ',DbgSName(Child),' ',dbgs(NewBounds)]);
ChildData:=Childs[Child];
// set left
SetFixedLeftTop(ChildData,akLeft,NewBounds.Left);
// set width
ChildData.PreferredSize[asboHorizontal]:=NewBounds.Right-NewBounds.Left;
// set top
SetFixedLeftTop(ChildData,akTop,NewBounds.Top);
// set height
ChildData.PreferredSize[asboVertical]:=NewBounds.Bottom-NewBounds.Top;
end;
end;
finally
ChildSizing.Free;
Box.Free;
AlignList.Free;
end;
end;
end;
procedure TAutoSizeCtrlData.ComputePreferredClientArea(
MoveNonAlignedChilds: boolean;
out PreferredClientWidth, PreferredClientHeight: integer);
{ if MoveNonAlignedChilds=true then all non-aligned childs will be moved in
parallel, so that at least one child is positioned left most and one child
is positioned top most.
Type of controls:
1. layout: the left and top side of the control has only designed position
and Parent.ChildSizing.Layout <> cclNone.
That means: Align=alNone, Anchors=[akLeft,akTop],
AnchorSide[akLeft/akTop].Control=nil, Parent.ChildSizing.Layout <> cclNone
2. non-aligned: the left+top side of the control has only a designed position.
That means: Align=alNone, akLeft is set, AnchorSide[akLeft].Control=nil
and Parent.ChildSizing.Layout=cclNone
Same for akTop.
3. Aligned: Align<>alNone
These are put consecutively into the remaining space.
BorderSpacing and AdjustClientRect defines the space.
The aligned sides automatically set the Anchors and the AnchorSide.Control
to nil.
alLeft,alRight,alTop,alBottom have one free side, which can be anchored.
4. centered: akLeft and akRight are not set
5. one side anchored: akLeft is set and akRight is not
OR akRight is set and akLeft is not
5.1 anchored to a side (asrLeft,asrRight)
5.2 anchored to a center (asrCenter)
6. both sides anchored: akLeft and akRight not
Note: asrCenter is not allowed here
Circles and invalid combinations will be automatically fixed.
}
procedure InitPreferredSizes;
var
i: Integer;
Child: TControl;
ChildData: TAutoSizeCtrlData;
CurAnchors: TAnchors;
CurPreferredWidth: integer;
CurPreferredHeight: integer;
UseCurrentWidth: Boolean;
UseCurrentHeight: Boolean;
NewWidth: LongInt;
NewHeight: LongInt;
begin
for i:=0 to ChildCount-1 do begin
Child:=WinControl.Controls[i];
ChildData:=Childs[Child];
if ChildData.Visible then begin
CurAnchors:=Child.Anchors;
if Child.Align in [alLeft,alRight,alTop,alBottom,alClient] then
CurAnchors:=CurAnchors+AnchorAlign[Child.Align];
// check if the current Width and/or Height of the Child control can be
// used. For example: The current Width can be used, if it is independent
// of the parent's width.
UseCurrentWidth:=true;
if Child.AutoSize
or ([akLeft,akRight]*CurAnchors=[akLeft,akRight]) then
UseCurrentWidth:=false;
UseCurrentHeight:=true;
if Child.AutoSize
or ([akTop,akBottom]*CurAnchors=[akTop,akBottom]) then
UseCurrentHeight:=false;
if (not UseCurrentWidth) or (not UseCurrentHeight) then
Child.GetPreferredSize(CurPreferredWidth,CurPreferredHeight,true,true);
if UseCurrentWidth then
NewWidth:=Child.Width
else if CurPreferredWidth>0 then
NewWidth:=CurPreferredWidth
else
NewWidth:=Child.GetDefaultWidth;
NewWidth:=Child.Constraints.MinMaxWidth(NewWidth);
if UseCurrentHeight then
NewHeight:=Child.Height
else if CurPreferredHeight>0 then
NewHeight:=CurPreferredHeight
else
NewHeight:=Child.GetDefaultHeight;
NewHeight:=Child.Constraints.MinMaxHeight(NewHeight);
end else begin
NewWidth:=0;
NewHeight:=0;
end;
ChildData.PreferredSize[asboHorizontal]:=NewWidth;
ChildData.PreferredSize[asboVertical]:=NewHeight;
//DebugLn(['InitPreferredSizes Child=',DbgSName(Child),' PrefSize=',NewWidth,',',NewHeight]);
end;
end;
procedure GetSideAnchor(ChildData: TAutoSizeCtrlData; a: TAnchorKind);
var
Child: TControl;
ReferenceControl: TControl;
ReferenceSide: TAnchorSideReference;
Position: Integer;
begin
Child:=ChildData.Control;
Child.AnchorSide[a].GetSidePosition(ReferenceControl,ReferenceSide,Position);
//DebugLn(['GetSideAnchor Child=',DbgSName(Child),', a=',dbgs(a),' ReferenceControl=',DbgSName(ReferenceControl),' ReferenceSide=',dbgs(a,ReferenceSide)]);
if ReferenceControl=Control then
ChildData.Sides[a].CtrlData:=Self
else if (ReferenceControl<>nil) and (ReferenceControl.Parent=Control) then
ChildData.Sides[a].CtrlData:=Childs[ReferenceControl];
ChildData.Sides[a].Side:=ReferenceSide;
//if ChildData.Sides[a].CtrlData<>nil then DebugLn(['GetSideAnchor AAA1 Child=',DbgSName(Child),', a=',dbgs(a),' ReferenceControl=',DbgSName(ChildData.Sides[a].CtrlData.Control),' ReferenceSide=',dbgs(a,ChildData.Sides[a].Side)]);
end;
var
i: Integer;
VisibleCount: Integer;
Child: TControl;
ChildData: TAutoSizeCtrlData;
a: TAnchorKind;
CurNeededClientWH: Integer;
begin
PreferredClientWidth:=0;
PreferredClientHeight:=0;
if ChildCount=0 then exit;
// fix control properties
// check if there are visible childs
VisibleCount:=0;
for i:=0 to ChildCount-1 do begin
Child:=WinControl.Controls[i];
if Child.Align=alCustom then begin
// this is not LCL business
exit;
end;
FixControlProperties(Child);
ChildData:=Childs[Child];
if ChildData.Visible then
inc(VisibleCount);
end;
//DebugLn(['TAutoSizeCtrlData.ComputePreferredClientArea ',DbgSName(Control),' VisibleCount=',VisibleCount]);
if VisibleCount=0 then begin
// nothing to do
exit;
end;
InitPreferredSizes;
repeat
// init dependencies
for i:=0 to ChildCount-1 do begin
Child:=WinControl.Controls[i];
ChildData:=Childs[Child];
ChildData.ClearSides;
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
ChildData.Sides[a].Side:=asrLeft;
if (Child.Align in [alLeft,alRight,alTop,alBottom,alClient])
and (a in AnchorAlign[Child.Align]) then begin
// this is an aligned side
// => the dependencies will be setup later in AlignChilds
end else if a in Child.Anchors then begin
// this is an anchored side
GetSideAnchor(ChildData,a);
end else begin
// this is a dangling side
end;
end;
end;
SetupNonAlignedChilds(MoveNonAlignedChilds);
// setup the dependencies for Aligned controls
AlignChilds;
// setup space for dependencies
SetupSpace;
{$IFDEF VerboseAutoSizeCtrlData}
WriteDebugReport('Space completed','');
{$ENDIF}
// calculate the needed positions for all childs
until ComputePositions;
{$IFDEF VerboseAutoSizeCtrlData}
WriteDebugReport('Positions completed','');
{$ENDIF}
// compute needed clientwidth/clientheight
for i:=0 to ChildCount-1 do begin
Child:=WinControl.Controls[i];
ChildData:=Childs[Child];
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
if (ChildData.Sides[a].DistanceState[assddLeftTop]=assdfValid)
and (ChildData.Sides[a].DistanceState[assddRightBottom]=assdfValid)
then begin
CurNeededClientWH:=ChildData.Sides[a].Distance[assddLeftTop]
+ChildData.Sides[a].Distance[assddRightBottom];
if a in [akLeft,akRight] then begin
if PreferredClientWidth<CurNeededClientWH then
PreferredClientWidth:=CurNeededClientWH;
end else begin
if PreferredClientHeight<CurNeededClientWH then
PreferredClientHeight:=CurNeededClientWH;
end;
end;
end;
end;
{$IFDEF VerboseAutoSizeCtrlData}
DebugLn(['TAutoSizeCtrlData.ComputePreferredClientArea END ',DbgSName(Control),' PreferredClientWidth/height=',PreferredClientWidth,',',PreferredClientHeight]);
{$ENDIF}
end;
procedure TAutoSizeCtrlData.FixControlProperties(Child: TControl);
var
a: TAnchorKind;
begin
// check that all anchor-controls are siblings or the parent
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
if Child.AnchorSide[a].Control=nil then continue;
if Child.AnchorSide[a].Control=Control then continue;
if (Child.AnchorSide[a].Control=Child)
or (Child.AnchorSide[a].Control.Parent<>Control) then begin
DebugLn(['TAutoSizeCtrlData.FixControlProperties ',DbgSName(Child),' a=',dbgs(a),' old=',DbgSName(Child.AnchorSide[a].Control),' new=nil']);
Child.AnchorSide[a].Control:=nil;
end;
end;
if Child.Align in [alLeft,alRight,alTop,alBottom,alClient] then begin
// the aligned sides must be anchored
Child.Anchors:=Child.Anchors+AnchorAlign[Child.Align];
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
if a in AnchorAlign[Child.Align] then begin
// the aligned sides can not be anchored to a control
if Child.AnchorSide[a].Control<>nil then
DebugLn(['TAutoSizeCtrlData.FixControlProperties aligned sides can not be anchored ',DbgSName(Child),' a=',dbgs(a),' old=',DbgSName(Child.AnchorSide[a].Control),' new=nil']);
Child.AnchorSide[a].Control:=nil;
end;
if Child.AnchorSide[a].Side=asrCenter then begin
// an aligned control can not be centered
DebugLn(['TAutoSizeCtrlData.FixControlProperties aligned control can not be centered ',DbgSName(Child),' a=',dbgs(a)]);
Child.AnchorSide[a].Side:=asrLeft;
if not (a in AnchorAlign[Child.Align]) then begin
Child.Anchors:=Child.Anchors-[a];
Child.AnchorSide[a].Control:=nil;
end;
end;
end;
end else begin
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
if (a in Child.Anchors)
and (Child.AnchorSide[a].Side=asrCenter) then begin
if Child.AnchorSide[a].Control<>nil then begin
// the control should be centered relative to another control
if a in [akLeft,akTop] then begin
// un-anchor the other side
if OppositeAnchor[a] in Child.Anchors then
DebugLn(['TAutoSizeCtrlData.FixControlProperties control is center-anchored -> unanchor opposite side: ',DbgSName(Child),' a=',dbgs(a)]);
Child.Anchors:=Child.Anchors-[OppositeAnchor[a]];
Child.AnchorSide[OppositeAnchor[a]].Control:=nil;
end else begin
// the centering was setup via the right,bottom
// => normalize it to center via the Left,Top
DebugLn(['TAutoSizeCtrlData.FixControlProperties control is center-anchored -> normalize it to use Left,Top instead of Bottom,Right: ',DbgSName(Child),' a=',dbgs(a)]);
Child.AnchorSide[OppositeAnchor[a]].Control:=Child.AnchorSide[a].Control;
Child.AnchorSide[OppositeAnchor[a]].Side:=asrCenter;
Child.AnchorSide[a].Control:=nil;
Child.AnchorSide[a].Side:=asrLeft;
Child.Anchors:=Child.Anchors+[OppositeAnchor[a]]-[a];
end;
end else begin
// the asrCenter is not active => ok
end;
end;
end;
end;
end;
procedure TAutoSizeCtrlData.ClearSides;
var
a: TAnchorKind;
d: TAutoSizeSideDistDirection;
begin
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
FillChar(Sides[a],SizeOf(TAutoSizeSideData),0);
for d:=Low(TAutoSizeSideDistDirection) to High(TAutoSizeSideDistDirection) do
Sides[a].DistanceState[d]:=assdfInvalid;
end;
end;
procedure TAutoSizeCtrlData.SetFixedLeftTop(ChildData: TAutoSizeCtrlData;
Side: TAnchorKind; NewLeftTop: integer);
begin
ChildData.Sides[Side].CtrlData:=Self;
ChildData.Sides[Side].Side:=asrLeft;
ChildData.Sides[Side].Space:=NewLeftTop;
ChildData.Sides[Side].Distance[assddLeftTop]:=NewLeftTop;
ChildData.Sides[Side].DistanceState[assddLeftTop]:=assdfValid;
end;
procedure TAutoSizeCtrlData.WriteDebugReport(const Title, Prefix: string);
function GetDistance(a: TAnchorKind; d: TAutoSizeSideDistDirection): string;
begin
case Sides[a].DistanceState[d] of
assdfInvalid: Result:='invalid';
assdfComputing: Result:='computing';
assdfUncomputable: Result:='uncomputable';
assdfValid: Result:=dbgs(Sides[a].Distance[d]);
else Result:='???';
end;
end;
function GetSideControl(a: TAnchorKind): string;
begin
if Sides[a].CtrlData<>nil then
Result:=DbgSName(Sides[a].CtrlData.Control)
else
Result:='nil';
end;
var
a: TAnchorKind;
i: Integer;
begin
if Title<>'' then
DebugLn([Prefix,'TAutoSizeCtrlData.WriteDebugReport ',Title]);
DebugLn([Prefix,' Control=',DbgSName(Control),' ChildCount=',ChildCount,' Visible=',Visible,' Anchors=',dbgs(Control.Anchors),' Align=',dbgs(Control.Align)]);
Debugln([Prefix,' PreferredSize=',PreferredSize[asboHorizontal],',',PreferredSize[asboVertical]]);
DebugLn([Prefix,' Borders=l=',Borders[akLeft],',t=',Borders[akTop],',r=',Borders[akRight],',b=',Borders[akBottom]]);
DebugLn([Prefix,' AdjustedClientBorders=l=',AdjustedClientBorders[akLeft],',t=',AdjustedClientBorders[akTop],',r=',AdjustedClientBorders[akRight],',b=',AdjustedClientBorders[akBottom]]);
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
DebugLn([Prefix,' Side ',dbgs(a),' Control=',GetSideControl(a),
' RefSide=',dbgs(a,Sides[a].Side),
' Space=',Sides[a].Space,
' DistLT=',GetDistance(a,assddLeftTop),
' DistBR=',GetDistance(a,assddRightBottom)]);
end;
for i:=0 to ChildCount-1 do
Childs[WinControl.Controls[i]].WriteDebugReport('',Prefix+dbgs(i)+': ');
end;
{ TAutoSizeBox }
procedure TAutoSizeBox.SetControl(AControl: TControl);
var
Border: TRect;
begin
Control:=AControl;
MinimumSize[asboHorizontal]:=Control.Constraints.EffectiveMinWidth;
MinimumSize[asboVertical]:=Control.Constraints.EffectiveMinHeight;
MaximumSize[asboHorizontal]:=Control.Constraints.EffectiveMaxWidth;
MaximumSize[asboVertical]:=Control.Constraints.EffectiveMaxHeight;
Control.GetPreferredSize(PreferredSize[asboHorizontal],
PreferredSize[asboVertical],
true, // without constraints
true // with theme space
);
// apply constraints
if PreferredSize[asboHorizontal]>0 then
PreferredSize[asboHorizontal]:=
Control.Constraints.MinMaxWidth(PreferredSize[asboHorizontal]);
if PreferredSize[asboVertical]>0 then
PreferredSize[asboVertical]:=
Control.Constraints.MinMaxHeight(PreferredSize[asboVertical]);
if (Control.AutoSize or (Control.BorderSpacing.CellAlignHorizontal<>ccaFill))
and (PreferredSize[asboHorizontal]>0)
then begin
// the control.width is fixed to its preferred width
MaximumSize[asboHorizontal]:=PreferredSize[asboHorizontal];
end;
if (Control.AutoSize or (Control.BorderSpacing.CellAlignVertical<>ccaFill))
and (PreferredSize[asboVertical]>0)
then begin
// the control.height is fixed to its preferred height
MaximumSize[asboVertical]:=PreferredSize[asboVertical];
end;
// if no preferred size is valid use the class defaults
if PreferredSize[asboHorizontal]<=0 then
PreferredSize[asboHorizontal]:=
Control.Constraints.MinMaxWidth(Control.GetControlClassDefaultSize.X);
if PreferredSize[asboVertical]<=0 then
PreferredSize[asboVertical]:=
Control.Constraints.MinMaxHeight(Control.GetControlClassDefaultSize.X);
//DebugLn(['TAutoSizeBox.SetControl ',DbgSName(Control),' ',PreferredSize[asboHorizontal]]);
Control.BorderSpacing.GetSpaceAround(Border);
BorderLeftTop[asboHorizontal]:=Border.Left;
BorderLeftTop[asboVertical]:=Border.Top;
BorderRightBottom[asboHorizontal]:=Border.Right;
BorderRightBottom[asboVertical]:=Border.Bottom;
end;
procedure TAutoSizeBox.AllocateChildsArray(Orientation: TAutoSizeBoxOrientation;
NewChildCount: Integer);
var
Size: Integer;
begin
Size:=NewChildCount*SizeOf(Pointer);
ReallocMem(Childs[Orientation],Size);
if Size>0 then
FillChar(Childs[Orientation][0],Size,0);
ChildCount[Orientation]:=NewChildCount;
end;
procedure TAutoSizeBox.AllocateTable(ColCount, RowCount: Integer);
{ This creates a ColCount x RowCount number of cells,
and a Row of Columns and a Column of Rows.
+-++-++-++-+ +----------+
| || || || | | |
| || || || | +----------+
| || || || | +----------+
| || || || | | |
| || || || | +----------+
| || || || | +----------+
| || || || | | |
+-++-++-++-+ +----------+
}
var
x, y: Integer;
RowBox: TAutoSizeBox;
ColBox: TAutoSizeBox;
CellBox: TAutoSizeBox;
begin
AllocateChildsArray(asboHorizontal,ColCount);
AllocateChildsArray(asboVertical,RowCount);
// create columns
for x:=0 to ColCount-1 do begin
ColBox:=TAutoSizeBox.Create;
Childs[asboHorizontal][x]:=ColBox;
ColBox.AllocateChildsArray(asboVertical,RowCount);
ColBox.Parent[asboHorizontal]:=Self;
ColBox.Index[asboHorizontal]:=x;
ColBox.Index[asboVertical]:=-1;
end;
// create rows
for y:=0 to RowCount-1 do begin
RowBox:=TAutoSizeBox.Create;
Childs[asboVertical][y]:=RowBox;
RowBox.AllocateChildsArray(asboHorizontal,ColCount);
RowBox.Parent[asboVertical]:=Self;
RowBox.Index[asboHorizontal]:=-1;
RowBox.Index[asboVertical]:=y;
end;
// create cells
for y:=0 to RowCount-1 do begin
RowBox:=Childs[asboVertical][y];
for x:=0 to ColCount-1 do begin
ColBox:=Childs[asboHorizontal][x];
CellBox:=TAutoSizeBox.Create;
RowBox.Childs[asboHorizontal][x]:=CellBox;
ColBox.Childs[asboVertical][y]:=CellBox;
CellBox.Parent[asboHorizontal]:=RowBox;
CellBox.Parent[asboVertical]:=ColBox;
CellBox.Index[asboHorizontal]:=x;
CellBox.Index[asboVertical]:=y;
end;
end;
end;
procedure TAutoSizeBox.SetTableControls(ListOfControls: TFPList;
ChildSizing: TControlChildSizing);
var
i: Integer;
Row: LongInt;
Col: LongInt;
ChildControl: TControl;
ChildBox: TAutoSizeBox;
RowCount: LongInt;
ColCount: Integer;
begin
// allocate table
case ChildSizing.Layout of
cclLeftToRightThenTopToBottom:
begin
ColCount:=Max(1,ChildSizing.ControlsPerLine);
RowCount:=((ListOfControls.Count-1) div ColCount)+1;
end;
cclTopToBottomThenLeftToRight:
begin
RowCount:=Max(1,ChildSizing.ControlsPerLine);
ColCount:=((ListOfControls.Count-1) div RowCount)+1;
end;
else
raise Exception.Create('TAutoSizeBox.SetTableControls TODO');
end;
AllocateTable(ColCount,RowCount);
// set controls
for i:=0 to ListOfControls.Count-1 do begin
ChildControl:=TControl(ListOfControls[i]);
case ChildSizing.Layout of
cclLeftToRightThenTopToBottom:
begin
Row:=i div ChildCount[asboHorizontal];
Col:=i mod ChildCount[asboHorizontal];
ChildBox:=Childs[asboHorizontal][Col].Childs[asboVertical][Row];
ChildBox.SetControl(ChildControl);
ChildBox.ApplyChildsizingBorders(ChildSizing);
end;
cclTopToBottomThenLeftToRight:
begin
Col:=i div ChildCount[asboVertical];
Row:=i mod ChildCount[asboVertical];
ChildBox:=Childs[asboVertical][Row].Childs[asboHorizontal][Col];
ChildBox.SetControl(ChildControl);
ChildBox.ApplyChildsizingBorders(ChildSizing);
end;
end;
end;
end;
procedure TAutoSizeBox.ApplyChildsizingBorders(ChildSizing: TControlChildSizing
);
var
MinBorder: LongInt;
begin
// left border
if (Parent[asboHorizontal]=nil) or (Index[asboHorizontal]=0) then
MinBorder:=ChildSizing.LeftRightSpacing
else
MinBorder:=ChildSizing.HorizontalSpacing;
BorderLeftTop[asboHorizontal]:=Max(BorderLeftTop[asboHorizontal],MinBorder);
// right border
if (Parent[asboHorizontal]=nil)
or (Index[asboHorizontal]=Parent[asboHorizontal].ChildCount[asboHorizontal]-1)
then
MinBorder:=ChildSizing.LeftRightSpacing
else
MinBorder:=ChildSizing.HorizontalSpacing;
BorderRightBottom[asboHorizontal]:=Max(BorderRightBottom[asboHorizontal],
MinBorder);
// top border
if (Parent[asboVertical]=nil) or (Index[asboVertical]=0) then
MinBorder:=ChildSizing.TopBottomSpacing
else
MinBorder:=ChildSizing.VerticalSpacing;
BorderLeftTop[asboVertical]:=Max(BorderLeftTop[asboVertical],MinBorder);
// bottom border
if (Parent[asboVertical]=nil)
or (Index[asboVertical]=Parent[asboVertical].ChildCount[asboVertical]-1)
then
MinBorder:=ChildSizing.TopBottomSpacing
else
MinBorder:=ChildSizing.VerticalSpacing;
BorderRightBottom[asboVertical]:=Max(BorderRightBottom[asboVertical],
MinBorder);
end;
procedure TAutoSizeBox.InitSums;
procedure Init(o: TAutoSizeBoxOrientation);
var
FirstChild: TAutoSizeBox;
begin
if ChildCount[o]>0 then begin
FirstChild:=Childs[o][0];
MaximumSize[o]:=FirstChild.MaximumSize[o];
MinimumSize[o]:=FirstChild.MinimumSize[o];
PreferredSize[o]:=FirstChild.PreferredSize[o];
BorderLeftTop[o]:=FirstChild.BorderLeftTop[o];
BorderRightBottom[o]:=FirstChild.BorderRightBottom[o];
end else begin
MaximumSize[o]:=0;
MinimumSize[o]:=0;
PreferredSize[o]:=0;
BorderLeftTop[o]:=0;
BorderRightBottom[o]:=0;
end;
end;
begin
Init(asboHorizontal);
Init(asboVertical);
end;
procedure TAutoSizeBox.SumLine(Orientation: TAutoSizeBoxOrientation;
DoInit: boolean);
// total orientated minimum is the sum of all minimums plus borders
// total orientated maximum is the sum of all maximums plus borders
// total orientated preferred is the sum of all preferred plus borders
// total othogonal minimum is the maximum of all minimums
// total othogonal maximum is the minimum of all maximums
// total othogonal preferred is the maximum of all preferred
var
i: Integer;
Orthogonal: TAutoSizeBoxOrientation;
CurChild: TAutoSizeBox;
CurBorder: integer;
LastChild: TAutoSizeBox;
begin
if DoInit then InitSums;
Orthogonal:=SizeBoxOrthogonal[Orientation];
if ChildCount[Orientation]>0 then begin
for i:=0 to ChildCount[Orientation]-1 do begin
CurChild:=Childs[Orientation][i];
// add border in Orientation
CurBorder:=CurChild.BorderLeftTop[Orientation];
if i>0 then
CurBorder:=Max(Childs[Orientation][i-1].BorderRightBottom[Orientation],
CurBorder);
if MaximumSize[Orientation]>0 then begin
inc(MaximumSize[Orientation],CurBorder);
end;
inc(MinimumSize[Orientation],CurBorder);
inc(PreferredSize[Orientation],CurBorder);
// add item size in Orientation
if MaximumSize[Orientation]>0 then begin
if CurChild.MaximumSize[Orientation]>0 then
inc(MaximumSize[Orientation],CurChild.MaximumSize[Orientation])
else
MaximumSize[Orientation]:=0;
end;
inc(MinimumSize[Orientation],CurChild.MinimumSize[Orientation]);
inc(PreferredSize[Orientation],CurChild.PreferredSize[Orientation]);
// maximize in Orthogonal
if MaximumSize[Orthogonal]>0 then begin
if CurChild.MaximumSize[Orthogonal]>0 then
MaximumSize[Orthogonal]:=Max(MaximumSize[Orthogonal],
CurChild.MaximumSize[Orthogonal])
else
MaximumSize[Orthogonal]:=0;
end;
MinimumSize[Orthogonal]:=Max(MinimumSize[Orthogonal],
CurChild.MinimumSize[Orthogonal]);
PreferredSize[Orthogonal]:=Max(PreferredSize[Orthogonal],
CurChild.PreferredSize[Orthogonal]);
BorderLeftTop[Orthogonal]:=Max(BorderLeftTop[Orthogonal],
CurChild.BorderLeftTop[Orthogonal]);
BorderRightBottom[Orthogonal]:=Max(BorderRightBottom[Orthogonal],
CurChild.BorderRightBottom[Orthogonal]);
end;
// last border
LastChild:=Childs[Orientation][ChildCount[Orientation]-1];
BorderRightBottom[Orientation]:=LastChild.BorderRightBottom[Orientation];
end;
end;
procedure TAutoSizeBox.SumTable;
var
x: Integer;
ColBox: TAutoSizeBox;
y: Integer;
RowBox: TAutoSizeBox;
begin
// sum items in rows
for y:=0 to ChildCount[asboVertical]-1 do begin
RowBox:=Childs[asboVertical][y];
RowBox.SumLine(asboHorizontal,true);
end;
// sum items in columns
for x:=0 to ChildCount[asboHorizontal]-1 do begin
ColBox:=Childs[asboHorizontal][x];
ColBox.SumLine(asboVertical,true);
end;
// sum rows
SumLine(asboVertical,true);
// sum columns
SumLine(asboHorizontal,false);
end;
procedure TAutoSizeBox.ComputeLeftTops(Orientation: TAutoSizeBoxOrientation);
var
i: Integer;
Child: TAutoSizeBox;
CurLeftTop: Integer;
begin
CurLeftTop:=0;
for i:=0 to ChildCount[Orientation]-1 do begin
Child:=Childs[Orientation][i];
if i=0 then
inc(CurLeftTop,Child.BorderLeftTop[Orientation]);
Child.LeftTop[Orientation]:=CurLeftTop;
inc(CurLeftTop,Child.PreferredSize[Orientation]);
inc(CurLeftTop,Child.BorderRightBottom[Orientation]);
end;
end;
procedure TAutoSizeBox.ResizeChilds(ChildSizing: TControlChildSizing;
Orientation: TAutoSizeBoxOrientation; TargetSize: integer);
type
TResizeFactor = record
Scale: double;
Offset: integer;
end;
var
EnlargeStyle: TChildControlResizeStyle;
ShrinkStyle: TChildControlResizeStyle;
CurSize: LongInt;
function GetChildTotalSize: integer;
// computes the total preferred size of all childs of this Orientation
var
i: Integer;
Child: TAutoSizeBox;
begin
Result:=0;
for i:=0 to ChildCount[Orientation]-1 do begin
Child:=Childs[Orientation][i];
if i=0 then
inc(Result,Child.BorderLeftTop[Orientation]);
if Child.PreferredSize[Orientation]<1 then
Child.PreferredSize[Orientation]:=1;
inc(Result,Child.PreferredSize[Orientation]);
inc(Result,Child.BorderRightBottom[Orientation]);
end;
end;
procedure GetChildMaxResize(out Factor: TResizeFactor;
out ResizeableCount: integer);
// returns the number of childs/gaps, that can grow (ResizeableCount)
// and the maximum factor, by which the childs/gaps can grow (TResizeFactor)
var
i: Integer;
CurScale: Double;
CurOffset: LongInt;
Child: TAutoSizeBox;
begin
Factor.Scale:=0;
Factor.Offset:=0;
ResizeableCount:=0;
case EnlargeStyle of
crsAnchorAligning:
exit; // no resizing
crsScaleChilds,crsHomogenousChildResize:
for i:=0 to ChildCount[Orientation]-1 do begin
Child:=Childs[Orientation][i];
if (Child.MaximumSize[Orientation]>0)
and (Child.PreferredSize[Orientation]>=Child.MaximumSize[Orientation])
then begin
// this child can not be further enlarged
continue;
end;
inc(ResizeableCount);
case EnlargeStyle of
crsScaleChilds, crsHomogenousChildResize:
begin
if Child.MaximumSize[Orientation]=0 then begin
CurScale:=double(TargetSize);
CurOffset:=TargetSize;
end else begin
CurScale:=double(Child.MaximumSize[Orientation])
/Child.PreferredSize[Orientation];
CurOffset:=Child.MaximumSize[Orientation]
-Child.PreferredSize[Orientation];
end;
if (Factor.Offset=0) or (Factor.Offset>CurOffset) then begin
Factor.Scale:=CurScale;
Factor.Offset:=CurOffset;
end;
end;
end;
end;
crsHomogenousSpaceResize:
if ChildCount[Orientation]>0 then begin
Factor.Scale:=double(TargetSize);
Factor.Offset:=TargetSize;
ResizeableCount:=ChildCount[Orientation]+1;
end;
else
raise Exception.Create('TAutoSizeBox.ResizeChilds');
end;
end;
procedure EnlargeChilds(const Factor: TResizeFactor);
var
i: Integer;
Child: TAutoSizeBox;
DiffSize: Integer;
NewSize: LongInt;
OldSize: LongInt;
begin
for i:=0 to ChildCount[Orientation]-1 do begin
if TargetSize=CurSize then break;
Child:=Childs[Orientation][i];
if (Child.MaximumSize[Orientation]<0)
and (Child.PreferredSize[Orientation]>=Child.MaximumSize[Orientation])
then begin
// this child can not be further enlarged
continue;
end;
case EnlargeStyle of
crsScaleChilds:
begin
// scale PreferredSize
DiffSize:=TargetSize-CurSize;
OldSize:=Child.PreferredSize[Orientation];
NewSize:=round(double(OldSize)*Factor.Scale);
NewSize:=Min(OldSize+DiffSize,Max(OldSize+1,NewSize));
inc(CurSize,NewSize-OldSize);
Child.PreferredSize[Orientation]:=NewSize;
end;
crsHomogenousChildResize:
begin
// add to PreferredSize
DiffSize:=TargetSize-CurSize;
OldSize:=Child.PreferredSize[Orientation];
NewSize:=Min(OldSize+Factor.Offset,OldSize+DiffSize);
inc(CurSize,NewSize-OldSize);
Child.PreferredSize[Orientation]:=NewSize;
end;
crsHomogenousSpaceResize:
begin
if i=0 then begin
// add to left/top border
DiffSize:=TargetSize-CurSize;
OldSize:=Child.BorderLeftTop[Orientation];
NewSize:=Min(OldSize+Factor.Offset,OldSize+DiffSize);
inc(CurSize,NewSize-OldSize);
Child.BorderLeftTop[Orientation]:=NewSize;
end;
// add to right/bottom border
DiffSize:=TargetSize-CurSize;
OldSize:=Child.BorderRightBottom[Orientation];
NewSize:=Min(OldSize+Factor.Offset,OldSize+DiffSize);
inc(CurSize,NewSize-OldSize);
Child.BorderRightBottom[Orientation]:=NewSize;
if i<ChildCount[Orientation]-1 then
Child.BorderLeftTop[Orientation]:=NewSize;
end;
end;
end;
end;
procedure GetChildMinResize(out Factor: TResizeFactor;
out ResizeableCount: integer);
// returns the number of childs/gaps, that can shrink (ResizeableCount)
// and the maximum factor, by which the childs/gaps can shrink (TResizeFactor)
var
i: Integer;
CurScale: Double;
CurOffset: LongInt;
Child: TAutoSizeBox;
begin
Factor.Scale:=0;
Factor.Offset:=0;
ResizeableCount:=0;
case ShrinkStyle of
crsAnchorAligning:
exit; // no resizing
crsScaleChilds,crsHomogenousChildResize:
for i:=0 to ChildCount[Orientation]-1 do begin
Child:=Childs[Orientation][i];
if (Child.PreferredSize[Orientation]<=Child.MinimumSize[Orientation])
or (Child.PreferredSize[Orientation]<=1)
then begin
// this child can not be further shrinked
continue;
end;
inc(ResizeableCount);
case ShrinkStyle of
crsScaleChilds:
begin
{$IFDEF CPU64}
{$NOTE remove this workaround, when compiler bug fixed}
if Child.MinimumSize[Orientation]=0 then
CurScale:=0
else
CurScale:=double(Child.MinimumSize[Orientation])
/Child.PreferredSize[Orientation];
{$ELSE}
CurScale:=double(Child.MinimumSize[Orientation])
/Child.PreferredSize[Orientation];
{$ENDIF}
CurOffset:=Child.PreferredSize[Orientation]
-Child.MinimumSize[Orientation];
if (Factor.Offset=0) or (Factor.Scale<CurScale) then begin
Factor.Scale:=CurScale;
Factor.Offset:=CurOffset;
end;
end;
crsHomogenousChildResize:
begin
{$IFDEF CPU64}
{$NOTE remove this workaround, when compiler bug fixed}
if Child.MinimumSize[Orientation]=0 then
CurScale:=0
else
CurScale:=double(Child.MinimumSize[Orientation])
/Child.PreferredSize[Orientation];
{$ELSE}
CurScale:=double(Child.MinimumSize[Orientation])
/Child.PreferredSize[Orientation];
{$ENDIF}
CurOffset:=Child.PreferredSize[Orientation]
-Child.MinimumSize[Orientation];
if (Factor.Offset=0) or (Factor.Offset>CurOffset) then begin
Factor.Scale:=CurScale;
Factor.Offset:=CurOffset;
end;
end;
end;
end;
crsHomogenousSpaceResize:
for i:=0 to ChildCount[Orientation]-1 do begin
Child:=Childs[Orientation][i];
if i=0 then begin
CurScale:=double(TargetSize);
CurOffset:=Child.BorderLeftTop[Orientation];
if CurOffset>0 then begin
inc(ResizeableCount);
if (Factor.Offset=0) or (Factor.Offset>CurOffset) then begin
Factor.Scale:=CurScale;
Factor.Offset:=CurOffset;
end;
end;
end;
CurScale:=double(TargetSize);
CurOffset:=Child.BorderRightBottom[Orientation];
if CurOffset>0 then begin
inc(ResizeableCount);
if (Factor.Offset=0) or (Factor.Offset>CurOffset) then begin
Factor.Scale:=CurScale;
Factor.Offset:=CurOffset;
end;
end;
end;
else
raise Exception.Create('TAutoSizeBox.ResizeChilds');
end;
end;
procedure ShrinkChilds(const Factor: TResizeFactor);
var
i: Integer;
Child: TAutoSizeBox;
DiffSize: Integer;
NewSize: LongInt;
OldSize: LongInt;
begin
for i:=0 to ChildCount[Orientation]-1 do begin
Child:=Childs[Orientation][i];
if (Child.PreferredSize[Orientation]<=1)
or (Child.PreferredSize[Orientation]<=Child.MinimumSize[Orientation])
then begin
// this child can not be further shrinked
continue;
end;
case ShrinkStyle of
crsScaleChilds:
begin
// scale PreferredSize
DiffSize:=CurSize-TargetSize;
OldSize:=Child.PreferredSize[Orientation];
NewSize:=Min(round(OldSize*Factor.Scale),OldSize-1);
NewSize:=Max(Max(1,NewSize),OldSize-DiffSize);
dec(CurSize,OldSize-NewSize);
Child.PreferredSize[Orientation]:=NewSize;
end;
crsHomogenousChildResize:
begin
// add to PreferredSize
DiffSize:=CurSize-TargetSize;
OldSize:=Child.PreferredSize[Orientation];
NewSize:=OldSize-Factor.Offset;
NewSize:=Max(Max(NewSize,1),OldSize-DiffSize);
dec(CurSize,OldSize-NewSize);
Child.PreferredSize[Orientation]:=NewSize;
end;
crsHomogenousSpaceResize:
begin
if i=0 then begin
// add to left/top border
DiffSize:=CurSize-TargetSize;
OldSize:=Child.BorderLeftTop[Orientation];
NewSize:=Max(Max(0,OldSize-Factor.Offset),OldSize-DiffSize);
dec(CurSize,OldSize-NewSize);
Child.BorderLeftTop[Orientation]:=NewSize;
end;
// add to right/bottom border
DiffSize:=CurSize-TargetSize;
OldSize:=Child.BorderRightBottom[Orientation];
NewSize:=Max(Max(0,OldSize-Factor.Offset),OldSize-DiffSize);
dec(CurSize,OldSize-NewSize);
Child.BorderRightBottom[Orientation]:=NewSize;
if i<ChildCount[Orientation]-1 then
Child.BorderLeftTop[Orientation]:=NewSize;
end;
end;
end;
end;
var
MaxResizeFactorPerItem, MinResizeFactorPerItem, CurScale: TResizeFactor;
ResizeableCount: integer;
i: Integer;
begin
CurSize:=GetChildTotalSize;
//DebugLn('TAutoSizeBox.ResizeChilds CurSize=',dbgs(CurSize),' TargetSize=',dbgs(TargetSize));
EnlargeStyle:=crsAnchorAligning;
ShrinkStyle:=crsAnchorAligning;
i:=0;
if TargetSize>CurSize then begin
// enlarge
if Orientation=asboHorizontal then
EnlargeStyle:=ChildSizing.EnlargeHorizontal
else
EnlargeStyle:=ChildSizing.EnlargeVertical;
while TargetSize>CurSize do begin
// shrink childs
GetChildMaxResize(MaxResizeFactorPerItem,ResizeableCount);
if (ResizeableCount=0) or (MaxResizeFactorPerItem.Offset=0) then break;
CurScale.Scale:=(double(TargetSize)/CurSize);
if (MaxResizeFactorPerItem.Scale>0)
and (MaxResizeFactorPerItem.Scale<CurScale.Scale) then
CurScale.Scale:=MaxResizeFactorPerItem.Scale;
CurScale.Offset:=((TargetSize-CurSize-1) div ResizeableCount)+1;
// note: the above formula makes sure, that Offset>0
if (MaxResizeFactorPerItem.Offset>0)
and (MaxResizeFactorPerItem.Offset<CurScale.Offset) then
CurScale.Offset:=MaxResizeFactorPerItem.Offset;
EnlargeChilds(CurScale);
inc(i);
if i>1000 then RaiseGDBException('TAutoSizeBox.ResizeChilds consistency error');
end;
end else if TargetSize<CurSize then begin
// shrink
if Orientation=asboHorizontal then
ShrinkStyle:=ChildSizing.ShrinkHorizontal
else
ShrinkStyle:=ChildSizing.ShrinkVertical;
while TargetSize<CurSize do begin
GetChildMinResize(MinResizeFactorPerItem,ResizeableCount);
if (ResizeableCount=0) or (MinResizeFactorPerItem.Offset=0) then break;
CurScale.Scale:=(double(TargetSize)/CurSize);
if (MinResizeFactorPerItem.Scale>0)
and (MinResizeFactorPerItem.Scale>CurScale.Scale) then
CurScale.Scale:=MinResizeFactorPerItem.Scale;
CurScale.Offset:=((CurSize-TargetSize-1) div ResizeableCount)+1;
// note: the above formula makes sure, that Offset>0
if (MinResizeFactorPerItem.Offset>0)
and (MinResizeFactorPerItem.Offset>CurScale.Offset) then
CurScale.Offset:=MinResizeFactorPerItem.Offset;
ShrinkChilds(CurScale);
inc(i);
if i>1000 then RaiseGDBException('TAutoSizeBox.ResizeChilds consistency error');
end;
end;
ComputeLeftTops(Orientation);
end;
procedure TAutoSizeBox.ResizeTable(ChildSizing: TControlChildSizing;
TargetWidth, TargetHeight: integer);
begin
// resize rows and columns
ResizeChilds(ChildSizing,asboHorizontal,TargetWidth);
ResizeChilds(ChildSizing,asboVertical,TargetHeight);
end;
procedure TAutoSizeBox.ComputeTableControlBounds(ChildSizing: TControlChildSizing
);
var
y: Integer;
RowBox: TAutoSizeBox;
x: Integer;
ColBox: TAutoSizeBox;
ControlBox: TAutoSizeBox;
CurControl: TControl;
NewBounds: TRect;
CellBounds: TRect;
NewWidth: LongInt;
NewHeight: LongInt;
begin
//WriteDebugReport;
for y:=0 to ChildCount[asboVertical]-1 do begin
RowBox:=Childs[asboVertical][y];
for x:=0 to RowBox.ChildCount[asboHorizontal]-1 do begin
ControlBox:=RowBox.Childs[asboHorizontal][x];
ColBox:=ControlBox.Parent[asboVertical];
CurControl:=ControlBox.Control;
if CurControl=nil then continue;
CellBounds:=Bounds(ColBox.LeftTop[asboHorizontal],
RowBox.LeftTop[asboVertical],
ColBox.PreferredSize[asboHorizontal],
RowBox.PreferredSize[asboVertical]);
NewBounds.Left:=CellBounds.Left;
NewBounds.Top:=CellBounds.Top;
NewWidth:=ControlBox.PreferredSize[asboHorizontal];
NewHeight:=ControlBox.PreferredSize[asboVertical];
if (NewWidth<ColBox.PreferredSize[asboHorizontal]) then begin
// column is bigger than preferred width of the control
//DebugLn('TAutoSizeBox.SetTableControlBounds ',DbgSName(CurControl),' ',dbgs(ord(CurControl.BorderSpacing.CellAlignHorizontal)));
case CurControl.BorderSpacing.CellAlignHorizontal of
ccaFill: NewWidth:=CellBounds.Right-CellBounds.Left;
ccaLeftTop: ;
ccaRightBottom: NewBounds.Left:=CellBounds.Right-NewWidth;
ccaCenter: NewBounds.Left:=NewBounds.Left
+(CellBounds.Right-CellBounds.Left-NewWidth) div 2;
end;
end else if (NewWidth>ColBox.PreferredSize[asboHorizontal]) then begin
// column is smaller than preferred width of the control
if ChildSizing.ShrinkHorizontal
in [crsScaleChilds,crsHomogenousChildResize]
then
NewWidth:=CellBounds.Right-CellBounds.Left;
end;
if (NewHeight<ColBox.PreferredSize[asboVertical]) then begin
// column is bigger than preferred height of the control
case CurControl.BorderSpacing.CellAlignVertical of
ccaFill: NewHeight:=CellBounds.Bottom-CellBounds.Top;
ccaLeftTop: ;
ccaRightBottom: NewBounds.Top:=CellBounds.Bottom-NewHeight;
ccaCenter: NewBounds.Top:=NewBounds.Top
+(CellBounds.Bottom-CellBounds.Top-NewHeight) div 2;
end;
end else if (NewHeight>ColBox.PreferredSize[asboVertical]) then begin
// column is smaller than preferred height of the control
if ChildSizing.ShrinkVertical
in [crsScaleChilds,crsHomogenousChildResize]
then
NewHeight:=CellBounds.Bottom-CellBounds.Top;
end;
NewBounds.Right:=NewBounds.Left+NewWidth;
NewBounds.Bottom:=NewBounds.Top+NewHeight;
ControlBox.NewControlBounds:=NewBounds;
end;
end;
end;
function TAutoSizeBox.SetTableControlBounds(ChildSizing: TControlChildSizing
): boolean;
var
y: Integer;
RowBox: TAutoSizeBox;
x: Integer;
ControlBox: TAutoSizeBox;
CurControl: TControl;
NewBounds: TRect;
OldBounds: TRect;
begin
Result:=false;
//WriteDebugReport;
for y:=0 to ChildCount[asboVertical]-1 do begin
RowBox:=Childs[asboVertical][y];
for x:=0 to RowBox.ChildCount[asboHorizontal]-1 do begin
ControlBox:=RowBox.Childs[asboHorizontal][x];
CurControl:=ControlBox.Control;
if CurControl=nil then continue;
NewBounds:=ControlBox.NewControlBounds;
OldBounds:=CurControl.BoundsRect;
if not CompareRect(@NewBounds,@OldBounds) then begin
Result:=true;
CurControl.SetAlignedBounds(NewBounds.Left,
NewBounds.Top,
NewBounds.Right-NewBounds.Left,
NewBounds.Bottom-NewBounds.Top);
end;
end;
end;
end;
function TAutoSizeBox.AlignControlsInTable(ListOfControls: TFPList;
ChildSizing: TControlChildSizing; TargetWidth, TargetHeight: integer;
Apply: boolean): boolean;
// true if a control was modified
begin
SetTableControls(ListOfControls,ChildSizing);
//WriteDebugReport('after SetTableControls');
SumTable;
//WriteDebugReport('after SumTable');
ResizeTable(ChildSizing,TargetWidth,TargetHeight);
//WriteDebugReport('after ResizeTable');
ComputeTableControlBounds(ChildSizing);
//WriteDebugReport('after ComputeTableControlBounds');
Result:=Apply and SetTableControlBounds(ChildSizing);
end;
procedure TAutoSizeBox.WriteDebugReport(const Title: string);
var
y: Integer;
RowBox: TAutoSizeBox;
x: Integer;
CellBox: TAutoSizeBox;
ColBox: TAutoSizeBox;
begin
DebugLn('TAutoSizeBox.WriteDebugReport '+Title
+' ChildCounts=',dbgs(ChildCount[asboHorizontal]),'x',dbgs(ChildCount[asboVertical]));
for y:=0 to ChildCount[asboVertical]-1 do begin
RowBox:=Childs[asboVertical][y];
DbgOut(' Row='+dbgs(y),
' MinY='+dbgs(RowBox.MinimumSize[asboVertical]),
' MaxY='+dbgs(RowBox.MaximumSize[asboVertical]),
' PrefY='+dbgs(RowBox.PreferredSize[asboVertical]),
' BorderTop=',dbgs(RowBox.BorderLeftTop[asboVertical]),
' #Col='+dbgs(RowBox.ChildCount[asboHorizontal]));
for x:=0 to RowBox.ChildCount[asboHorizontal]-1 do begin
CellBox:=RowBox.Childs[asboHorizontal][x];
DbgOut(' CellControl=',DbgSName(CellBox.Control),
' Min='+dbgs(CellBox.MinimumSize[asboHorizontal])+'x'+dbgs(CellBox.MinimumSize[asboVertical]),
' Max='+dbgs(CellBox.MaximumSize[asboHorizontal])+'x'+dbgs(CellBox.MaximumSize[asboVertical]),
' BorderLeft=',dbgs(CellBox.BorderLeftTop[asboHorizontal]),
' Pref='+dbgs(CellBox.PreferredSize[asboHorizontal])+'x'+dbgs(CellBox.PreferredSize[asboVertical]),
'');
end;
DebugLn;
end;
DbgOut(' Columns: ');
for x:=0 to ChildCount[asboHorizontal]-1 do begin
ColBox:=Childs[asboHorizontal][x];
DbgOut(' Col='+dbgs(ColBox.Index[asboHorizontal]),
' Min='+dbgs(ColBox.MinimumSize[asboHorizontal]),
' Max='+dbgs(ColBox.MaximumSize[asboHorizontal]),
' Pref='+dbgs(ColBox.PreferredSize[asboHorizontal]),
'');
end;
DebugLn;
end;
destructor TAutoSizeBox.Destroy;
var
o: TAutoSizeBoxOrientation;
begin
// unlink from parent
for o:=Low(TAutoSizeBoxOrientation) to high(TAutoSizeBoxOrientation) do
if Parent[o]<>nil then
Parent[o].Childs[o][Index[o]]:=nil;
Clear;
inherited Destroy;
end;
procedure TAutoSizeBox.Clear;
var
o: TAutoSizeBoxOrientation;
i: Integer;
begin
// free all childs
for o:=Low(TAutoSizeBoxOrientation) to high(TAutoSizeBoxOrientation) do
for i:=0 to ChildCount[o]-1 do
Childs[o][i].Free;
// free childs arrays
for o:=Low(TAutoSizeBoxOrientation) to high(TAutoSizeBoxOrientation) do
ReallocMem(Childs[o],0);
end;
{------------------------------------------------------------------------------
function TWinControl.AutoSizeDelayed: boolean;
------------------------------------------------------------------------------}
function TWinControl.AutoSizeDelayed: boolean;
begin
Result:=// no handle means not visible
(not HandleAllocated)
or ((not FShowing) and (not (csDesigning in ComponentState)))
// during handle creation no autosize
or (wcfCreatingChildHandles in FWinControlFlags)
or (inherited AutoSizeDelayed);
//if Result then debugln('TWinControl.AutoSizeDelayed A ',DbgSName(Self),' wcfCreatingChildHandles=',dbgs(wcfCreatingChildHandles in FWinControlFlags),' csLoading=',dbgs(csLoading in ComponentState));
{$IFDEF VerboseCanAutoSize}
if Result {and AutoSize} then begin
DbgOut('TWinControl.AutoSizeDelayed Self='+DbgSName(Self)+' ');
if not HandleAllocated then debugln('not HandleAllocated')
else if not FShowing then debugln('not FShowing')
else if wcfCreatingChildHandles in FWinControlFlags then debugln('wcfCreatingChildHandles')
else debugln('inherited AutoSizeDelayed');
end;
{$ENDIF}
end;
{------------------------------------------------------------------------------
TWinControl AdjustClientRect
------------------------------------------------------------------------------}
procedure TWinControl.AdjustClientRect(var ARect: TRect);
Begin
//Not used. It's a virtual procedure that should be overriden.
end;
{------------------------------------------------------------------------------
TWinControl CreateControlAlignList
Creates a list of controls that need to be aligned via TheAlign.
------------------------------------------------------------------------------}
procedure TWinControl.CreateControlAlignList(TheAlign: TAlign;
AlignList: TFPList; StartControl: TControl);
function InsertBefore(Control1, Control2: TControl;
AAlign: TAlign): Boolean;
begin
Result := False;
case AAlign of
alTop:
Result := (Control1.Top < Control2.Top)
or ((Control1.Top = Control2.Top)
and (Control1.FBaseBounds.Top<Control2.FBaseBounds.Top));
alLeft: Result := (Control1.Left < Control2.Left)
or ((Control1.Left = Control2.Left)
and (Control1.FBaseBounds.Left<Control2.FBaseBounds.Left));
// contrary to VCL, LCL uses > for alBottom, alRight
// Maybe it is a bug in the VCL.
// This results in first control is put rightmost/bottommost
alBottom: Result :=
((Control1.Top + Control1.Height) > (Control2.Top + Control2.Height))
or (((Control1.Top + Control1.Height) = (Control2.Top + Control2.Height))
and (Control1.FBaseBounds.Bottom > Control2.FBaseBounds.Bottom));
alRight: Result :=
((Control1.Left + Control1.Width) > (Control2.Left + Control2.Width))
or (((Control1.Left + Control1.Width) = (Control2.Left + Control2.Width))
and (Control1.FBaseBounds.Right > Control2.FBaseBounds.Right));
end;
end;
var
I, X: Integer;
Control: TControl;
begin
AlignList.Clear;
// first add the current control
if (StartControl <> nil)
and (StartControl.Align = TheAlign)
and ((TheAlign = alNone)
or StartControl.IsControlVisible)
then
AlignList.Add(StartControl);
// then add all other
for I := 0 to ControlCount - 1 do
begin
Control := Controls[I];
if (Control.Align = TheAlign) and Control.IsControlVisible then
begin
if Control = StartControl then Continue;
X := 0;
while (X < AlignList.Count)
and not InsertBefore(Control, TControl(AlignList[X]), TheAlign) do
Inc(X);
AlignList.Insert(X, Control);
end;
end;
end;
{------------------------------------------------------------------------------
TWinControl AlignControls
Align child controls
------------------------------------------------------------------------------}
procedure TWinControl.AlignControls(AControl: TControl;
var RemainingClientRect: TRect);
{ $DEFINE CHECK_POSITION}
var
AlignList: TFPList;
BoundsMutated: boolean;
LastBoundsMutated: TControl;
LastBoundsMutatedOld: TRect;
RemainingBorderSpace: TRect; // borderspace around RemainingClientRect
// e.g. Right=3 means borderspace of 3
function NeedAlignWork: Boolean;
var
I: Integer;
CurControl: TControl;
begin
Result := True;
for I := ControlCount - 1 downto 0 do
begin
CurControl:=Controls[I];
if (CurControl.Align <> alNone)
or (CurControl.Anchors <> [akLeft, akTop])
or (CurControl.AnchorSide[akLeft].Control<>nil)
or (CurControl.AnchorSide[akTop].Control<>nil)
or (ChildSizing.Layout<>cclNone)
then Exit;
end;
Result := False;
end;
function Anchored(Align: TAlign; Anchors: TAnchors): Boolean;
begin
case Align of
alLeft: Result := akLeft in Anchors;
alTop: Result := akTop in Anchors;
alRight: Result := akRight in Anchors;
alBottom: Result := akBottom in Anchors;
alClient: Result := Anchors = [akLeft, akTop, akRight, akBottom];
else
Result := False;
end;
end;
function IsNotAligned(AControl: TControl): boolean;
begin
Result:=(AControl.Align=alNone)
and (AControl.Anchors=[akLeft,akTop])
and (AControl.AnchorSide[akLeft].Control=nil)
and (AControl.AnchorSide[akTop].Control=nil);
end;
procedure DoPosition(Control: TControl; AAlign: TAlign);
var
NewLeft, NewTop, NewWidth, NewHeight: Integer;
ParentBaseClientSize: TPoint;
ParentClientWidth: integer;
ParentClientHeight: integer;
CurBaseBounds: TRect;
NewRight: Integer;// temp variable, not always valid, use with care !
NewBottom: Integer;// temp variable, not always valid, use with care !
MinWidth: Integer;
MaxWidth: Integer;
MinHeight: Integer;
MaxHeight: Integer;
CurRemainingClientRect: TRect;
CurRemainingBorderSpace: TRect; // borderspace around RemainingClientRect
// e.g. Right=3 means borderspace of 3
ChildAroundSpace: TRect;
AnchorSideCacheValid: array[TAnchorKind] of boolean;
AnchorSideCache: array[TAnchorKind] of integer;
CurAnchors: TAnchors;
CurAlignAnchors: TAnchors;
OldBounds: TRect;
NewBounds: TRect;
r: TRect;
function ConstraintWidth(NewWidth: integer): Integer;
begin
Result:=NewWidth;
if (MaxWidth>=MinWidth) and (Result>MaxWidth) and (MaxWidth>0) then
Result:=MaxWidth;
if Result<MinWidth then Result:=MinWidth;
end;
procedure ConstraintWidth(var NewLeft, NewWidth: integer);
var
ConWidth: LongInt;
begin
ConWidth:=ConstraintWidth(NewWidth);
if ConWidth<>NewWidth then begin
if [akLeft,akRight]*CurAnchors=[akRight] then
// move left side, keep right
inc(NewLeft,NewWidth-ConWidth);
NewWidth:=ConWidth;
end;
end;
function ConstraintHeight(NewHeight: integer): Integer;
begin
Result:=NewHeight;
if (MaxHeight>=MinHeight) and (Result>MaxHeight) and (MaxHeight>0) then
Result:=MaxHeight;
if Result<MinHeight then Result:=MinHeight;
end;
procedure ConstraintHeight(var NewTop, NewHeight: integer);
var
ConHeight: LongInt;
begin
ConHeight:=ConstraintHeight(NewHeight);
if ConHeight<>NewHeight then begin
if [akTop,akBottom]*CurAnchors=[akBottom] then
// move top side, keep bottom
inc(NewTop,NewHeight-ConHeight);
NewHeight:=ConHeight;
end;
end;
procedure InitAnchorSideCache;
var
a: TAnchorKind;
begin
for a:=Low(TAnchorKind) to High(TAnchorKind) do
AnchorSideCacheValid[a]:=false;
end;
function GetAnchorSidePosition(Kind: TAnchorKind;
DefaultPosition: Integer): integer;
// calculates the position in pixels of a side due to anchors
// For example: if akLeft is set, it returns the coordinate for the left anchor
var
CurAnchorSide: TAnchorSide;
ReferenceControl: TControl;
ReferenceSide: TAnchorSideReference;
Position: Integer;
begin
if AnchorSideCacheValid[Kind] then begin
Result:=AnchorSideCache[Kind];
exit;
end;
Result:=DefaultPosition;
CurAnchorSide:=Control.AnchorSide[Kind];
//if CheckPosition(Control) and (Kind=akLeft) then debugln(['GetAnchorSidePosition A Self=',DbgSName(Self),' Control=',DbgSName(Control),' CurAnchorSide.Control=',DbgSName(CurAnchorSide.Control),' Spacing=',Control.BorderSpacing.GetSpace(Kind)]);
CurAnchorSide.GetSidePosition(ReferenceControl,ReferenceSide,Position);
if ReferenceControl<>nil then
Result:=Position;
//if CheckPosition(Control) and (Kind=akLeft) then begin
//debugln('GetAnchorSidePosition B Self=',DbgSName(Self),' Control=',DbgSName(Control),' Result=',dbgs(Result),' ReferenceControl=',dbgsName(ReferenceControl));
//if ReferenceControl<>nil then DebugLn(['GetAnchorSidePosition ReferenceControl.BoundsRect=',dbgs(ReferenceControl.BoundsRect)]);
//end;
AnchorSideCacheValid[Kind]:=true;
AnchorSideCache[Kind]:=Result;
if ReferenceSide=asrTop then ;
end;
begin
{$IFDEF CHECK_POSITION}
if CheckPosition(Control) then
with Control do
DebugLn('[TWinControl.AlignControls.DoPosition] A Control=',dbgsName(Control),' ',dbgs(Left),',',dbgs(Top),',',dbgs(Width),',',dbgs(Height),' recalculate the anchors=',dbgs(Control.Anchors <> AnchorAlign[AAlign]),' Align=',AlignNames[AAlign]);
{$ENDIF}
with Control do begin
// get constraints
MinWidth:=Constraints.EffectiveMinWidth;
if MinWidth<0 then MinWidth:=0;
MaxWidth:=Constraints.EffectiveMaxWidth;
MinHeight:=Constraints.EffectiveMinHeight;
if MinHeight<0 then MinHeight:=0;
MaxHeight:=Constraints.EffectiveMaxHeight;
// get anchors set by Align
CurAlignAnchors:=[];
if Align in [alLeft,alRight,alBottom,alTop,alClient] then
CurAlignAnchors:=AnchorAlign[Align];
CurAnchors:=Anchors+CurAlignAnchors;
// get default bounds
NewLeft:=Left;
NewTop:=Top;
NewWidth:=Width;
NewHeight:=Height;
ConstraintWidth(NewLeft,NewWidth);
ConstraintHeight(NewTop,NewHeight);
end;
r:=Control.Parent.GetLogicalClientRect;
ParentClientWidth:=r.Right;
ParentClientHeight:=r.Bottom;
InitAnchorSideCache;
{ Recalculate the anchors
Use Anchors to ensure that a control maintains its current position
relative to an edge of its parent or another sibling.
This is controlled with the AnchorSide properties.
1. If AnchorSide[].Control is not set, the distance is kept relative to
the edges of the client area of its parent.
When its parent is resized, the control holds its position relative to the
edges to which it is anchored.
If a control is anchored to opposite edges of its parent, the control
stretches when its parent is resized. For example, if a control has its
Anchors property set to [akLeft,akRight], the control stretches when the
width of its parent changes.
Anchors is enforced only when the parent is resized. Thus, for example,
if a control is anchored to opposite edges of a form at design time and
the form is created in a maximized state, the control is not stretched
because the form is not resized after the control is created.
2. If AnchorSide[].Control is set, the BorderSpace properties defines the
distance to another sibling (i.e. AnchorSide[].Control).
}
if (AAlign = alNone) or (Control.Anchors <> CurAlignAnchors) then begin
// at least one side is anchored without align
// Get the base bounds. The base bounds are the user defined bounds
// without automatic aligning and/or anchoring
// get base size of parents client area
ParentBaseClientSize:=Control.FBaseParentClientSize;
if (ParentBaseClientSize.X=0)
and (ParentBaseClientSize.Y=0) then
ParentBaseClientSize:=Point(ParentClientWidth,ParentClientHeight);
// get base bounds of Control
CurBaseBounds:=Control.FBaseBounds;
if (CurBaseBounds.Right=CurBaseBounds.Left)
and (CurBaseBounds.Bottom=CurBaseBounds.Top) then
CurBaseBounds:=Control.BoundsRect;
{$IFDEF CHECK_POSITION}
//if csDesigning in ComponentState then
if CheckPosition(Control) then
DebugLn('[TWinControl.AlignControls.DoPosition] Before Anchoring ',
' Self='+DbgSName(Self),' Control='+DbgSName(Control),
' CurBaseBounds='+dbgs(CurBaseBounds.Left)+','+dbgs(CurBaseBounds.Top)+','+dbgs(CurBaseBounds.Right-CurBaseBounds.Left)+','+dbgs(CurBaseBounds.Bottom-CurBaseBounds.Top),
' ParentBaseClientSize='+dbgs(ParentBaseClientSize.X)+','+dbgs(ParentBaseClientSize.Y),
' ControlParent.Client='+dbgs(ParentClientWidth)+','+dbgs(ParentClientHeight),
' NewBounds='+dbgs(NewLeft)+','+dbgs(NewTop)+','+dbgs(NewWidth)+','+dbgs(NewHeight),
'');
{$ENDIF}
if akLeft in CurAnchors then begin
// keep distance to left side of parent or another sibling
NewLeft:=GetAnchorSidePosition(akLeft,CurBaseBounds.Left);
if akRight in CurAnchors then begin
// keep distance to right side of parent or another sibling
// -> change the width
NewRight:=ParentClientWidth
-(ParentBaseClientSize.X-CurBaseBounds.Right);
if (not (akRight in CurAlignAnchors))
and (akRight in Control.Anchors) then
NewRight:=GetAnchorSidePosition(akRight,NewRight);
NewWidth:=ConstraintWidth(NewRight-NewLeft);
end else begin
// do not anchor to the right
// -> keep new width
end;
end else begin
// do not anchor to the left
if akRight in CurAnchors then begin
// keep distance to right side of parent
// and keep new width
NewRight:=ParentClientWidth
-(ParentBaseClientSize.X-CurBaseBounds.Right);
if (not (akRight in CurAlignAnchors))
and (akRight in Control.Anchors) then
NewRight:=GetAnchorSidePosition(akRight,NewRight);
NewLeft:=NewRight-NewWidth;
end else begin
// do not anchor to the right
// -> keep new width and scale center position.
NewLeft:=MulDiv(ParentClientWidth,
(CurBaseBounds.Left+CurBaseBounds.Right) div 2,
ParentBaseClientSize.X)
-(NewWidth div 2);
end;
end;
if akTop in CurAnchors then begin
// keep distance to top side of parent
NewTop:=GetAnchorSidePosition(akTop,CurBaseBounds.Top);
if akBottom in CurAnchors then begin
// keep distance to bottom side of parent
// -> change the height
NewBottom:=ParentClientHeight
-(ParentBaseClientSize.Y-CurBaseBounds.Bottom);
if (not (akBottom in CurAlignAnchors))
and (akBottom in Control.Anchors) then
NewBottom:=GetAnchorSidePosition(akBottom,NewBottom);
NewHeight:=ConstraintHeight(NewBottom-NewTop);
end else begin
// do not anchor to the bottom
// -> keep new height
end;
end else begin
// do not anchor to the top
if akBottom in CurAnchors then begin
// keep distance to bottom side of parent
// and keep new height
NewBottom:=ParentClientHeight
-(ParentBaseClientSize.Y-CurBaseBounds.Bottom);
if (not (akBottom in CurAlignAnchors))
and (akBottom in Control.Anchors) then
NewBottom:=GetAnchorSidePosition(akBottom,NewBottom);
NewTop:=NewBottom-NewHeight;
end else begin
// do not anchor to the bottom
// -> keep new height and scale center position.
NewTop:=MulDiv(ParentClientHeight,
(CurBaseBounds.Top+CurBaseBounds.Bottom) div 2,
ParentBaseClientSize.Y)
-(NewHeight div 2);
end;
end;
{$IFDEF CHECK_POSITION}
//if csDesigning in ComponentState then
if CheckPosition(Control) then
with Control do begin
DebugLn(['[TWinControl.AlignControls.DoPosition] After Anchoring',
' ',Name,':',ClassName,
' Align=',AlignNames[AAlign],
' Control=',dbgsName(Control),
' Old= l=',Left,',t=',Top,',w=',Width,',h=',Height,
' New= l=',NewLeft,',t=',NewTop,',w=',NewWidth,',h=',NewHeight,
'']);
DebugLn(['DoPosition akRight=',akRight in CurAnchors,' ',GetAnchorSidePosition(akRight,NewLeft+NewWidth)]);
end;
{$ENDIF}
end;
// set min size to stop circling (this should not be needed. But if someone
// plays/fixes the above code, new bugs can enter and there are far too many
// combinations to test, and so the LCL can loop for some applications.
// Prevent this, so users can at least report a bug.)
if NewWidth<0 then NewWidth:=0;
if NewHeight<0 then NewHeight:=0;
if AAlign in [alLeft,alTop,alRight,alBottom,alClient] then begin
{ Realign
Use Align to align a control to the top, bottom, left, right of a
form or panel and have it remain there even if the size of the form,
panel, or component that contains the control changes. When the parent
is resized, an aligned control also resizes so that it continues to span
the top, bottom, left, or right edge of the parent (more exact:
span the remaining client area of its parent).
}
NewRight:=NewLeft+NewWidth;
NewBottom:=NewTop+NewHeight;
// calculate current RemainingClientRect for the current Control
CurRemainingClientRect:=RemainingClientRect;
CurRemainingBorderSpace:=RemainingBorderSpace;
Control.BorderSpacing.GetSpaceAround(ChildAroundSpace);
AdjustBorderSpace(CurRemainingClientRect,CurRemainingBorderSpace,
ChildAroundSpace);
{$IFDEF CHECK_POSITION}
if CheckPosition(Control) then
DebugLn('DoPosition Before aligning ',dbgsName(Control),' akRight in AnchorAlign[AAlign]=',DbgS(akRight in AnchorAlign[AAlign]),
' akLeft in Control.Anchors=',DbgS(akLeft in Control.Anchors),
//' ARect=',ARect.Left,',',ARect.Top,',',ARect.Right,',',ARect.Bottom,
' New=',DbgS(NewLeft,NewTop,NewRight,NewBottom));
{$ENDIF}
if akLeft in AnchorAlign[AAlign] then begin
if (akRight in CurAnchors) then begin
// left align and keep right border
NewLeft:=CurRemainingClientRect.Left;
NewRight:=NewLeft+ConstraintWidth(NewRight-NewLeft);
end else begin
// left align and right border free to move (-> keep width)
dec(NewRight,NewLeft-CurRemainingClientRect.Left);
NewLeft:=CurRemainingClientRect.Left;
end;
end;
if akTop in AnchorAlign[AAlign] then begin
if (akBottom in CurAnchors) then begin
// top align and keep bottom border
NewTop:=CurRemainingClientRect.Top;
NewBottom:=NewTop+ConstraintHeight(NewBottom-NewTop);
end else begin
// top align and bottom border is free to move (-> keep height)
dec(NewBottom,NewTop-CurRemainingClientRect.Top);
NewTop:=CurRemainingClientRect.Top;
end;
end;
if akRight in AnchorAlign[AAlign] then begin
if (akLeft in CurAnchors) then begin
// right align and keep left border
NewWidth:=ConstraintWidth(CurRemainingClientRect.Right-NewLeft);
if Align=alRight then begin
// align to right (this overrides the keeping of left border)
NewRight:=CurRemainingClientRect.Right;
NewLeft:=NewRight-NewWidth;
end else begin
// keep left border overrides keeping right border
NewRight:=NewLeft+NewWidth;
end;
end else begin
// right align and left border free to move (-> keep width)
inc(NewLeft,CurRemainingClientRect.Right-NewRight);
NewRight:=CurRemainingClientRect.Right;
end;
end;
if akBottom in AnchorAlign[AAlign] then begin
if (akTop in CurAnchors) then begin
// bottom align and keep top border
NewHeight:=ConstraintHeight(CurRemainingClientRect.Bottom-NewTop);
if AAlign=alBottom then begin
// align to bottom (this overrides the keeping of top border)
NewBottom:=CurRemainingClientRect.Bottom;
NewTop:=NewBottom-NewHeight;
end else begin
// keeping top border overrides keeping bottom border
NewBottom:=NewTop+NewHeight;
end;
end else begin
// bottom align and top border free to move (-> keep height)
inc(NewTop,CurRemainingClientRect.Bottom-NewBottom);
NewBottom:=CurRemainingClientRect.Bottom;
end;
end;
NewWidth:=Max(0,NewRight-NewLeft);
NewHeight:=Max(0,NewBottom-NewTop);
{$IFDEF CHECK_POSITION}
//if csDesigning in Control.ComponentState then
if CheckPosition(Control) then
with Control do
DebugLn('[TWinControl.AlignControls.DoPosition] After Aligning',
' ',Name,':',ClassName,
' Align=',AlignNames[AAlign],
' Control=',Name,':',ClassName,
' Old=',DbgS(Left,Top,Width,Height),
' New=',DbgS(NewLeft,NewTop,NewWidth,NewHeight),
//' ARect=',ARect.Left,',',ARect.Top,',',ARect.Right-ARect.Left,',',ARect.Bottom-ARect.Top,
'');
{$ENDIF}
end;
// apply the constraints
NewWidth:=ConstraintWidth(NewWidth);
NewHeight:=ConstraintHeight(NewHeight);
NewRight:=NewLeft+NewWidth;
NewBottom:=NewTop+NewHeight;
// set the new bounds
if (Control.Left <> NewLeft) or (Control.Top <> NewTop)
or (Control.Width <> NewWidth) or (Control.Height <> NewHeight) then begin
{$IFDEF CHECK_POSITION}
//if csDesigning in Control.ComponentState then
if CheckPosition(Control) then
with Control do
DebugLn('[TWinControl.AlignControls.DoPosition] NEW BOUNDS Control=',DbgSName(Control),
' New=l=',dbgs(NewLeft)+',t='+dbgs(NewTop)+',w='+dbgs(NewWidth)+',h='+dbgs(NewHeight));
{$ENDIF}
// lock the base bounds, so that the new automatic bounds do not override
// the user settings
OldBounds:=Control.BoundsRect;
Control.SetAlignedBounds(NewLeft, NewTop, NewWidth, NewHeight);
//DebugLn(['DoPosition ',DbgSName(Control),' ',cfAutoSizeNeeded in Control.FControlFlags]);
NewBounds:=Control.BoundsRect;
BoundsMutated:=not CompareRect(@OldBounds,@NewBounds);
if BoundsMutated then begin
LastBoundsMutated:=Control;
LastBoundsMutatedOld:=OldBounds;
end;
// Sometimes SetBounds change the bounds. For example due to constraints.
// update the new bounds
with Control do
begin
NewLeft:=Left;
NewTop:=Top;
NewWidth:=Width;
NewHeight:=Height;
end;
{$IFDEF CHECK_POSITION}
//if csDesigning in Control.ComponentState then
if CheckPosition(Control) then
with Control do
DebugLn('[TWinControl.AlignControls.DoPosition] AFTER SETBOUND Control=',DbgSName(Control),' Bounds=',DbgS(Control.BoundsRect));
{$ENDIF}
end;
// adjust the remaining client area
case AAlign of
alTop:
begin
RemainingClientRect.Top:=Min(NewTop+NewHeight,RemainingClientRect.Bottom);
RemainingBorderSpace.Top:=0;
AdjustBorderSpace(RemainingClientRect,RemainingBorderSpace,
0,Max(ChildSizing.VerticalSpacing,ChildAroundSpace.Bottom),0,0);
end;
alBottom:
begin
RemainingClientRect.Bottom:=Max(NewTop,RemainingClientRect.Top);
RemainingBorderSpace.Bottom:=0;
AdjustBorderSpace(RemainingClientRect,RemainingBorderSpace,
0,0,0,Max(ChildSizing.VerticalSpacing,ChildAroundSpace.Top));
end;
alLeft:
begin
RemainingClientRect.Left:=Min(NewLeft+NewWidth,RemainingClientRect.Right);
RemainingBorderSpace.Left:=0;
AdjustBorderSpace(RemainingClientRect,RemainingBorderSpace,
Max(ChildSizing.HorizontalSpacing,ChildAroundSpace.Right),0,0,0);
end;
alRight:
begin
RemainingClientRect.Right:=Max(NewLeft,RemainingClientRect.Left);
RemainingBorderSpace.Right:=0;
AdjustBorderSpace(RemainingClientRect,RemainingBorderSpace,
0,0,Max(ChildSizing.HorizontalSpacing,ChildAroundSpace.Left),0);
end;
alClient:
begin
// For VCL compatibility alClient should *not* reduce the free space,
// so that several alClient controls can overlap. This can be used
// for example to simulate a two page control and edit both pages
// at designtime with SendToBack.
// At runtime programs should use Visible instead of BringToFront to
// reduce overhead.
// See bug 10380.
end;
end;
{$IFDEF CHECK_POSITION}
if CheckPosition(Control) then
with Control do
DebugLn('[TWinControl.AlignControls.DoPosition] END Control=',
Name,':',ClassName,
' ',DbgS(Left,Top,Width,Height),
' Align=',AlignNames[AAlign],
//' ARect=',ARect.Left,',',ARect.Top,',',ARect.Right-ARect.Left,',',ARect.Bottom-ARect.Top,
'');
{$ENDIF}
end;
procedure DoAlign(AAlign: TAlign);
var
I: Integer;
Control: TControl;
begin
//DebugLn(['DoAlign ',DbgSName(Self),' ',dbgs(AALign),' ClientRect=',dbgs(ClientRect),' ControlCount=',ControlCount]);
CreateControlAlignList(AAlign,AlignList,AControl);
{$IFDEF CHECK_POSITION}
if CheckPosition(Self) then
if AlignList.Count>0 then begin
DbgOut('[TWinControl.AlignControls.DoAlign] Self=',DbgSName(Self),' Control=',dbgsName(AControl),
' current align=',AlignNames[AAlign],' AlignList=[');
for i:=0 to AlignList.Count-1 do begin
if i>0 then DbgOut(',');
DbgOut(DbgSName(TObject(AlignList[i])));
end;
DebugLn(']');
end;
{$ENDIF}
// let override handle them
if DoAlignChildControls(AAlign,AControl,AlignList,RemainingClientRect) then
exit;
// remove controls that are positioned by other means
if (AAlign=alNone) and (AutoSize or (ChildSizing.Layout<>cclNone)) then
for I := AlignList.Count - 1 downto 0 do begin
Control:=TControl(AlignList[I]);
if IsNotAligned(Control) then AlignList.Delete(I);
end;
// anchor/align control
for I := 0 to AlignList.Count - 1 do
DoPosition(TControl(AlignList[I]), AAlign);
end;
procedure DoAlignNotAligned;
// All controls, not aligned by their own properties, can be auto aligned.
var
i: Integer;
Control: TControl;
begin
// check if ChildSizing aligning is enabled
if (ChildSizing.Layout=cclNone) then
exit;
/// collect all 'not aligned' controls
AlignList.Clear;
for i:=0 to ControlCount-1 do begin
Control := Controls[i];
if IsNotAligned(Control) and Control.IsControlVisible then
AlignList.Add(Control);
end;
//debugln('DoAlignNotAligned ',DbgSName(Self),' AlignList.Count=',dbgs(AlignList.Count));
if AlignList.Count=0 then exit;
LastBoundsMutated:=nil;
AlignNonAlignedControls(AlignList,BoundsMutated);
end;
var
i: Integer;
ChildControl: TControl;
OldRemainingClientRect: TRect;
OldRemainingBorderSpace: TRect;
MaxTries: LongInt;
begin
if wcfAligningControls in FWinControlFlags then exit;
Include(FWinControlFlags,wcfAligningControls);
try
// call delayed autosize
for i:=ControlCount-1 downto 0 do begin
ChildControl:=Controls[i];
if cfAutoSizeNeeded in ChildControl.FControlFlags then begin
//DebugLn(['TWinControl.AlignControls ',DbgSName(Self),' autosize needed for child ',DbgSName(ChildControl)]);
ChildControl.AdjustSize;
end;
end;
// unset all align needed flags
Exclude(FWinControlFlags,wcfReAlignNeeded);
for i:=ControlCount-1 downto 0 do begin
ChildControl:=Controls[i];
Exclude(ChildControl.FControlFlags,cfRequestAlignNeeded);
end;
//if csDesigning in ComponentState then begin
//DebugLn('[TWinControl.AlignControls] ',Name,':',Classname,' ',Left,',',Top,',',Width,',',Height,' AlignWork=',NeedAlignWork,' ControlCount=',ControlCount);
//if AControl<>nil then DebugLn(' AControl=',AControl.Name,':',AControl.ClassName);
//end;
if NeedAlignWork then
begin
AdjustClientRect(RemainingClientRect);
//DebugLn(['TWinControl.AlignControls AAA1 ',DbgSName(Self),' RemainingClientRect=',dbgs(RemainingClientRect),' ',dbgs(ClientRect)]);
RemainingBorderSpace:=Rect(0,0,0,0);
// adjust RemainingClientRect by ChildSizing properties
AdjustBorderSpace(RemainingClientRect,RemainingBorderSpace,
ChildSizing.LeftRightSpacing,ChildSizing.TopBottomSpacing,
ChildSizing.LeftRightSpacing,ChildSizing.TopBottomSpacing);
//DebugLn('[TWinControl.AlignControls] ',Name,':',Classname,' ',Left,',',Top,',',Width,',',Height,' ClientRect=',RemainingClientRect.Left,',',RemainingClientRect.Top,',',RemainingClientRect.Right,',',RemainingClientRect.Bottom);
AlignList := TFPList.Create;
try
// Auto aligning/anchoring can be very interdependent.
// In worst case the n-2 depends on the n-1, the n-3 depends on n-2
// and so forth. This is allowed, so do up to n loop step.
// Do not more, to avoid endless loops, if there are circle
// dependencies.
MaxTries:=ControlCount;
{$IFDEF CHECK_POSITION}inc(MaxTries);{$ENDIF}
for i:=1 to MaxTries do begin
// align and anchor child controls
BoundsMutated:=false;
OldRemainingClientRect:=RemainingClientRect;
OldRemainingBorderSpace:=RemainingBorderSpace;
DoAlign(alTop);
DoAlign(alBottom);
DoAlign(alLeft);
DoAlign(alRight);
DoAlign(alClient);
DoAlign(alCustom);
DoAlign(alNone);
DoAlignNotAligned;
if not BoundsMutated then break;
if (i=ControlCount+1) then begin
DebugLn(['TWinControl.AlignControls ENDLESS LOOP STOPPED ',DbgSName(Self),' i=',i]);
if LastBoundsMutated<>nil then
DebugLn(['TWinControl.AlignControls LAST CHANGED: ',DbgSName(LastBoundsMutated),' Old=',dbgs(LastBoundsMutatedOld),' Now=',dbgs(LastBoundsMutated.BoundsRect)]);
end;
// update again
RemainingClientRect:=OldRemainingClientRect;
RemainingBorderSpace:=OldRemainingBorderSpace;
end;
finally
AlignList.Free;
end;
end;
ControlsAligned;
finally
Exclude(FWinControlFlags,wcfAligningControls);
end;
// let childs autosize themselves
for i:=0 to ControlCount-1 do begin
ChildControl:=Controls[i];
if cfAutoSizeNeeded in ChildControl.FControlFlags then begin
//DebugLn(['TWinControl.AlignControls AdjustSize: ',DbgSName(ChildControl),' ',(not ChildControl.AutoSizeCanStart),' ',ChildControl.AutoSizeDelayed]);
ChildControl.AdjustSize;
end;
end;
end;
function TWinControl.DoAlignChildControls(TheAlign: TAlign; AControl: TControl;
AControlList: TFPList; var ARect: TRect): Boolean;
begin
Result:=false;
end;
procedure TWinControl.DoChildSizingChange(Sender: TObject);
begin
//debugln('TWinControl.DoChildSizingChange ',DbgSName(Self));
if ControlCount=0 then exit;
InvalidatePreferredSize;
ReAlign;
end;
procedure TWinControl.ResizeDelayedAutoSizeChildren;
var
i: Integer;
Child: TControl;
AWinControl: TWinControl;
begin
if ControlCount=0 then exit;
//DebugLn(['TWinControl.ResizeDelayedAutoSizeChildren START ',DbgSName(Self),' Visible=',Visible]);
if AutoSizeDelayed then exit;
//DebugLn(['TWinControl.ResizeDelayedAutoSizeChildren RUN ',DbgSName(Self)]);
DisableAlign;
try
for i:=0 to ControlCount-1 do begin
Child:=Controls[i];
if Child.AutoSizeDelayed then continue;
if cfRequestAlignNeeded in Child.FControlFlags then
Child.RequestAlign;
if cfAutoSizeNeeded in Child.FControlFlags then
Child.AdjustSize;
if Child is TWinControl then begin
AWinControl:=TWinControl(Child);
AWinControl.ResizeDelayedAutoSizeChildren;
if wcfReAlignNeeded in AWinControl.FWinControlFlags then
AWinControl.ReAlign;
end;
end;
finally
EnableAlign;
end;
end;
{-------------------------------------------------------------------------------
procedure TWinControl.DoAutoSize;
Shrink or enlarge to fit childs.
-------------------------------------------------------------------------------}
procedure TWinControl.DoAutoSize;
var
HasVisibleChilds: boolean;
function WidthAnchored(CurAnchors: TAnchors): boolean;
begin
Result:=(CurAnchors*[akLeft,akRight]=[akLeft,akRight]);
end;
function WidthDependsOnChilds: boolean;
begin
Result:=false;
end;
function WidthDependsOnParent: boolean;
begin
if Parent=nil then
Result:=false
else
Result:=(Parent.ChildSizing.Layout<>cclNone);
end;
function HeightAnchored(CurAnchors: TAnchors): boolean;
begin
Result:=(CurAnchors*[akTop,akBottom]=[akTop,akBottom]);
end;
function HeightDependsOnChilds: boolean;
begin
Result:=false;
end;
function HeightDependsOnParent: boolean;
begin
if Parent=nil then
Result:=false
else
Result:=Parent.ChildSizing.Layout<>cclNone;
end;
procedure GetMoveDiffForNonAlignedChilds(const CurClientRect: TRect;
out dx, dy: integer);
// how much can non-aligned-childs be moved up and left
// non-aligned-childs: no fixed anchoring or autosizing,
// (Align=alNone, visible, AnchorSide[].Control=nil)
// borderspacing is used
// e.g. dx=10 means all non-align-childs should be moved 10 pixels to the left
var
i: Integer;
Child: TControl;
p: Integer;
MinSpaceLeft: LongInt;
MinSpaceTop: LongInt;
begin
if ChildSizing.Layout<>cclNone then begin
dx:=0;
dy:=0;
exit;
end;
dx:=High(integer);
dy:=High(integer);
MinSpaceLeft:=ChildSizing.LeftRightSpacing;
MinSpaceTop:=ChildSizing.TopBottomSpacing;
for i:=0 to ControlCount-1 do begin
Child:=Controls[i];
if Child.Align<>alNone then continue;
if not Child.IsControlVisible then continue;
if (akLeft in Child.Anchors)
and (Child.AnchorSide[akLeft].Control=nil) then begin
// the left side of this control is only defined by designer coords
p:=Child.Left
-Max(Child.BorderSpacing.GetSpace(akLeft),MinSpaceLeft)
-CurClientRect.Left;
if dx>p then dx:=p;
end;
if (akTop in Child.Anchors)
and (Child.AnchorSide[akTop].Control=nil) then begin
// the top side of this control is only defined by designer coords
p:=Child.Top
-Max(Child.BorderSpacing.GetSpace(akTop),MinSpaceTop)
-CurClientRect.Top;
if dy>p then dy:=p;
end;
end;
if dx=High(integer) then
dx:=0;
if dy=High(integer) then
dy:=0;
end;
var
I: Integer;
AControl: TControl;
PreferredWidth: LongInt;
PreferredHeight: LongInt;
CurClientRect: TRect;
WidthIsFixed: boolean;
HeightIsFixed: boolean;
NewLeft: LongInt;
NewTop: LongInt;
CurAnchors: TAnchors;
dx: Integer;
dy: Integer;
NewChildBounds: TRect;
OldChildBounds: TRect;
begin
{$IFDEF VerboseAutoSize}
debugln('TWinControl.DoAutoSize ',DbgSName(Self));
{$ENDIF}
if (not AutoSizeCanStart) or AutoSizeDelayed then begin
Include(FControlFlags,cfAutoSizeNeeded);
exit;
end;
DisableAutoSizing;
DisableAlign;
try
// test if resizing is possible
HasVisibleChilds:=false;
for i:=0 to ControlCount-1 do
if Controls[i].IsControlVisible then begin
HasVisibleChilds:=true;
break;
end;
CurAnchors:=Anchors;
if Align<>alNone then CurAnchors:=CurAnchors+AnchorAlign[Align];
WidthIsFixed:=WidthAnchored(CurAnchors)
or WidthDependsOnChilds
or WidthDependsOnParent;
HeightIsFixed:=HeightAnchored(CurAnchors)
or HeightDependsOnChilds
or HeightDependsOnParent;
// move free childs as much as possible to left and top (all free childs the same)
if HasVisibleChilds then begin
CurClientRect:=GetLogicalClientRect;
AdjustClientRect(CurClientRect);
// get minimum left, top of non aligned childs
GetMoveDiffForNonAlignedChilds(CurClientRect,dx,dy);
//DebugLn(['TWinControl.DoAutoSize ',DbgSName(Self),' ChildsBounds=',dbgs(ChildBounds),' CurClientRect=',dbgs(CurClientRect)]);
if (dx<>0) or (dy<>0) then begin
// move all free childs to left and top of client area
//DebugLn(['TWinControl.DoAutoSize ',DbgSName(Self),' dx=',dbgs(dx),' dy=',dbgs(dy),' CurClientRect=',dbgs(CurClientRect),' CurAnchors=',dbgs(CurAnchors),' IsFixed: w=',WidthIsFixed,' h=',HeightIsFixed]);
for I := 0 to ControlCount - 1 do begin
AControl:=Controls[I];
if not AControl.IsControlVisible then continue;
if AControl.Align<>alNone then continue;
//DebugLn(['TWinControl.DoAutoSize BEFORE ',DbgSName(AControl),' ',dbgs(AControl.BoundsRect)]);
NewChildBounds:=AControl.BoundsRect;
if (akLeft in AControl.Anchors)
and (AControl.AnchorSide[akLeft].Control=nil) then begin
dec(NewChildBounds.Left,dx);
if not (akRight in AControl.Anchors) then
dec(NewChildBounds.Right,dx);
end;
if (akTop in AControl.Anchors)
and (AControl.AnchorSide[akTop].Control=nil) then begin
dec(NewChildBounds.Top,dy);
if not (akBottom in AControl.Anchors) then
dec(NewChildBounds.Bottom,dy);
end;
// Important: change the BaseBounds too, otherwise the changes will be undone by AlignControls
OldChildBounds:=AControl.BoundsRect;
if not CompareRect(@OldChildBounds,@NewChildBounds) then begin
//DebugLn(['TWinControl.DoAutoSize moving child: ',DbgSName(AControl),' Old=',dbgs(OldChildBounds),' New=',dbgs(NewChildBounds)]);
AControl.BoundsRect:=NewChildBounds;
//DebugLn(['TWinControl.DoAutoSize AFTER ',DbgSName(AControl),' ',dbgs(AControl.BoundsRect)]);
end;
end;
end;
end;
// autosize control to preferred size
if (not WidthIsFixed) or (not HeightIsFixed) then begin
GetPreferredSize(PreferredWidth,PreferredHeight,
false,// with constraints
true // with theme space
);
//if ControlCount>0 then DebugLn(['TWinControl.DoAutoSize ',DbgSName(Self),' PreferredWidth=',PreferredWidth,' PreferredHeight=',PreferredHeight,' ControlCount=',ControlCount]);
end else begin
PreferredWidth:=0;
PreferredHeight:=0;
end;
if WidthIsFixed or (PreferredWidth<=0) then
PreferredWidth:=Constraints.MinMaxWidth(Width);
if HeightIsFixed or (PreferredHeight<=0) then
PreferredHeight:=Constraints.MinMaxHeight(Height);
// set new size
{$IFDEF VerboseAutoSize}
debugln(['DoAutoSize A ',DbgSName(Self),' Cur=',Width,'x',Height,' Prefer=',PreferredWidth,'x',PreferredHeight,' WidgetClass=',WidgetSetClass.ClassName,' Fixed=',WidthIsFixed,'x',HeightIsFixed]);
{$ENDIF}
if (PreferredWidth<>Width) or (PreferredHeight<>Height) then begin
// adjust Left/Top as well to reduce auto sizing overhead
NewLeft:=Left;
NewTop:=Top;
if akRight in CurAnchors then
inc(NewLeft,Width-PreferredWidth);
if akBottom in CurAnchors then
inc(NewTop,Height-PreferredHeight);
//if CompareText(Name,'NewUnitOkButton')=0 then
//debugln(['DoAutoSize Resize ',DbgSName(Self),' Old=',dbgs(BoundsRect),' New=',dbgs(Bounds(NewLeft,NewTop,PreferredWidth,PreferredHeight)),' WidthIsFixed=',WidthIsFixed,' HeightIsFixed=',HeightIsFixed,' Align=',dbgs(Align),' Anchors=',dbgs(Anchors)]);
SetAlignedBounds(NewLeft,NewTop,PreferredWidth,PreferredHeight);
end;
finally
Exclude(FControlFlags,cfAutoSizeNeeded);
EnableAutoSizing;
EnableAlign;
end;
end;
{------------------------------------------------------------------------------
TWinControl BroadCast
------------------------------------------------------------------------------}
Procedure TWinControl.BroadCast(var ToAllMessage);
var
I: Integer;
begin
for I := 0 to ControlCount - 1 do
begin
Controls[I].WindowProc(TLMessage(ToAllMessage));
if TLMessage(ToAllMessage).Result <> 0 then Exit;
end;
end;
procedure TWinControl.NotifyControls(Msg: Word);
var
ToAllMessage: TLMessage;
begin
ToAllMessage.Msg := Msg;
ToAllMessage.WParam := 0;
ToAllMessage.LParam := 0;
ToAllMessage.Result := 0;
Broadcast(ToAllMessage);
end;
procedure TWinControl.DefaultHandler(var AMessage);
begin
CallDefaultWndHandler(Self,AMessage);
end;
{------------------------------------------------------------------------------
TWinControl CanFocus
------------------------------------------------------------------------------}
Function TWinControl.CanFocus : Boolean;
var
Control: TWinControl;
Form: TCustomForm;
begin
Result := False;
//Verify that every parent is enabled and visible before returning true.
Form := GetParentForm(Self);
if Form <> nil then
begin
Control := Self;
repeat
if Control = Form then break;
// test all except the Form if it is visible and enabled
if not (Control.IsControlVisible and Control.Enabled) then Exit;
Control := Control.Parent;
until false;
Result := True;
end;
end;
{------------------------------------------------------------------------------
TWinControl CMDrag
------------------------------------------------------------------------------}
Procedure TWinControl.CMDrag(var Message: TCMDrag);
Begin
{$IFDEF VerboseDrag}
DebugLn('TWinControl.CMDrag ',Name,':',ClassName,' ',IntToStr(ord(Message.DragMessage)));
{$ENDIF}
DoDragMsg(Message);
end;
{------------------------------------------------------------------------------
TWinControl CreateSubClass
------------------------------------------------------------------------------}
procedure TWinControl.CreateSubClass(var Params: TCreateParams;
ControlClassName: PChar);
begin
// TODO: Check if we need this method
end;
{------------------------------------------------------------------------------
TWinControl DisableAlign
------------------------------------------------------------------------------}
procedure TWinControl.DisableAlign;
begin
Inc(FAlignLevel);
//DebugLn(['TWinControl.DisableAlign ',dbgsName(Self),' ',FAlignLevel]);
end;
{-------------------------------------------------------------------------------
TWinControl DoAdjustClientRectChange
Asks the interface if clientrect has changed since last AlignControl
and calls AlignControl(nil) on change.
-------------------------------------------------------------------------------}
procedure TWinControl.DoAdjustClientRectChange;
var r: TRect;
begin
r:=GetClientRect;
AdjustClientRect(r);
//DebugLn(['TWinControl.DoAdjustClientRectChange ',DbgSName(Self),' ',r.Right,',',r.Bottom,' ',CompareRect(@r,@FAdjustClientRectRealized)]);
if not CompareRect(@r,@FAdjustClientRectRealized) then begin
// client rect changed since last AlignControl
{$IFDEF VerboseClientRectBugFix}
DebugLn('UUU TWinControl.DoAdjustClientRectChange ClientRect changed ',Name,':',ClassName,
' Old=',Dbgs(FAdjustClientRectRealized.Right),'x',DbgS(FAdjustClientRectRealized.Bottom),
' New=',DbgS(r.Right),'x',DbgS(r.Bottom));
{$ENDIF}
FAdjustClientRectRealized:=r;
ReAlign;
Resize;
AdjustSize;
end;
end;
{-------------------------------------------------------------------------------
TWinControl DoConstraintsChange
Params: Sender : TObject
Call inherited, then send the constraints to the interface
-------------------------------------------------------------------------------}
procedure TWinControl.DoConstraintsChange(Sender : TObject);
begin
inherited DoConstraintsChange(Sender);
//debugln('TWinControl.DoConstraintsChange ',DbgSName(Self),' HandleAllocated=',dbgs(HandleAllocated));
if HandleAllocated then
TWSWinControlClass(WidgetSetClass).ConstraintsChange(Self);
end;
{-------------------------------------------------------------------------------
TWinControl InvalidateClientRectCache(WithChildControls: boolean)
The clientrect is cached. Call this procedure to invalidate the cache, so that
next time the clientrect is fetched from the interface.
-------------------------------------------------------------------------------}
procedure TWinControl.InvalidateClientRectCache(WithChildControls: boolean);
var
I: Integer;
begin
{$IFDEF VerboseClientRectBugFix}
DebugLn('[TWinControl.InvalidateClientRectCache] ',DbgSName(Self));
{$ENDIF}
Include(FWinControlFlags,wcfClientRectNeedsUpdate);
if WithChildControls then begin
// invalidate clients too
if Assigned(FWinControls) then
for I := 0 to FWinControls.Count - 1 do
if Assigned(FWinControls.Items[I]) then
TWinControl(FWinControls.Items[I]).InvalidateClientRectCache(true);
end;
InvalidatePreferredSize;
end;
{-------------------------------------------------------------------------------
TWinControl ClientRectNeedsInterfaceUpdate
The clientrect is cached. Check if cache is valid.
-------------------------------------------------------------------------------}
function TWinControl.ClientRectNeedsInterfaceUpdate: boolean;
var
IntfClientRect: TRect;
begin
if (not HandleAllocated) or (csDestroyingHandle in ControlState)
or (csDestroying in ComponentState)
then
exit(false);
if wcfClientRectNeedsUpdate in FWinControlFlags then
exit(true);
LCLIntf.GetClientRect(Handle,IntfClientRect);
Result:=(FClientWidth<>IntfClientRect.Right)
or (FClientHeight<>IntfClientRect.Bottom);
end;
{-------------------------------------------------------------------------------
TWinControl DoSetBounds
Params: ALeft, ATop, AWidth, AHeight : integer
Anticipate the new clientwidth/height and call inherited
Normally the clientwidth/clientheight is adjusted automatically by the
interface. But it is up to interface when this will be done. The gtk for
example just puts resize requests into a queue. The LCL would resize the
childs just after this procedure due to the clientrect. On complex forms with
lots of nested controls, this would result in thousands of resizes.
Changing the clientrect in the LCL to the most probable size reduces
unneccessary resizes.
-------------------------------------------------------------------------------}
procedure TWinControl.DoSetBounds(ALeft, ATop, AWidth, AHeight : integer);
begin
{$IFDEF VerboseClientRectBugFix}
DbgOut('[TWinControl.DoSetBounds] ',Name,':',ClassName,
' OldHeight=',DbgS(FHeight),' NewHeight=',DbgS(AHeight));
{$ENDIF}
InvalidateClientRectCache(false);
inherited DoSetBounds(ALeft,ATop,AWidth,AHeight);
end;
{------------------------------------------------------------------------------
TWinControl EnableAlign
------------------------------------------------------------------------------}
procedure TWinControl.EnableAlign;
begin
Dec(FAlignLevel);
//DebugLn(['TWinControl.EnableAlign ',dbgsName(Self),' ',FAlignLevel]);
if FAlignLevel = 0 then begin
if (csAlignmentNeeded in ControlState)
or (wcfReAlignNeeded in FWinControlFlags) then
ReAlign;
end;
end;
procedure TWinControl.WriteLayoutDebugReport(const Prefix: string);
var
i: Integer;
begin
inherited WriteLayoutDebugReport(Prefix);
for i:=0 to ControlCount-1 do
Controls[i].WriteLayoutDebugReport(Prefix+' ');
end;
{------------------------------------------------------------------------------
TWinControl.CanTab
------------------------------------------------------------------------------}
Function TWinControl.CanTab: Boolean;
begin
Result := CanFocus and TWSWinControlClass(WidgetSetClass).CanFocus(Self);
end;
procedure TWinControl.DoDragMsg(var DragMsg: TCMDrag);
var
TargetControl: TControl;
begin
case DragMsg.DragMessage of
dmFindTarget:
begin
{$IFDEF VerboseDrag}
DebugLn('TWinControl.DoDragMsg dmFindTarget ',Name,':',ClassName,' Start DragMsg.DragRec^.Pos=',IntToStr(DragMsg.DragRec^.Pos.X),',',IntToStr(DragMsg.DragRec^.Pos.Y));
{$ENDIF}
TargetControl := ControlatPos(ScreentoClient(DragMsg.DragRec^.Pos),
[capfAllowWinControls,capfRecursive]);
if TargetControl = nil then TargetControl := Self;
{$IFDEF VerboseDrag}
DebugLn('TWinControl.DoDragMsg dmFindTarget ',Name,':',ClassName,' End Result=',TargetControl.Name,':',TargetControl.ClassName);
{$ENDIF}
DragMsg.Result := LRESULT(TargetControl);
end;
else
inherited DoDragMsg(DragMsg);
end;
end;
{------------------------------------------------------------------------------
TWinControl GetChildren
------------------------------------------------------------------------------}
Procedure TWinControl.GetChildren(Proc: TGetChildProc; Root : TComponent);
var
I : Integer;
Control : TControl;
Begin
for I := 0 to ControlCount-1 do
Begin
Control := Controls[i];
if Control.Owner = Root then Proc(Control);
end;
End;
{-------------------------------------------------------------------------------
function TWinControl.ChildClassAllowed(ChildClass: TClass): boolean;
Allow TControl as child.
-------------------------------------------------------------------------------}
function TWinControl.ChildClassAllowed(ChildClass: TClass): boolean;
begin
Result:=(ChildClass<>nil) and ChildClass.InheritsFrom(TControl);
end;
{-------------------------------------------------------------------------------
TWinControl GetClientOrigin
Result: TPoint
returns the screen coordinate of the topleft coordinate 0,0 of the client area
Note that this value is the position as stored in the interface and is not
always in sync with the LCL. When a control is moved, the LCL sets the bounds
to the wanted position and sends a move message to the interface. It is up to
the interface to handle moves instantly or queued.
-------------------------------------------------------------------------------}
Function TWinControl.GetClientOrigin: TPoint;
var
AControl: TWinControl;
Begin
Result.X := 0;
Result.Y := 0;
//DebugLn('[TWinControl.GetClientOrigin] ',Name,':',ClassName,' ',Handle);
if HandleAllocated then begin
// get the interface idea where the client area is on the screen
LCLIntf.ClientToScreen(Handle,Result);
// adjust the result by all bounds, that are not yet sent to the interface
AControl:=Self;
repeat
inc(Result.X,AControl.Left-AControl.FBoundsRealized.Left);
inc(Result.Y,AControl.Top-AControl.FBoundsRealized.Top);
AControl:=AControl.Parent;
until AControl=nil;
end else
Result:=inherited GetClientOrigin;
Assert(False, Format('Trace:[TWinControl.GetClientOrigin] %s --> (%d, %d)', [Classname, Result.X, Result.Y]));
end;
{-------------------------------------------------------------------------------
TWinControl GetClientRect
Result: TRect
returns the client area. Starting at 0,0.
-------------------------------------------------------------------------------}
function TWinControl.GetClientRect: TRect;
procedure StoreClientRect(NewClientRect: TRect);
var
ClientSizeChanged: boolean;
i: Integer;
begin
if wcfClientRectNeedsUpdate in FWinControlFlags then begin
ClientSizeChanged:=(FClientWidth<>NewClientRect.Right)
or (FClientHeight<>NewClientRect.Bottom);
FClientWidth:=NewClientRect.Right;
FClientHeight:=NewClientRect.Bottom;
{$IFDEF VerboseSizeMsg}
DebugLn(['StoreClientRect ',Name,':',ClassName,' ',FClientWidth,',',FClientHeight,' HandleAllocated=',HandleAllocated]);
{$ENDIF}
if ClientSizeChanged then begin
for i:=0 to ControlCount-1 do
Controls[i].fLastAlignedBoundsTried:=0;
end;
Exclude(FWinControlFlags,wcfClientRectNeedsUpdate);
end;
end;
var
InterfaceWidth, InterfaceHeight: integer;
begin
if wcfClientRectNeedsUpdate in FWinControlFlags then begin
if TWSWinControlClass(WidgetSetClass).GetDefaultClientRect(Self,
Left, Top, Width, Height, Result)
then begin
// the LCL interface provided a ClientRect
end
else if HandleAllocated then begin
// update clientrect from interface
LCLIntf.GetClientRect(Handle, Result);
// the LCL is not always in sync with the interface
// -> adjust client rect based on LCL bounds
// for example: if the Width in LCL differ from the Width of the Interface
// object, then adjust the clientwidth accordingly
// this often anticipates later LM_SIZE messages from the interface
// and reduces resizes
LCLIntf.GetWindowSize(Handle, InterfaceWidth, InterfaceHeight);
//debugln('TWinControl.GetClientRect ',DbgSName(Self),' Interface=',dbgs(InterfaceWidth),',',dbgs(InterfaceHeight),' Result=',dbgs(Result),' Bounds=',dbgs(BoundsRect));
Result.Right:=Width-(InterfaceWidth-Result.Right);
Result.Bottom:=Height-(InterfaceHeight-Result.Bottom);
end else begin
// no handle and no interface help => use defaults
Result:=inherited GetClientRect;
if csLoading in ComponentState then begin
if cfClientWidthLoaded in FControlFlags then
Result.Right:=FLoadedClientSize.X;
if cfClientHeightLoaded in FControlFlags then
Result.Bottom:=FLoadedClientSize.Y;
end;
end;
Result.Right:=Max(Result.Left,Result.Right);
Result.Bottom:=Max(Result.Top,Result.Bottom);
StoreClientRect(Result);
{r:=inherited GetClientRect;
if (r.Left<>Result.Left)
or (r.Top<>Result.Top)
or (r.Right<>Result.Right)
or (r.Bottom<>Result.Bottom) then begin
//DebugLn(' TWinControl.GetClientRect ',Name,':',ClassName,
// ' Old=',r.Left,',',r.Top,',',r.Right,',',r.Bottom,
// ' New=',Result.Left,',',Result.Top,',',Result.Right,',',Result.Bottom
// );
end;}
end else begin
Result:=Rect(0,0,FClientWidth,FClientHeight);
end;
end;
{-------------------------------------------------------------------------------
TWinControl GetControlOrigin
Result: TPoint
Returns the screen coordinate of the topleft coordinate 0,0 of the control
area. (The topleft pixel of the control on the screen)
Note that this value is the position as stored in the interface and is not
always in sync with the LCL. When a control is moved, the LCL sets the bounds
to the wanted position and sends a move message to the interface. It is up to
the interface to handle moves instantly or queued.
-------------------------------------------------------------------------------}
function TWinControl.GetControlOrigin: TPoint;
var
AControl: TWinControl;
IntfBounds: TRect;
Begin
if HandleAllocated then begin
// get the interface idea where the client area is on the screen
LCLIntf.GetWindowRect(Handle,IntfBounds);
Result.X := IntfBounds.Left;
Result.Y := IntfBounds.Top;
// adjust the result by all bounds, that are not yet sent to the interface
AControl:=Self;
repeat
inc(Result.X,AControl.Left-AControl.FBoundsRealized.Left);
inc(Result.Y,AControl.Top-AControl.FBoundsRealized.Top);
AControl:=AControl.Parent;
until AControl=nil;
end else
Result:=inherited GetControlOrigin;
end;
{------------------------------------------------------------------------------
function TWinControl.GetChildsRect(Scrolled: boolean): TRect;
Returns the Client rectangle relative to the controls left, top.
If Scrolled is true, the rectangle is moved by the current scrolling values
(for an example see TScrollingWincontrol).
------------------------------------------------------------------------------}
function TWinControl.GetChildsRect(Scrolled: boolean): TRect;
var
ScrolledOffset: TPoint;
begin
if HandleAllocated then begin
LCLIntf.GetClientBounds(Handle,Result);
if Scrolled then begin
ScrolledOffset:=GetClientScrollOffset;
inc(Result.Left,ScrolledOffset.X);
inc(Result.Top,ScrolledOffset.Y);
inc(Result.Right,ScrolledOffset.X);
inc(Result.Bottom,ScrolledOffset.Y);
end;
end else
Result:=inherited GetChildsRect(Scrolled);
end;
{------------------------------------------------------------------------------
TWinControl SetBorderStyle
------------------------------------------------------------------------------}
procedure TWinControl.SetBorderStyle(NewStyle: TBorderStyle);
begin
FBorderStyle := NewStyle;
if HandleAllocated then
TWSWinControlClass(WidgetSetClass).SetBorderStyle(Self, NewStyle);
end;
{------------------------------------------------------------------------------
TWinControl SetBorderWidth
------------------------------------------------------------------------------}
Procedure TWinControl.SetBorderWidth(value : TBorderWidth);
Begin
if FBorderWidth = Value then exit;
FBorderWidth := Value;
Invalidate;
InvalidatePreferredSize;
AdjustSize;
end;
{------------------------------------------------------------------------------
TWinControl.SetChildZPosition
Set the position of the child control in the FControls (in case of a TControl)
or in the FWinControls (in all other cases) list.
Notes:
* The FControls are always below the FWinControls.
* FControls and FWinControls can be nil
------------------------------------------------------------------------------}
procedure TWinControl.SetChildZPosition(const AChild: TControl;
const APosition: Integer);
var
list: TFPList;
idx, NewPos: Integer;
IsWinControl: boolean;
begin
if AChild = nil
then begin
DebugLn('WARNING: TWinControl.SetChildZPosition: Child = nil');
Exit;
end;
IsWinControl := AChild is TWincontrol;
if IsWinControl
then list := FWinControls
else list := FControls;
if list = nil
then idx := -1
else idx := list.IndexOf(AChild);
if idx = -1
then begin
DebugLn('WARNING: TWinControl.SetChildZPosition: Unknown child');
Exit;
end;
if IsWinControl and (FControls <> nil)
then NewPos := APosition - FControls.Count
else NewPos := APosition;
if NewPos < 0
then NewPos := 0
else if NewPos >= list.Count
then NewPos := list.Count - 1;
if NewPos = idx then Exit;
list.Move(idx, NewPos);
if IsWinControl
then begin
if HandleAllocated and TWinControl(AChild).HandleAllocated
then TWSWinControlClass(WidgetSetClass).SetChildZPosition(Self,
TWinControl(AChild), idx, NewPos, list);
end
else begin
AChild.InvalidateControl(AChild.IsVisible, True, True);
end;
end;
{------------------------------------------------------------------------------
TWinControl.SetTabOrder
------------------------------------------------------------------------------}
procedure TWinControl.SetTabOrder(NewTabOrder: TTabOrder);
begin
if csLoading in ComponentState then
FTabOrder := NewTabOrder
else
UpdateTabOrder(NewTabOrder);
end;
procedure TWinControl.SetTabStop(NewTabStop: Boolean);
begin
if FTabStop = NewTabStop then
exit;
FTabStop := NewTabStop;
UpdateTabOrder(FTabOrder);
end;
{------------------------------------------------------------------------------
TControl UpdateTabOrder
------------------------------------------------------------------------------}
procedure TWinControl.UpdateTabOrder(NewTabOrder: TTabOrder);
var
Count: Integer;
begin
if FParent <> nil then
begin
FTabOrder := GetTabOrder;
Count := ListCount(FParent.FTabList);
if NewTabOrder < 0 then NewTabOrder := Count;
if FTabOrder = -1 then Inc(Count);
if NewTabOrder >= Count then NewTabOrder := Count - 1;
if NewTabOrder <> FTabOrder then
begin
if FTabOrder <> - 1 then
ListDelete(FParent.FTabList,FTabOrder);
if NewTabOrder <> -1 then
begin
ListInsert(FParent.FTabList,NewTabOrder,Self);
FTabOrder := NewTabOrder;
end;
end;
end;
end;
{-------------------------------------------------------------------------------
procedure TWinControl.SendMoveSizeMessages(SizeChanged, PosChanged: boolean);
Send Move and Size messages through the LCL message paths. This simulates the
VCL behaviour and has no real effect.
-------------------------------------------------------------------------------}
procedure TWinControl.SendMoveSizeMessages(SizeChanged, PosChanged: boolean);
var
SizeMsg : TLMSize;
MoveMsg : TLMMove;
//Flags: UINT;
begin
if (not HandleAllocated)
or ((not SizeChanged) and (not PosChanged)) then exit;
Perform(LM_WindowposChanged, 0, 0);
if SizeChanged then begin
with SizeMsg do begin
Msg:= LM_SIZE;
SizeType:= 6; // force realign
Width:= FWidth;
Height:= FHeight;
{$IFDEF CHECK_POSITION}
if CheckPosition(Self) then
DebugLn(' [TControl.SendMoveSizeMessages] ',Name,':',ClassName,' SizeMsg Width=',DbgS(Width),' Height=',DbgS(Height));
{$ENDIF}
end;
WindowProc(TLMessage(SizeMsg));
end;
if PosChanged then begin
with MoveMsg do begin
Msg:= LM_MOVE;
MoveType:= 1;
XPos:= FLeft;
YPos:= FTop;
{$IFDEF CHECK_POSITION}
if CheckPosition(Self) then
DebugLn(' [TControl.SendMoveSizeMessages] ',Name,':',ClassName,' MoveMsg XPos=',Dbgs(XPos),' YPos=',Dbgs(YPos));
{$ENDIF}
end;
WindowProc(TLMessage(MoveMsg));
end;
end;
{------------------------------------------------------------------------------
TWinControl UpdateShowing
------------------------------------------------------------------------------}
procedure TWinControl.UpdateShowing;
var
bShow: Boolean;
n: Integer;
ok: boolean;
begin
bShow := HandleObjectShouldBeVisible;
if bShow then begin
if not HandleAllocated then CreateHandle;
if FWinControls <> nil
then begin
for n := 0 to FWinControls.Count - 1 do
TWinControl(FWinControls[n]).UpdateShowing;
end;
end;
if not HandleAllocated then Exit;
//DebugLn('TWinControl.UpdateShowing A ',Name,':',ClassName,' FShowing=',dbgs(FShowing),' bShow=',dbgs(bShow));
if FShowing = bShow then Exit;
FShowing := bShow;
ok := false;
try
Perform(CM_SHOWINGCHANGED, 0, 0);
ok := true;
finally
if not ok then
FShowing := not bShow;
end;
//DebugLn(['TWinControl.UpdateShowing ',DbgSName(Self),' FShowing=',FShowing,' AutoSizeDelayed=',AutoSizeDelayed]);
if FShowing then
begin
DisableAlign;
try
ResizeDelayedAutoSizeChildren;
AdjustSize;
finally
EnableAlign;
end;
end;
end;
procedure TWinControl.Update;
begin
if HandleAllocated then UpdateWindow(Handle);
end;
{------------------------------------------------------------------------------
TWinControl Focused
------------------------------------------------------------------------------}
function TWinControl.Focused: Boolean;
begin
Result := CanTab and (HandleAllocated and (FindOwnerControl(GetFocus)=Self));
end;
function TWinControl.PerformTab(ForwardTab: boolean): boolean;
Function GetHighestParent(TopControl : TControl) : TWinControl;
begin
Result := nil;
If TopControl = nil then exit;
If (TopControl.Parent=nil) then begin
if TopControl is TWinControl then
Result := TWinControl(TopControl)
end else
Result := GetHighestParent(TopControl.Parent);
end;
var
I : Integer;
List : TFPList;
FirstFocus, OldFocus, NewFocus : TWinControl;
TopLevel : TWinControl;
begin
NewFocus := nil;
OldFocus := nil;
TopLevel := GetHighestParent(Self);
If TopLevel = nil then
exit;
try
List := TFPList.Create;
TopLevel.GetTabOrderList(List);
FirstFocus := nil;
For I := 0 to List.Count - 1 do
If List[I] <> nil then begin
If I = 0 then
FirstFocus := TWinControl(List[I]);
If TWinControl(List[I]).Focused then begin
OldFocus := TWinControl(List[I]);
Break;
end;
end;
Finally
List.Free;
end;
if OldFocus<>nil then
NewFocus := TopLevel.FindNextControl(OldFocus,ForwardTab,True,False);
//DebugLn('TControl.PerformTab A ',DbgSName(Self),' NewFocus=',DbgSName(NewFocus),' OldFocus=',DbgSName(OldFocus));
If (NewFocus = nil) then NewFocus:=FirstFocus;
If NewFocus = OldFocus then begin
Result := True;
exit;
end;
if NewFocus<>nil then begin
NewFocus.SetFocus;
Result := NewFocus.Focused;
end else
Result:=true;
end;
{------------------------------------------------------------------------------
TWinControl FindChildControl
Find next control (Tab control or Child control).
Like VCL the CurControl parameter is ignored.
------------------------------------------------------------------------------}
procedure TWinControl.SelectNext(CurControl: TWinControl; GoForward,
CheckTabStop: Boolean);
begin
CurControl := FindNextControl(CurControl, GoForward,
CheckTabStop, not CheckTabStop);
if CurControl <> nil then CurControl.SetFocus;
end;
procedure TWinControl.SetTempCursor(Value: TCursor);
begin
if not HandleAllocated then exit;
TWSWinControlClass(WidgetSetClass).SetCursor(Self, Screen.Cursors[Value]);
end;
{------------------------------------------------------------------------------
TWinControl FindChildControl
------------------------------------------------------------------------------}
function TWinControl.FindChildControl(const ControlName: string): TControl;
var
I: Integer;
begin
Result := nil;
if FWinControls <> nil then
for I := 0 to FWinControls.Count - 1 do
if CompareText(TWinControl(FWinControls[I]).Name, ControlName) = 0 then
begin
Result := TControl(FWinControls[I]);
Exit;
end;
end;
procedure TWinControl.FlipChildren(AllLevels: Boolean);
var
i: Integer;
FlipControls: TFPList;
CurControl: TControl;
begin
if ControlCount = 0 then exit;
FlipControls := TFPList.Create;
DisableAlign;
try
// Collect all controls with Align Right and Left
for i := 0 to ControlCount - 1 do begin
CurControl:=Controls[i];
if CurControl.Align in [alLeft,alRight] then
FlipControls.Add(CurControl);
end;
// flip the rest
DoFlipChildren;
// reverse Right and Left alignments
while FlipControls.Count > 0 do begin
CurControl:=TControl(FlipControls[FlipControls.Count-1]);
if CurControl.Align=alLeft then
CurControl.Align:=alRight
else if CurControl.Align=alRight then
CurControl.Align:=alLeft;
FlipControls.Delete(FlipControls.Count - 1);
end;
finally
FlipControls.Free;
EnableAlign;
end;
// flip recursively
if AllLevels then begin
for i := 0 to ControlCount - 1 do begin
CurControl:=Controls[i];
if CurControl is TWinControl then
TWinControl(CurControl).FlipChildren(true);
end;
end;
end;
{------------------------------------------------------------------------------}
{ TWinControl FindNextControl }
{------------------------------------------------------------------------------}
function TWinControl.FindNextControl(CurrentControl: TWinControl;
GoForward, CheckTabStop, CheckParent: boolean): TWinControl;
var
List : TFPList;
Next : TWinControl;
I, J : Longint;
begin
Try
Result := nil;
List := TFPList.Create;
GetTabOrderList(List);
//for i:=0 to List.Count-1 do begin
// debugln('TWinControl.FindNextControl TabOrderList ',dbgs(i),' ',DbgSName(TObject(List[i])));
//end;
If List.Count > 0 then begin
J := List.IndexOf(CurrentControl);
if J<0 then begin
if GoForward then
J := List.Count - 1
else
J := 0;
end;
//DebugLn('TWinControl.FindNextControl A ',DbgSName(CurrentControl),' ',dbgs(J),
// ' GoForward='+dbgs(GoForward)+' CheckTabStop='+dbgs(CheckTabStop)+' CheckParent='+dbgs(CheckParent));
I := J;
Repeat
If GoForward then begin
Inc(I);
if I>=List.Count then
I:=0;
end else begin
Dec(I);
if I<0 then
I:=List.Count-1;
end;
if I=J then exit;
Next := TWinControl(List[I]);
//DebugLn('TWinControl.FindNextControl B ',Next.Name,' ',dbgs(I),
// +' ChckTabStop='+dbgs(CheckTabStop)+' TabStop='+dbgs(Next.TabStop)
// +' ChckParent='+dbgs(CheckParent)+' Parent=Self='+dbgs(Next.Parent = Self)
// +' Enabled='+dbgs(Next.Enabled)
// +' TestTab='+dbgs(((Not CheckTabStop) or Next.TabStop))
// +' TestPar='+dbgs(((not CheckParent) or (Next.Parent = Self)))
// +' TestEnVi='+dbgs(Next.Enabled and Next.IsVisible)
// );
If (((not CheckTabStop) or Next.TabStop)
and ((not CheckParent) or (Next.Parent = Self)))
and (Next.Enabled and Next.IsVisible) then
Result := Next;
until (Result <> nil);
//DebugLn('TWinControl.FindNextControl END ',DbgSName(Result),' I=',dbgs(I));
end;
finally
List.Free;
end;
end;
procedure TWinControl.SelectFirst;
var
Form : TCustomForm;
Control : TWinControl;
begin
Form := GetParentForm(Self);
if Form <> nil then begin
Control := FindNextControl(nil, true, true, false);
if Control = nil then
Control := FindNextControl(nil, true, false, false);
if Control <> nil then
Form.ActiveControl := Control;
end;
end;
procedure TWinControl.FixupTabList;
var
Count, I, J: Integer;
List: TFPList;
Control: TWinControl;
begin
if FWinControls <> nil then
begin
List := TFPList.Create;
try
Count := FWinControls.Count;
List.Count := Count;
for I := 0 to Count - 1 do
begin
Control := TWinControl(FWinControls[I]);
J := Control.FTabOrder;
if (J >= 0) and (J < Count) then List[J] := Control;
end;
for I := 0 to Count - 1 do
begin
Control := TWinControl(List[I]);
if Control <> nil then Control.UpdateTabOrder(TTabOrder(I));
end;
finally
List.Free;
end;
end;
end;
{------------------------------------------------------------------------------
TWinControl GetTabOrderList
------------------------------------------------------------------------------}
Procedure TWinControl.GetTabOrderList(List: TFPList);
var
I : Integer;
lWinControl : TWinControl;
begin
If FTabList <> nil then
For I := 0 to FTabList.Count - 1 do begin
lWinControl := TWinControl(FTabList[I]);
If lWinControl.CanTab and lWinControl.TabStop then
List.Add(lWinControl);
lWinControl.GetTabOrderList(List);
end;
end;
{------------------------------------------------------------------------------
TWinControl IsControlMouseMsg
------------------------------------------------------------------------------}
function TWinControl.IsControlMouseMsg(var TheMessage: TLMMouse) : Boolean;
var
Control : TControl;
ScrolledOffset,
P : TPoint;
ClientBounds: TRect;
begin
{ CaptureControl = nil means that widgetset has captured input, but it does
not know anything about TControl controls }
if (FindOwnerControl(GetCapture) = Self) and (CaptureControl <> nil)
then begin
Control := nil;
//DebugLn(['TWinControl.IsControlMouseMsg A ', DbgSName(CaptureControl), ', ',DbgSName(CaptureControl.Parent),', Self: ', DbgSName(Self)]);
if (CaptureControl.Parent = Self)
then Control := CaptureControl;
end
else begin
// do query wincontrol childs, in case they overlap
Control := ControlAtPos(SmallPointToPoint(TheMessage.Pos),
[capfAllowWinControls]);
if Control is TWinControl then begin
// there is a TWinControl child at this position
// TWinControl childs get their own messages
// => ignore here
Control:=nil;
end;
end;
//DebugLn('TWinControl.IsControlMouseMsg B ',DbgSName(Self),' Control=',DbgSName(Control));
Result := False;
if Control <> nil then
begin
// map mouse coordinates to control
ScrolledOffset:=GetClientScrollOffset;
P.X := TheMessage.XPos - Control.Left + ScrolledOffset.X;
P.Y := TheMessage.YPos - Control.Top + ScrolledOffset.Y;
if (Control is TWinControl) and TWinControl(Control).HandleAllocated then
begin
// map coordinates to client area of control
LCLIntf.GetClientBounds(TWinControl(Control).Handle,ClientBounds);
dec(P.X,ClientBounds.Left);
dec(P.Y,ClientBounds.Top);
{$IFDEF VerboseMouseBugfix}
DebugLn('TWinControl.IsControlMouseMsg ',Name,' -> ',Control.Name,
' MsgPos=',TheMessage.Pos.X,',',TheMessage.Pos.Y,
' Control=',Control.Left,',',Control.Top,
' ClientBounds=',ClientBounds.Left,',',ClientBounds.Top,
' Scrolled=',GetClientScrollOffset.X,',',GetClientScrollOffset.Y,
' P=',P.X,',',P.Y
);
{$ENDIF}
end;
Control.Perform(TheMessage.Msg, WParam(TheMessage.Keys),
LParam(Integer(PointToSmallPoint(P))));
Result := True;
end;
end;
procedure TWinControl.FontChanged(Sender: TObject);
var
i: Integer;
begin
ParentFont := False;
if HandleAllocated and ([csLoading, csDestroying]*ComponentState=[]) then
begin
TWSWinControlClass(WidgetSetClass).SetFont(Self, Font);
Exclude(FWinControlFlags,wcfFontChanged);
Invalidate;
end else
Include(FWinControlFlags,wcfFontChanged);
for i := 0 to ControlCount - 1 do
Controls[i].ParentFontChanged;
end;
procedure TWinControl.SetColor(Value: TColor);
begin
if Value=Color then exit;
inherited SetColor(Value);
if FBrush <> nil then
FBrush.Color := Color;
if HandleAllocated and ([csLoading,csDestroying]*ComponentState=[]) then
begin
TWSWinControlClass(WidgetSetClass).SetColor(Self);
Exclude(FWinControlFlags,wcfColorChanged);
end else
Include(FWinControlFlags,wcfColorChanged);
NotifyControls(CM_PARENTCOLORCHANGED);
end;
procedure TWinControl.PaintHandler(var TheMessage: TLMPaint);
function ControlMustBeClipped(AControl: TControl): boolean;
begin
Result := (csOpaque in AControl.ControlStyle) and AControl.IsVisible;
end;
var
I, Clip, SaveIndex: Integer;
DC: HDC;
PS: TPaintStruct; //defined in LCLIntf.pp
ControlsNeedsClipping: boolean;
CurControl: TControl;
begin
//DebugLn('[TWinControl.PaintHandler] ',Name,':',ClassName,' DC=',DbgS(TheMessage.DC,8));
if (csDestroying in ComponentState) or (not HandleAllocated) then exit;
{$IFDEF VerboseDsgnPaintMsg}
if csDesigning in ComponentState then
DebugLn('TWinControl.PaintHandler A ',Name,':',ClassName);
{$ENDIF}
Assert(False, Format('Trace:> [TWinControl.PaintHandler] %s --> Msg.DC: 0x%x', [ClassName, TheMessage.DC]));
DC := TheMessage.DC;
if DC = 0 then DC := BeginPaint(Handle, PS);
try
// check if child controls need clipping
//DebugLn('[TWinControl.PaintHandler] ',DbgSName(Self),' B');
ControlsNeedsClipping:=false;
if FControls<>nil then
for I := 0 to FControls.Count - 1 do
if ControlMustBeClipped(TControl(FControls[I])) then begin
ControlsNeedsClipping:=true;
break;
end;
// exclude child controls and send new paint message
if not ControlsNeedsClipping then begin
//DebugLn('[TWinControl.PaintHandler] ',DbgSName(Self),' no clipping ...');
PaintWindow(DC)
end else
begin
SaveIndex := SaveDC(DC);
Clip := SimpleRegion;
for I := 0 to FControls.Count - 1 do begin
CurControl:=TControl(FControls[I]);
if ControlMustBeClipped(CurControl) then
with CurControl do begin
//DebugLn('TWinControl.PaintHandler Exclude Child ',DbgSName(Self),' Control=',DbgSName(CurControl),'(',dbgs(CurControl.BoundsRect),')');
Clip := ExcludeClipRect(DC, Left, Top, Left + Width, Top + Height);
if Clip = NullRegion then Break;
end;
end;
//DebugLn('[TWinControl.PaintHandler] ',DbgSName(Self),' with clipping ...');
if Clip <> NullRegion then
PaintWindow(DC);
RestoreDC(DC, SaveIndex);
end;
// paint controls
//DebugLn('[TWinControl.PaintHandler] ',DbgSName(Self),' PaintControls ...');
PaintControls(DC, nil);
finally
if TheMessage.DC = 0 then EndPaint(Handle, PS);
end;
Assert(False, Format('Trace:< [TWinControl.PaintHandler] %s', [ClassName]));
//DebugLn('[TWinControl.PaintHandler] END ',Name,':',ClassName,' DC=',DbgS(Message.DC,8));
end;
procedure TWinControl.PaintControls(DC: HDC; First: TControl);
var
I, Count, SaveIndex: Integer;
// FrameBrush: HBRUSH;
TempControl : TControl;
{off $Define VerboseControlDCOrigin}
{$IFDEF VerboseControlDCOrigin}
P: TPoint;
{$ENDIF}
begin
//DebugLn('[TWinControl.PaintControls] ',Name,':',ClassName,' DC=',DbgS(DC,8));
if (csDestroying in ComponentState)
or ((DC=0) and (not HandleAllocated)) then
exit;
{$IFDEF VerboseDsgnPaintMsg}
if csDesigning in ComponentState then
DebugLn('TWinControl.PaintControls A ',Name,':',ClassName);
{$ENDIF}
// Controls that are not TWinControl, have no handle of their own, and so
// they are repainted as part of the parent:
if FControls <> nil then
begin
I := 0;
if First <> nil then
begin
I := FControls.IndexOf(First);
if I < 0 then I := 0;
end;
Count := FControls.Count;
while I < Count do
begin
TempControl := TControl(FControls.Items[I]);
//DebugLn('TWinControl.PaintControls B Self=',Self.Name,':',Self.ClassName,' Control=',TempControl.Name,':',TempControl.ClassName,' ',TempControl.Left,',',TempControl.Top,',',TempControl.Width,',',TempControl.Height);
with TempControl do
if IsVisible
and RectVisible(DC, Rect(Left, Top, Left + Width, Top + Height)) then
begin
if csPaintCopy in Self.ControlState then
Include(FControlState, csPaintCopy);
SaveIndex := SaveDC(DC);
MoveWindowOrg(DC, Left, Top);
{$IFDEF VerboseControlDCOrigin}
DebugLn('TWinControl.PaintControls B Self=',DbgSName(Self),' Control=',DbgSName(TempControl),' ',dbgs(Left),',',dbgs(Top),',',dbgs(Width),',',dbgs(Height));
{$ENDIF}
IntersectClipRect(DC, 0, 0, Width, Height);
{$IFDEF VerboseControlDCOrigin}
DebugLn('TWinControl.PaintControls C');
P:=Point(-1,-1);
GetWindowOrgEx(DC,@P);
debugln(' DCOrigin=',dbgs(P));
{$ENDIF}
Perform(LM_PAINT, WParam(DC), 0);
{$IFDEF VerboseControlDCOrigin}
DebugLn('TWinControl.PaintControls D TempControl=',DbgSName(TempControl));
{$ENDIF}
RestoreDC(DC, SaveIndex);
Exclude(FControlState, csPaintCopy);
end;
Inc(I);
end;
end;
// draw specials for the wincontrols:
if FWinControls <> nil then
for I := 0 to FWinControls.Count - 1 do ;
//DebugLn('[TWinControl.PaintControls] END ',Name,':',ClassName,' DC=',DbgS(DC,8));
end;
procedure TWinControl.PaintWindow(DC: HDC);
var
Message: TLMessage;
begin
//DebugLn('[TWinControl.PaintWindow] ',Name,':',Classname,' DC=',DbgS(DC,8));
if (csDestroying in ComponentState)
or ((DC=0) and (not HandleAllocated)) then
exit;
{$IFDEF VerboseDsgnPaintMsg}
if csDesigning in ComponentState then
DebugLn('TWinControl.PaintWindow A ',Name,':',ClassName);
{$ENDIF}
Message.Msg := LM_PAINT;
Message.WParam := WParam(DC);
Message.LParam := 0;
Message.Result := 0;
DefaultHandler(Message);
end;
procedure TWinControl.CreateBrush;
begin
if FBrush<>nil then exit;
FBrush:=TBrush.Create;
FBrush.Color:=Color;
// ToDo: ParentColor
end;
{------------------------------------------------------------------------------
procedure TWinControl.EraseBackground;
------------------------------------------------------------------------------}
procedure TWinControl.EraseBackground(DC: HDC);
var
ARect: TRect;
begin
if DC = 0 then Exit;
ARect := Rect(0, 0, Width, Height);
FillRect(DC, ARect, Brush.Reference.Handle)
end;
{------------------------------------------------------------------------------
function TWinControl.IntfUTF8KeyPress(var UTF8Key: TUTF8Char;
RepeatCount: integer; SystemKey: boolean): boolean;
Called by the interface after the navigation and specials keys are handled
(e.g. after KeyDown but before KeyPress).
------------------------------------------------------------------------------}
function TWinControl.IntfUTF8KeyPress(var UTF8Key: TUTF8Char;
RepeatCount: integer; SystemKey: boolean): boolean;
begin
Result:=(RepeatCount>0) and (not SystemKey) and DoUTF8KeyPress(UTF8Key);
end;
procedure TWinControl.PaintTo(DC: HDC; X, Y: Integer);
begin
if HandleAllocated then
TWSWinControlClass(WidgetSetClass).PaintTo(Self, DC, X, Y);
end;
procedure TWinControl.PaintTo(ACanvas: TCanvas; X, Y: Integer);
begin
PaintTo(ACanvas.Handle, X, Y);
end;
{------------------------------------------------------------------------------
TWinControl ControlAtPos
Params: const Pos : TPoint
AllowDisabled: Boolean
Results: TControl
Searches a child (not grand child) control, which client area contains Pos.
Pos is relative to the ClientOrigin.
------------------------------------------------------------------------------}
Function TWinControl.ControlAtPos(const Pos : TPoint;
AllowDisabled : Boolean): TControl;
Begin
Result := ControlAtPos(Pos,AllowDisabled,false);
end;
{------------------------------------------------------------------------------
TWinControl ControlAtPos
Params: const Pos : TPoint
AllowDisabled, AllowWinControls: Boolean
Results: TControl
Searches a child (not grand child) control, which client area contains Pos.
Pos is relative to the ClientOrigin.
------------------------------------------------------------------------------}
Function TWinControl.ControlAtPos(const Pos : TPoint;
AllowDisabled, AllowWinControls: Boolean): TControl;
var
Flags: TControlAtPosFlags;
begin
Flags:=[capfOnlyClientAreas];
if AllowDisabled then Include(Flags,capfAllowDisabled);
if AllowWinControls then Include(Flags,capfAllowWinControls);
Result := ControlAtPos(Pos,Flags);
end;
{------------------------------------------------------------------------------
TWinControl ControlAtPos
Params: const Pos : TPoint
Flags: TControlAtPosFlags
Results: TControl
Searches a child (not grand child) control, which contains Pos.
Pos is relative to the ClientOrigin.
------------------------------------------------------------------------------}
function TWinControl.ControlAtPos(const Pos: TPoint;
Flags: TControlAtPosFlags): TControl;
var
I: Integer;
P: TPoint;
LControl: TControl;
ClientBounds: TRect;
function GetControlAtPos(AControl: TControl): Boolean;
var
ControlPos: TPoint;
ControlClientBounds: TRect;
begin
with AControl do
begin
// MG: Delphi checks for PtInRect(ClientRect,P). But the client area is
// not always at 0,0, so I guess this is a bug in the VCL.
ControlPos:=Point(P.X-Left,P.Y-Top);
Result:=(ControlPos.X>=0) and (ControlPos.Y>=0)
and (ControlPos.X<Width) and (ControlPos.Y<Height);
if Result and (capfOnlyClientAreas in Flags) then begin
ControlClientBounds:=GetChildsRect(false);
Result:=PtInRect(ControlClientBounds,ControlPos);
end;
Result:= Result
and (
(
(csDesigning in ComponentState)
and not (csNoDesignVisible in ControlStyle)
// Here was a VCL bug: VCL checks if control is Visible,
// which should be ignored at designtime
)
or
(
(not (csDesigning in ComponentState))
and
(Visible)
and
(Enabled or (capfAllowDisabled in Flags))
and
(Perform(CM_HITTEST, 0,
LParam(Integer(PointToSmallPoint(ControlPos)))) <> 0)
)
);
{$IFDEF VerboseMouseBugfix}
//if Result then
DebugLn(['GetControlAtPos ',Name,':',ClassName,
' Pos=',Pos.X,',',Pos.Y,
' P=',P.X,',',P.Y,
' ControlPos=',dbgs(ControlPos),
' ClientBounds=',ClientBounds.Left,',',ClientBounds.Top,',',ClientBounds.Right,',',ClientBounds.Bottom,
' OnlyCl=',OnlyClientAreas,
' Result=',Result]);
{$ENDIF}
if Result then
LControl := AControl;
end;
end;
var
ScrolledOffset: TPoint;
OldClientOrigin: TPoint;
NewClientOrigin: TPoint;
NewPos: TPoint;
begin
//debugln(['TWinControl.ControlAtPos START ',DbgSName(Self),' P=',dbgs(Pos)]);
// check if Pos in visible client area
ClientBounds:=GetClientRect;
ScrolledOffset:=GetClientScrollOffset;
if capfHasScrollOffset in Flags then
begin
{ ClientBounds do not include scrolling offset }
inc(ClientBounds.Left, ScrolledOffset.x);
inc(ClientBounds.Right, ScrolledOffset.x);
inc(ClientBounds.Top, ScrolledOffset.y);
inc(ClientBounds.Bottom, ScrolledOffset.y);
end;
if not PtInRect(ClientBounds,Pos) then begin
//debugln(['TWinControl.ControlAtPos OUT OF CLIENTBOUNDS ',DbgSName(Self),' P=',dbgs(Pos),' ClientBounds=',dbgs(ClientBounds)]);
//DumpStack;
Result:=nil;
exit;
end;
// map Pos to logical client area
P:=Pos;
if not (capfHasScrollOffset in Flags) then begin
inc(P.X,ScrolledOffset.X);
inc(P.Y,ScrolledOffset.Y);
end;
LControl := nil;
// check wincontrols
if (capfAllowWinControls in Flags) and (FWinControls <> nil) then
for I := FWinControls.Count - 1 downto 0 do
if GetControlAtPos(TControl(FWinControls[I])) then
Break;
// check controls
if (FControls <> nil) and (LControl = nil) then
for I := FControls.Count - 1 downto 0 do
if GetControlAtPos(TControl(FControls[I])) then
Break;
Result := LControl;
// check recursive sub childs
if (capfRecursive in Flags) and (Result is TWinControl)
and (TWinControl(Result).ControlCount>0) then begin
OldClientOrigin:=ClientOrigin;
NewClientOrigin:=TWinControl(Result).ClientOrigin;
NewPos:=Pos;
NewPos.X:=NewPos.X-NewClientOrigin.X+OldClientOrigin.X;
NewPos.Y:=NewPos.Y-NewClientOrigin.Y+OldClientOrigin.Y;
LControl:=TWinControl(Result).ControlAtPos(NewPos,Flags-[capfHasScrollOffset]);
//debugln(['TWinControl.RECURSED ControlAtPos Result=',DbgSName(Result),' LControl=',DbgSName(LControl),' ',dbgs(NewPos),' AllowDisabled=',AllowDisabled,' OnlyClientAreas=',OnlyClientAreas]);
if LControl<>nil then
Result:=LControl;
end;
//debugln(['TWinControl.ControlAtPos END ',DbgSName(Self),' P=',dbgs(Pos),' Result=',DbgSName(Result)]);
end;
{-------------------------------------------------------------------------------
function TWinControl.GetControlIndex(AControl: TControl): integer;
-------------------------------------------------------------------------------}
function TWinControl.GetControlIndex(AControl: TControl): integer;
begin
if FControls <> nil
then begin
Result := FControls.IndexOf(AControl);
if Result >= 0 then Exit;
end;
if FWinControls = nil
then begin
Result:=-1;
Exit;
end;
Result := FWinControls.IndexOf(AControl);
if Result = -1 then Exit;
if FControls = nil then Exit;
Inc(Result, FControls.Count);
end;
{-------------------------------------------------------------------------------
function TWinControl.GetControlIndex(AControl: TControl): integer;
-------------------------------------------------------------------------------}
procedure TWinControl.SetControlIndex(AControl: TControl; NewIndex: integer);
begin
SetChildZPosition(AControl, NewIndex);
end;
function TWinControl.ControlByName(const ControlName: string): TControl;
var
i: Integer;
begin
if FControls<>nil then
for i:=0 to FControls.Count-1 do begin
Result:=TControl(FControls[i]);
if CompareText(Result.Name,ControlName)=0 then exit;
end;
if FWinControls<>nil then
for i:=0 to FWinControls.Count-1 do begin
Result:=TControl(FWinControls[i]);
if CompareText(Result.Name,ControlName)=0 then exit;
end;
Result:=nil;
end;
{------------------------------------------------------------------------------
TWinControl DestroyHandle
------------------------------------------------------------------------------}
procedure TWinControl.DestroyHandle;
var
i: integer;
AWinControl: TWinControl;
begin
if not HandleAllocated then begin
DebugLn('Warning: TWinControl.DestroyHandle ',Name,':',ClassName,' Handle not Allocated');
//RaiseGDBException('');
end;
// First destroy all children handles
Include(FControlState, csDestroyingHandle);
if FWinControls <> nil then begin
for i:= 0 to FWinControls.Count - 1 do begin
//DebugLn([' i=',i]);
//DebugLn([' ',TWinControl(FWinControls[i]).Name,':',TWinControl(FWinControls[i]).ClassName]);
AWinControl:=TWinControl(FWinControls[i]);
if AWinControl.HandleAllocated then
AWinControl.DestroyHandle;
end;
end;
DestroyWnd;
Exclude(FControlState, csDestroyingHandle);
end;
{------------------------------------------------------------------------------
TWinControl WndPRoc
------------------------------------------------------------------------------}
Procedure TWinControl.WndProc(Var Message: TLMessage);
Var
Form: TCustomForm;
Begin
// Assert(False, Format('Trace:[TWinControl.WndPRoc] %s(%s) --> Message = %d', [ClassName, Name, Message.Msg]));
case Message.Msg of
LM_SETFOCUS:
begin
Assert(False, Format('Trace:[TWinControl.WndPRoc] %s --> LM_SETFOCUS', [ClassName]));
{$IFDEF VerboseFocus}
DebugLn('TWinControl.WndProc LM_SetFocus ',DbgSName(Self));
{$ENDIF}
if not (csDestroyingHandle in ControlState) then begin
Form := GetParentForm(Self);
if (Form <> nil) and not Form.SetFocusedControl(Self) then Exit;
Message.Result:=0;
end;
end;
LM_KILLFOCUS:
begin
Assert(False, Format('Trace:[TWinControl.WndPRoc] %s --> _KILLFOCUS', [ClassName]));
if csFocusing in ControlState then begin
{$IFDEF VerboseFocus}
DebugLn('TWinControl.WndProc LM_KillFocus during focusing ',Name,':',ClassName);
{$ENDIF}
Exit;
end;
Message.Result:=0;
end;
LM_NCHITTEST:
begin
inherited WndPRoc(Message);
if (Message.Result = HTTRANSPARENT)
and (ControlAtPos(ScreenToClient(SmallPointToPoint(TLMNCHitTest(Message).Pos)),
False) <> nil)
then Message.Result := HTCLIENT;
Exit;
end;
LM_MOUSEFIRST..LM_MOUSELAST,
LM_LBUTTONTRIPLECLK,
LM_LBUTTONQUADCLK,
LM_MBUTTONTRIPLECLK,
LM_MBUTTONQUADCLK,
LM_RBUTTONTRIPLECLK,
LM_RBUTTONQUADCLK:
begin
{$IFDEF VerboseMouseBugfix}
DebugLn('TWinControl.WndPRoc A ',Name,':',ClassName);
{$ENDIF}
//if Message.Msg=LM_RBUTTONUP then begin DebugLn(['TWinControl.WndProc ',DbgSName(Self)]); DumpStack end;
if IsControlMouseMSG(TLMMouse(Message)) then
Exit;
{$IFDEF VerboseMouseBugfix}
DebugLn('TWinControl.WndPRoc B ',Name,':',ClassName);
{$ENDIF}
end;
LM_KEYFIRST..LM_KEYLAST:
if Dragging then Exit;
LM_CANCELMODE:
if (FindOwnerControl(GetCapture) = Self)
and (CaptureControl <> nil)
and (CaptureControl.Parent = Self)
then CaptureControl.Perform(LM_CANCELMODE,0,0);
else
end;
inherited WndProc(Message);
end;
procedure TWinControl.WSSetText(const AText: String);
begin
TWSWinControlClass(WidgetSetClass).SetText(Self, AText);
InvalidatePreferredSize;
end;
{------------------------------------------------------------------------------
procedure TWinControl.DoAddDockClient(Client: TControl; const ARect: TRect);
Default method for addind a dock client. Just become the new parent.
------------------------------------------------------------------------------}
procedure TWinControl.DoAddDockClient(Client: TControl; const ARect: TRect);
begin
Client.Parent := Self;
end;
{------------------------------------------------------------------------------
procedure TWinControl.DockOver(Source: TDragDockObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
Called to check whether this control allows docking and where.
------------------------------------------------------------------------------}
procedure TWinControl.DockOver(Source: TDragDockObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
begin
PositionDockRect(Source);
DoDockOver(Source,X,Y,State,Accept);
end;
{------------------------------------------------------------------------------
procedure TWinControl.DoDockOver(Source: TDragDockObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
------------------------------------------------------------------------------}
procedure TWinControl.DoDockOver(Source: TDragDockObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
begin
if Assigned(FOnDockOver) then
FOnDockOver(Self,Source,X,Y,State,Accept);
end;
{------------------------------------------------------------------------------
procedure TWinControl.DoRemoveDockClient(Client: TControl);
Called to remove client from dock list.
This method exists for descendent overrides.
------------------------------------------------------------------------------}
procedure TWinControl.DoRemoveDockClient(Client: TControl);
begin
// empty (this method exists for descendent overrides)
end;
{------------------------------------------------------------------------------
function TWinControl.DoUnDock(NewTarget: TWinControl; Client: TControl
): Boolean;
------------------------------------------------------------------------------}
function TWinControl.DoUnDock(NewTarget: TWinControl; Client: TControl;
KeepDockSiteSize: Boolean): Boolean;
var
NewBounds: TRect;
begin
DebugLn('TWinControl.DoUnDock ',Name,' NewTarget=',DbgSName(NewTarget),' Client=',DbgSName(Client));
if Assigned(FOnUnDock) then begin
Result := True;
FOnUnDock(Self,Client,NewTarget,Result);
if not Result then exit;
end;
if not KeepDockSiteSize then begin
NewBounds:=BoundsRect;
case Client.Align of
alLeft:
inc(NewBounds.Left,Client.Width);
alTop:
inc(NewBounds.Top,Client.Height);
alRight:
dec(NewBounds.Right,Client.Width);
alBottom:
dec(NewBounds.Bottom,Client.Height);
end;
SetBoundsKeepBase(NewBounds.Left,NewBounds.Top,
NewBounds.Right-NewBounds.Left,
NewBounds.Bottom-NewBounds.Top);
end;
// ToDo Dock
//Result := (Perform(CM_UNDOCKCLIENT, Integer(NewTarget), Integer(Client)) = 0);
end;
{------------------------------------------------------------------------------
procedure TWinControl.GetSiteInfo(Client: TControl; var InfluenceRect: TRect;
MousePos: TPoint; var CanDock: Boolean);
------------------------------------------------------------------------------}
procedure TWinControl.GetSiteInfo(Client: TControl; var InfluenceRect: TRect;
MousePos: TPoint; var CanDock: Boolean);
begin
GetWindowRect(Handle,InfluenceRect);
// VCL inflates the docking rectangle. Do we need this too? Why?
//InflateRect(InfluenceRect,?,?);
if Assigned(FOnGetSiteInfo) then
FOnGetSiteInfo(Self,Client,InfluenceRect,MousePos,CanDock);
end;
{------------------------------------------------------------------------------
procedure TWinControl.ReloadDockedControl(const AControlName: string;
var AControl: TControl);
------------------------------------------------------------------------------}
procedure TWinControl.ReloadDockedControl(const AControlName: string;
var AControl: TControl);
begin
AControl := Owner.FindComponent(AControlName) as TControl;
end;
{------------------------------------------------------------------------------
function TWinControl.CreateDockManager: TDockManager;
------------------------------------------------------------------------------}
function TWinControl.CreateDockManager: TDockManager;
begin
if (FDockManager = nil) and DockSite and UseDockManager then
Result := DefaultDockTreeClass.Create(Self)
else
Result := FDockManager;
end;
{------------------------------------------------------------------------------
procedure TWinControl.SetUseDockManager(const AValue: Boolean);
------------------------------------------------------------------------------}
procedure TWinControl.SetUseDockManager(const AValue: Boolean);
begin
if FUseDockManager=AValue then exit;
FUseDockManager:=AValue;
if FUseDockManager and ([csDesigning,csDestroying]*ComponentState=[]) then
FDockManager := CreateDockManager;
end;
{------------------------------------------------------------------------------
Procedure TWinControl.MainWndProc(Var Message : TLMessage);
The message handler of this wincontrol.
Only needed by controls, which needs features not yet supported by the LCL.
------------------------------------------------------------------------------}
Procedure TWinControl.MainWndProc(Var Msg: TLMessage);
Begin
Assert(False, Format('Trace:[TWinControl.MainWndPRoc] %s(%s) --> Message = %d', [ClassName, Name, Msg.Msg]));
end;
{------------------------------------------------------------------------------
TWinControl SetFocus
------------------------------------------------------------------------------}
procedure TWinControl.SetFocus;
var
Form : TCustomForm;
begin
{$IFDEF VerboseFocus}
DebugLn('[TWinControl.SetFocus] ',Name,':',ClassName,' Visible=',dbgs(Visible),' HandleAllocated=',dbgs(HandleAllocated));
{$ENDIF}
Form := GetParentForm(Self);
if Form <> nil then
Form.FocusControl(Self)
else if IsVisible and HandleAllocated then
LCLIntf.SetFocus(Handle);
end;
{------------------------------------------------------------------------------
TWinControl SetParentCtl3D
------------------------------------------------------------------------------}
Procedure TWinControl.SetParentCtl3D(value : Boolean);
Begin
if FParentCtl3D <> Value then
Begin
FParentCtl3D := Value;
if FParent <> nil then
Begin
// Sendmessage to do something?
End;
end;
end;
{------------------------------------------------------------------------------
TWinControl KeyDown
------------------------------------------------------------------------------}
Procedure TWinControl.KeyDown(var Key: Word; shift : TShiftState);
Begin
if Assigned(FOnKeyDown) then FOnKeyDown(Self, Key, Shift);
end;
{------------------------------------------------------------------------------
TWinControl KeyDownBeforeInterface
------------------------------------------------------------------------------}
procedure TWinControl.KeyDownBeforeInterface(var Key: Word; Shift: TShiftState
);
begin
KeyDown(Key,Shift);
end;
{------------------------------------------------------------------------------
TWinControl KeyDownAfterInterface
------------------------------------------------------------------------------}
procedure TWinControl.KeyDownAfterInterface(var Key: Word; Shift: TShiftState);
begin
end;
{------------------------------------------------------------------------------
TWinControl KeyPress
------------------------------------------------------------------------------}
Procedure TWinControl.KeyPress(var Key: char);
begin
if Assigned(FOnKeyPress) then FOnKeyPress(Self, Key);
end;
{------------------------------------------------------------------------------
TWinControl UTF8KeyPress
Called before KeyPress.
------------------------------------------------------------------------------}
procedure TWinControl.UTF8KeyPress(var UTF8Key: TUTF8Char);
begin
if Assigned(FOnUTF8KeyPress) then FOnUTF8KeyPress(Self, UTF8Key);
end;
{------------------------------------------------------------------------------
TWinControl KeyUp
------------------------------------------------------------------------------}
Procedure TWinControl.KeyUp(var Key: Word; Shift : TShiftState);
begin
if Assigned(FOnKeyUp) then FOnKeyUp(Self, Key, Shift);
end;
procedure TWinControl.KeyUpBeforeInterface(var Key: Word; Shift: TShiftState);
begin
//debugln('TWinControl.KeyUpBeforeInterface ',DbgSName(Self));
KeyUp(Key,Shift);
end;
procedure TWinControl.KeyUpAfterInterface(var Key: Word; Shift: TShiftState);
begin
//debugln('TWinControl.KeyUpAfterInterface ',DbgSName(Self));
end;
{------------------------------------------------------------------------------
Method: TWinControl.WantsKey
Params: CharCode - the key to inspect whether it is wanted
Returns: true if key is wanted before the interface handles it.
Checks if control wants the passed key to handle before the interface.
------------------------------------------------------------------------------}
function TWinControl.WantsKeyBeforeInterface(Key: word; Shift: TShiftState
): boolean;
var
lWantKeys: dword;
{ values for lWantKeys
0 - if not wanted
1 - if wanted, but is special (arrow)
2 - if wanted, but is special (tab)
4 - if wanted, but is special (all)
8 - if wanted, is normal key
}
begin
// For Delphi compatibility we send a LM_GETDLGCODE message to the control
// asking if it wants to handle the key.
// We don't define a default handler for LM_GETDLGCODE,
// so the default return is 0.
// Note: Contrary to Delphi/win32api, we don't know what keys are special,
// different widgetsets may have different sets of special keys;
lWantKeys := Perform(LM_GETDLGCODE, 0, 0);
if (lWantKeys and DLGC_WANTALLKEYS) <> 0 then
begin
lWantKeys := DLGC_WANTALLKEYS;
end else begin
case Key of
VK_TAB:
lWantKeys := lWantKeys and DLGC_WANTTAB;
VK_UP, VK_LEFT, VK_DOWN, VK_RIGHT:
lWantKeys := lWantKeys and DLGC_WANTARROWS;
end;
end;
Result := (lWantKeys<>0);
end;
{------------------------------------------------------------------------------
TWinControl DoKeyDownBeforeInterface
returns true if handled
------------------------------------------------------------------------------}
function TWinControl.DoKeyDownBeforeInterface(Var Message: TLMKey): Boolean;
var
F: TCustomForm;
ShiftState: TShiftState;
AParent: TWinControl;
begin
//debugln('TWinControl.DoKeyDown ',DbgSName(Self),' ShiftState=',dbgs(KeyDataToShiftState(Message.KeyData)),' CharCode=',dbgs(Message.CharCode));
Result:=true;
with Message do
begin
if CharCode = VK_UNKNOWN then Exit;
ShiftState := KeyDataToShiftState(KeyData);
// let application handle the key
if Application<>nil then
Application.NotifyKeyDownBeforeHandler(Self, CharCode, ShiftState);
if CharCode = VK_UNKNOWN then Exit;
// let each parent form with keypreview handle the key
AParent:=Parent;
while (AParent<>nil) do begin
if (AParent is TCustomForm) then begin
F := TCustomForm(AParent);
if (F.KeyPreview)
and (F.DoKeyDownBeforeInterface(Message)) then Exit;
end;
AParent:=AParent.Parent;
end;
if CharCode = VK_UNKNOWN then Exit;
ShiftState := KeyDataToShiftState(KeyData);
// let drag object handle the key
if Dragging and (DragObject<>nil) then
begin
DragObject.KeyDown(CharCode, ShiftState);
if CharCode = VK_UNKNOWN then Exit;
end;
// let user handle the key
if not (csNoStdEvents in ControlStyle) then
begin
KeyDownBeforeInterface(CharCode, ShiftState);
if CharCode = VK_UNKNOWN then Exit;
end;
end;
Result := False;
end;
function TWinControl.ChildKey(var Message: TLMKey): boolean;
begin
if Assigned(Parent) then
Result := Parent.ChildKey(Message)
else
Result := false;
end;
function TWinControl.DialogChar(var Message: TLMKey): boolean;
var
I: integer;
begin
// broadcast to children
Result := false;
for I := 0 to ControlCount - 1 do
begin
Result := Controls[I].DialogChar(Message);
if Result then exit;
end;
end;
{------------------------------------------------------------------------------
TWinControl DoRemainingKeyDown
Returns True if key handled
------------------------------------------------------------------------------}
function TWinControl.DoRemainingKeyDown(var Message: TLMKeyDown): Boolean;
var
ShiftState: TShiftState;
AParent: TWinControl;
begin
Result:=true;
ShiftState := KeyDataToShiftState(Message.KeyData);
// check popup menu
if Assigned(FPopupMenu) then
begin
if FPopupMenu.IsShortCut(Message) then
exit;
end;
// let each parent form handle shortcuts
AParent:=Parent;
while (AParent<>nil) do begin
if (AParent is TCustomForm) then begin
if TCustomForm(AParent).IsShortcut(Message) then
exit;
end;
AParent:=AParent.Parent;
end;
// let application handle shortcut
if Assigned(Application) and Application.IsShortcut(Message) then
exit;
// let parent(s) handle key from child key
if Assigned(Parent) then
if Parent.ChildKey(Message) then
exit;
// handle LCL special keys
ControlKeyDown(Message.CharCode,ShiftState);
if Message.CharCode=VK_UNKNOWN then exit;
//DebugLn('TWinControl.WMKeyDown ',Name,':',ClassName);
if not (csNoStdEvents in ControlStyle) then
begin
KeyDownAfterInterface(Message.CharCode, ShiftState);
if Message.CharCode=VK_UNKNOWN then exit;
// Note: Message.CharCode can now be different or even 0
end;
// let application handle the remaining key
if Application<>nil then
Application.NotifyKeyDownHandler(Self, Message.CharCode, ShiftState);
if Message.CharCode = VK_UNKNOWN then Exit;
Result := False;
end;
{------------------------------------------------------------------------------
TWinControl DoKeyPress
Returns True if key handled
------------------------------------------------------------------------------}
Function TWinControl.DoKeyPress(Var Message : TLMKey): Boolean;
var
F: TCustomForm;
C: char;
AParent: TWinControl;
begin
Result := True;
// let each parent form with keypreview handle the key
AParent:=Parent;
while (AParent<>nil) do begin
if (AParent is TCustomForm) then begin
F := TCustomForm(AParent);
if (F.KeyPreview)
and (F.DoKeyPress(Message)) then Exit;
end;
AParent:=AParent.Parent;
end;
if not (csNoStdEvents in ControlStyle)
then with Message do
begin
C := char(CharCode);
KeyPress(C);
CharCode := Ord(C);
if Char(CharCode) = #0 then Exit;
end;
Result := False;
End;
{------------------------------------------------------------------------------
TWinControl DoRemainingKeyPress
Returns True if key handled
------------------------------------------------------------------------------}
function TWinControl.SendDialogChar(var Message : TLMKey): Boolean;
var
ParentForm: TCustomForm;
begin
ParentForm := GetParentForm(Self);
if ParentForm <> nil then
begin
Result := ParentForm.DialogChar(Message);
if Result then
begin
Message.CharCode := VK_UNKNOWN;
exit;
end;
end;
end;
function TWinControl.DoRemainingKeyUp(var Message: TLMKeyDown): Boolean;
var
ShiftState: TShiftState;
begin
//debugln('TWinControl.DoRemainingKeyUp ',DbgSName(Self));
Result:=true;
ShiftState := KeyDataToShiftState(Message.KeyData);
// handle LCL special keys
ControlKeyUp(Message.CharCode,ShiftState);
if Message.CharCode=VK_UNKNOWN then exit;
if not (csNoStdEvents in ControlStyle) then
begin
KeyUpAfterInterface(Message.CharCode, ShiftState);
if Message.CharCode=VK_UNKNOWN then exit;
// Note: Message.CharCode can now be different or even 0
end;
end;
{------------------------------------------------------------------------------
TWinControl DoUTF8KeyPress
Returns True if key handled
------------------------------------------------------------------------------}
function TWinControl.DoUTF8KeyPress(var UTF8Key: TUTF8Char): boolean;
var
AParent: TWinControl;
F: TCustomForm;
begin
Result:=true;
// let each parent form with keypreview handle the key
AParent:=Parent;
while (AParent<>nil) do begin
if (AParent is TCustomForm) then begin
F := TCustomForm(AParent);
if (F.KeyPreview)
and (F.DoUTF8KeyPress(UTF8Key)) then Exit;
end;
AParent:=AParent.Parent;
end;
if not (csNoStdEvents in ControlStyle) then begin
UTF8KeyPress(UTF8Key);
if UTF8Key='' then exit;
end;
Result := False;
end;
{------------------------------------------------------------------------------
TWinControl DoKeyUpBeforeInterface
Returns True if key handled
------------------------------------------------------------------------------}
Function TWinControl.DoKeyUpBeforeInterface(Var Message : TLMKey): Boolean;
var
F: TCustomForm;
ShiftState: TShiftState;
AParent: TWinControl;
begin
Result := True;
// let each parent form with keypreview handle the key
AParent:=Parent;
while (AParent<>nil) do begin
if (AParent is TCustomForm) then begin
F := TCustomForm(AParent);
if (F.KeyPreview)
and (F.DoKeyUpBeforeInterface(Message)) then Exit;
end;
AParent:=AParent.Parent;
end;
with Message do
begin
ShiftState := KeyDataToShiftState(KeyData);
if Dragging and (DragObject<>nil) then
begin
DragObject.KeyUp(CharCode, ShiftState);
if CharCode = VK_UNKNOWN then exit;
end;
if not (csNoStdEvents in ControlStyle)
then begin
KeyUpBeforeInterface(CharCode, ShiftState);
if CharCode = VK_UNKNOWN then Exit;
end;
// TODO
//if (CharCode = VK_APPS) and not (ssAlt in ShiftState) then
// CheckMenuPopup(SmallPoint(0, 0));
end;
Result := False;
end;
{------------------------------------------------------------------------------
TWinControl ControlKeyDown
------------------------------------------------------------------------------}
procedure TWinControl.ControlKeyDown(var Key: Word; Shift: TShiftState);
begin
Application.ControlKeyDown(Self,Key,Shift);
end;
procedure TWinControl.ControlKeyUp(var Key: Word; Shift: TShiftState);
begin
//debugln('TWinControl.ControlKeyUp ',DbgSName(Self));
Application.ControlKeyUp(Self,Key,Shift);
end;
{------------------------------------------------------------------------------
TWinControl CreateParams
------------------------------------------------------------------------------}
procedure TWinControl.CreateParams(var Params : TCreateParams);
begin
FillChar(Params, SizeOf(Params),0);
Params.Caption := PChar(FCaption);
Params.Style := WS_CHILD or WS_CLIPSIBLINGS;
if (Parent <> nil) then Params.WndParent := Parent.Handle;
Params.X := FLeft;
Params.Y := FTop;
Params.Width := FWidth;
Params.Height := FHeight;
end;
{------------------------------------------------------------------------------
TWinControl Invalidate
------------------------------------------------------------------------------}
procedure TWinControl.Invalidate;
begin
//DebugLn(['TWinControl.Invalidate ',DbgSName(Self),' HandleAllocated=',HandleAllocated]);
if HandleAllocated and ([csDestroying,csLoading]*ComponentState=[]) then
TWSWinControlClass(WidgetSetClass).Invalidate(Self);
end;
{------------------------------------------------------------------------------
TWinControl AddControl
Add Handle object to parents Handle object.
------------------------------------------------------------------------------}
procedure TWinControl.AddControl;
begin
TWSControlClass(WidgetSetClass).AddControl(Self);
end;
{------------------------------------------------------------------------------
TWinControl Repaint
------------------------------------------------------------------------------}
Procedure TWinControl.Repaint;
Begin
if (not HandleAllocated) or (csDestroying in ComponentState) then exit;
{$IFDEF VerboseDsgnPaintMsg}
if csDesigning in ComponentState then
DebugLn('TWinControl.Repaint A ',Name,':',ClassName);
{$ENDIF}
Invalidate;
Update;
end;
{------------------------------------------------------------------------------
TWinControl DoMouseWheel "Event Handler"
------------------------------------------------------------------------------}
function TWinControl.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
MousePos: TPoint): Boolean;
begin
Result := False;
if Assigned(FOnMouseWheel)
then FOnMouseWheel(Self, Shift, WheelDelta, MousePos, Result);
if not Result
then begin
if WheelDelta < 0
then Result := DoMouseWheelDown(Shift, MousePos)
else Result := DoMouseWheelUp(Shift, MousePos);
end;
end;
{------------------------------------------------------------------------------
TWinControl DoMouseWheelDown "Event Handler"
------------------------------------------------------------------------------}
function TWinControl.DoMouseWheelDown(Shift: TShiftState; MousePos: TPoint): Boolean;
begin
Result := False;
if Assigned(FOnMouseWheelDown) then
FOnMouseWheelDown(Self, Shift, MousePos, Result);
end;
{------------------------------------------------------------------------------
TWinControl DoMouseWheelUp "Event Handler"
------------------------------------------------------------------------------}
function TWinControl.DoMouseWheelUp(Shift: TShiftState; MousePos: TPoint): Boolean;
begin
Result := False;
if Assigned(FOnMouseWheelUp) then
FOnMouseWheelUp(Self, Shift, MousePos, Result);
end;
{------------------------------------------------------------------------------
TWinControl Insert
------------------------------------------------------------------------------}
procedure TWinControl.Insert(AControl : TControl);
begin
Insert(AControl,ControlCount);
End;
{------------------------------------------------------------------------------
procedure TWinControl.Insert(AControl: TControl; Index: integer);
------------------------------------------------------------------------------}
procedure TWinControl.Insert(AControl: TControl; Index: integer);
begin
if AControl = nil then exit;
if AControl = Self then
raise EInvalidOperation.Create(rsAControlCanNotHaveItselfAsParent);
if AControl is TWinControl then
begin
if (FControls<>nil) then dec(Index,FControls.Count);
if (FWinControls<>nil) and (Index<FWinControls.Count) then
FWinControls.Insert(Index,AControl)
else
ListAdd(FWinControls, AControl);
if TWinControl(AControl).TabStop or (csAcceptsControls in AControl.ControlStyle) then
ListAdd(FTabList, AControl);
if (csDesigning in ComponentState) and (not (csLoading in ComponentState))
and AControl.CanTab then
TWinControl(AControl).TabStop := true;
end else begin
if (FControls<>nil) and (Index<FControls.Count) then
FControls.Insert(Index,AControl)
else
ListAdd(FControls, AControl);
end;
AControl.FParent := Self;
end;
{------------------------------------------------------------------------------
TWinControl ReAlign
Realign all childs
------------------------------------------------------------------------------}
procedure TWinControl.ReAlign;
begin
if (csDestroying in ComponentState) then exit;
if (csLoading in ComponentState) or (not HandleAllocated) then begin
Include(FWinControlFlags,wcfReAlignNeeded);
exit;
end;
{$IFDEF VerboseAutoSize}
DebugLn('TWinControl.ReAlign A',Name,':',ClassName,' ', Dbgs(BoundsRect));
{$ENDIF}
AlignControl(nil);
{$IFDEF VerboseAutoSize}
DebugLn('TWinControl.ReAlign B',Name,':',ClassName,' ', Dbgs(BoundsRect));
{$ENDIF}
Exclude(FWinControlFlags,wcfReAlignNeeded);
end;
{------------------------------------------------------------------------------
TWinControl Remove
------------------------------------------------------------------------------}
procedure TWinControl.Remove(AControl : TControl);
begin
if AControl <> nil then
begin
Assert(False, Format('trace:[TWinControl.Remove] %s(%S) --> Remove: %s(%s)', [ClassName, Name, AControl.ClassName, AControl.Name]));
if AControl is TWinControl then
begin
ListRemove(FTabList, AControl);
ListRemove(FWInControls, ACOntrol);
end else
Listremove(FControls, AControl);
AControl.FParent := Nil;
end;
end;
procedure TWinControl.AlignNonAlignedControls(ListOfControls: TFPList;
var BoundsModified: Boolean);
{ All controls, not aligned/anchored by their own properties, can be auto aligned.
Example:
cclLeftToRightThenTopToBottom
+-----------------------------------+
|+---------------------------------+|
|| Control1 | Control2 | Control 3 ||
|+---------------------------------+|
|+---------------------------------+|
|| Control4 | Control5 | Control 6 ||
|+---------------------------------+|
|+---------------------+ |
|| Control7 | Control8 | |
|+---------------------+ |
+-----------------------------------+
}
var
Box: TAutoSizeBox;
r: TRect;
begin
// check if ChildSizing aligning is enabled
if (ChildSizing.Layout=cclNone) or (ListOfControls.Count=0) then
exit;
//debugln('TWinControl.AlignNonAlignedControls ',DbgSName(Self),' ListOfControls.Count=',dbgs(ListOfControls.Count),' ',dbgs(ord(ChildSizing.EnlargeHorizontal)));
Box:=TAutoSizeBox.Create;
try
r:=GetLogicalClientRect;
BoundsModified:=Box.AlignControlsInTable(ListOfControls,ChildSizing,
r.Right,r.Bottom,true);
finally
Box.Free;
end;
end;
function TWinControl.IsClientHeightStored: boolean;
begin
// The ClientHeight is needed to restore childs anchored akBottom
Result:=ControlCount>0;
end;
function TWinControl.IsClientWidthStored: boolean;
begin
// The ClientWidth is needed to restore childs anchored akRight
Result:=ControlCount>0;
end;
{------------------------------------------------------------------------------
TWinControl RemoveFocus
------------------------------------------------------------------------------}
procedure TWinControl.RemoveFocus(Removing : Boolean);
var
Form: TCustomForm;
begin
Form := GetParentForm(Self);
if Form <> nil then Form.DefocusControl(Self, Removing);
end;
{------------------------------------------------------------------------------
TWinControl UpdateControlState
------------------------------------------------------------------------------}
procedure TWinControl.UpdateControlState;
var AWinControl: TWinControl;
begin
AWinControl:= Self;
{ If any of the parent is not visible, exit }
while AWinControl.Parent <> nil do
begin
AWinControl:= AWinControl.Parent;
if (not AWinControl.Showing) or (not AWinControl.HandleAllocated) then Exit;
end;
if ((AWinControl is TCustomForm) and (AWinControl.Parent=nil))
or (AWinControl.FParentWindow <> 0) then
UpdateShowing;
end;
{------------------------------------------------------------------------------
TWinControl InsertControl
------------------------------------------------------------------------------}
procedure TWinControl.InsertControl(AControl: TControl);
begin
InsertControl(AControl,ControlCount);
end;
procedure TWinControl.InsertControl(AControl: TControl; Index: integer);
begin
AControl.ValidateContainer(Self);
Perform(CM_CONTROLLISTCHANGE, WParam(AControl), LParam(True));
Insert(AControl,Index);
if not (csReadingState in AControl.ControlState) then
begin
AControl.Perform(CM_PARENTCOLORCHANGED, 0, 0);
AControl.Perform(CM_PARENTSHOWHINTCHANGED, 0, 0);
AControl.Perform(CM_PARENTBIDIMODECHANGED, 0, 0);
AControl.ParentFontChanged;
if AControl is TWinControl then
begin
AControl.Perform(CM_PARENTCTL3DCHANGED, 0, 0);
TWinControl(AControl).UpdateControlState;
end else
if HandleAllocated then AControl.Invalidate;
//DebugLn('TWinControl.InsertControl ',Name,':',ClassName);
end;
if not (csDestroying in ComponentState) then
AControl.RequestAlign;
Perform(CM_CONTROLCHANGE, WParam(AControl), LParam(True));
end;
{------------------------------------------------------------------------------
TWinControl removeControl
------------------------------------------------------------------------------}
Procedure TWinControl.RemoveControl(AControl: TControl);
var
AWinControl: TWinControl;
Begin
Perform(CM_CONTROLCHANGE, WParam(AControl), LParam(False));
if AControl is TWinControl then begin
AWinControl:=TWinControl(AControl);
AWinControl.RemoveFocus(True);
if AWinControl.HandleAllocated then AWinControl.DestroyHandle;
end else
if HandleAllocated then
AControl.InvalidateControl(AControl.IsVisible, False, True);
Remove(AControl);
if not (csDestroying in ComponentState) then
Realign;
End;
{------------------------------------------------------------------------------
TWinControl AlignControl
------------------------------------------------------------------------------}
procedure TWinControl.AlignControl(AControl: TControl);
var
ARect: TRect;
i: Integer;
ChildControl: TControl;
NewRect: TRect;
begin
//if csDesigning in ComponentState then begin
// DbgOut('TWinControl.AlignControl ',Name,':',ClassName);
// if AControl<>nil then DebugLn(' AControl=',AControl.Name,':',AControl.ClassName) else DebugLn(' AControl=nil');;
//end;
if csDestroying in ComponentState then exit;
if FAlignLevel <> 0 then begin
Include(FControlState, csAlignmentNeeded);
exit;
end;
// check if all childs have finished loading
for i:=0 to ControlCount-1 do begin
ChildControl:=Controls[i];
if csLoading in ChildControl.ComponentState then begin
// child is loading
// -> mark the child, that itself and its brothers needs realigning
// (it will do this, when it has finished loading)
Include(ChildControl.FControlFlags,cfRequestAlignNeeded);
exit;
end;
end;
DisableAlign;
try
ARect:= GetLogicalClientRect;
AlignControls(AControl, ARect);
// some widgetsets updates their clientrect when the first child was moved
// do a second pass if ClientRect changed
NewRect:=GetLogicalClientRect;
if not CompareRect(@ARect,@NewRect) then
AlignControls(AControl, NewRect);
finally
Exclude(FControlState, csAlignmentNeeded);
EnableAlign;
if (FAlignLevel=0) and (not IsAParentAligning) and (FWinControls<>nil) then
for i:=0 to FWinControls.Count-1 do
TWinControl(FWinControls[i]).RealizeBoundsRecursive;
end;
End;
{------------------------------------------------------------------------------
Method: TWinControl.ContainsControl
Params: Control: the control to be checked
Returns: Self is a (super)parent of Control
Checks if Control is a child of Self
------------------------------------------------------------------------------}
function TWinControl.ContainsControl(Control: TControl): Boolean;
begin
while (Control <> nil) and (Control <> Self) do Control := Control.Parent;
Result := Control = Self;
end;
{------------------------------------------------------------------------------
TWinControl GetBorderStyle
------------------------------------------------------------------------------}
function TWinControl.GetBorderStyle: TBorderStyle;
begin
Result := TBorderStyle(FBorderStyle);
end;
{------------------------------------------------------------------------------
TWinControl GetBrush
------------------------------------------------------------------------------}
function TWinControl.GetBrush: TBrush;
begin
if FBrush=nil then CreateBrush;
Result:=FBrush;
end;
{------------------------------------------------------------------------------
TWinControl GetControl
------------------------------------------------------------------------------}
function TWinControl.GetControl(const Index: Integer): TControl;
var
N: Integer;
begin
if FControls <> nil then
N := FControls.Count
else
N := 0;
if Index < N then
Result := TControl(FControls[Index])
else
Result := TControl(FWinControls[Index - N]);
end;
{------------------------------------------------------------------------------
TWinControl GetControlCount
------------------------------------------------------------------------------}
function TWinControl.GetControlCount: Integer;
begin
Result := 0;
if FControls <> nil then Inc(Result, FControls.Count);
if FWinControls <> nil then Inc(Result, FWinControls.Count);
end;
function TWinControl.GetDockClientCount: Integer;
begin
if FDockClients<>nil then
Result := FDockClients.Count
else
Result := 0;
end;
function TWinControl.GetDockClients(Index: Integer): TControl;
begin
if FDockClients<>nil then
Result := TControl(FDockClients[Index])
else
Result := nil;
end;
{------------------------------------------------------------------------------
TWinControl GetHandle
------------------------------------------------------------------------------}
function TWinControl.GetHandle: HWND;
begin
//if not HandleAllocated then DebugLn('TWinControl.GetHandle Creating handle on the fly: ',DbgSName(Self));
HandleNeeded;
Result := FHandle;
end;
{------------------------------------------------------------------------------
TWinControl SetHandle
Params: NewHandle
Returns: Nothing
-------------------------------------------------------------------------------}
procedure TWincontrol.SetHandle(NewHandle: HWND);
begin
//if (NewHandle=0) and (AnsiCompareText(ClassName,'TPAGE')=0) then
// RaiseGDBException('TWincontrol.SetHandle');
FHandle:=NewHandle;
InvalidatePreferredSize;
end;
{------------------------------------------------------------------------------
Method: TWinControl.Create
Params: None
Returns: Nothing
Contructor for the class.
------------------------------------------------------------------------------}
constructor TWinControl.Create(TheOwner : TComponent);
begin
// do not set borderstyle, because TCustomForm needs to set it before calling
// inherited, to have it set before handle is created via streaming
// use property that bsNone is zero
//FBorderStyle := bsNone;
inherited Create(TheOwner);
FCompStyle := csWinControl;
FChildSizing:=TControlChildSizing.Create(Self);
FChildSizing.OnChange:=@DoChildSizingChange;
FBrush := nil; // Brush will be created on demand. Only few controls need it.
FParentCtl3D:=true;
FTabOrder := -1;
FTabStop := False;
InvalidateClientRectCache(false);
end;
{------------------------------------------------------------------------------
TWinControl CreateParented
------------------------------------------------------------------------------}
constructor TWinControl.CreateParented(ParentWindow: hwnd);
begin
FParentWindow := ParentWindow;
inherited Create(nil);
end;
{------------------------------------------------------------------------------
TWinControl CreateParentedControl
------------------------------------------------------------------------------}
class function TWinControl.CreateParentedControl(ParentWindow: hwnd): TWinControl;
begin
// ToDo
Result:=nil;
end;
{------------------------------------------------------------------------------
Method: TWinControl.Destroy
Params: None
Returns: Nothing
Destructor for the class.
------------------------------------------------------------------------------}
destructor TWinControl.Destroy;
var
n: Integer;
Control: TControl;
begin
//DebugLn('[TWinControl.Destroy] A ',Name,':',ClassName);
// prevent parent to try to focus a to be destroyed control
if Parent <> nil then
RemoveFocus(true);
if HandleAllocated then
DestroyHandle;
//DebugLn('[TWinControl.Destroy] B ',Name,':',ClassName);
//for n:=0 to ComponentCount-1 do
// DebugLn(' n=',n,' ',Components[n].ClassName);
n := ControlCount;
while n > 0 do
begin
Control := Controls[n - 1];
//DebugLn('[TWinControl.Destroy] C ',Name,':',ClassName,' ',Control.Name,':',Control.ClassName);
Remove(Control);
// don't free the control just set parent to nil
// controls are freed by the owner
//Control.Free;
Control.Parent := nil;
n := ControlCount;
end;
FreeThenNil(FBrush);
FreeThenNil(FChildSizing);
FreeThenNil(FDockClients);
FreeThenNil(FTabList);
//DebugLn('[TWinControl.Destroy] D ',Name,':',ClassName);
inherited Destroy;
//DebugLn('[TWinControl.Destroy] END ',Name,':',ClassName);
end;
{------------------------------------------------------------------------------
Method: TWinControl.DoEnter
Params: none
Returns: Nothing
Call user's callback for OnEnter.
------------------------------------------------------------------------------}
procedure TWinControl.DoEnter;
begin
if Assigned(FOnEnter) then FOnEnter(Self);
end;
{------------------------------------------------------------------------------
Method: TWinControl.DoExit
Params: none
Returns: Nothing
Call user's callback for OnExit.
------------------------------------------------------------------------------}
procedure TWinControl.DoExit;
begin
if Assigned(FOnExit) then FOnExit(Self);
end;
{------------------------------------------------------------------------------
procedure TWinControl.DoFlipChildren;
Flip children horizontally. That means mirroring the left position.
------------------------------------------------------------------------------}
procedure TWinControl.DoFlipChildren;
var
i: Integer;
CurControl: TControl;
AWidth: Integer;
begin
AWidth:=GetLogicalClientRect.Right;
for i:=0 to ControlCount-1 do begin
CurControl:=Controls[i];
CurControl.Left:=AWidth-CurControl.Left-CurControl.Width;
end;
end;
{------------------------------------------------------------------------------
Method: TWinControl.CMEnabledChanged
Params: Message
Returns: Nothing
Called when enabled is changed. Takes action to enable control
------------------------------------------------------------------------------}
procedure TWinControl.CMEnabledChanged(var Message: TLMessage);
begin
if not Enabled and (Parent <> nil)
then RemoveFocus(False);
if HandleAllocated and not (csDesigning in ComponentState) then begin
//if (not Enabled) then debugln('TWinControl.CMEnabledChanged disable ',Name,':',CLassName);
EnableWindow(Handle, Enabled);
end;
end;
{------------------------------------------------------------------------------
Method: TWinControl.CMShowHintChanged
Params: Message
Returns: Nothing
Called when showhint is changed. Notifies children
------------------------------------------------------------------------------}
procedure TWinControl.CMShowHintChanged(var Message: TLMessage);
begin
NotifyControls(CM_PARENTSHOWHINTCHANGED);
end;
procedure TWinControl.CMBiDiModeChanged(var Message: TLMessage);
begin
inherited;
NotifyControls(CM_PARENTBIDIMODECHANGED);
if HandleAllocated and (Message.wParam = 0) then
TWSWinControlClass(WidgetSetClass).SetBiDiMode(Self, BiDiMode);
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMSetFocus
Params: Message
Returns: Nothing
SetFocus event handler
------------------------------------------------------------------------------}
Procedure TWinControl.WMSetFocus(var Message: TLMSetFocus);
Begin
//DebugLn('TWinControl.WMSetFocus A ',Name,':',ClassName);
Assert(False, Format('Trace: %s', [ClassName]));
if [csLoading,csDestroying,csDesigning]*ComponentState=[] then begin
DoEnter;
end;
end;
{------------------------------------------------------------------------------
Method: TWinControl.LMKillFocus
Params: Msg: The message
Returns: nothing
event handler.
------------------------------------------------------------------------------}
procedure TWinControl.WMKillFocus(var Message: TLMKillFocus);
begin
//DebugLn('TWinControl.WMKillFocus A ',Name,':',ClassName);
Assert(False, Format('Trace: %s', [ClassName]));
if [csLoading,csDestroying,csDesigning]*ComponentState=[] then begin
EditingDone;
DoExit;
end;
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMPaint
Params: Msg: The paint message
Returns: nothing
Paint event handler.
------------------------------------------------------------------------------}
procedure TWinControl.WMPaint(var Msg: TLMPaint);
var
DC,MemDC: HDC;
{$ifdef BUFFERED_WMPAINT}
MemBitmap, OldBitmap : HBITMAP;
MemWidth: Integer;
MemHeight: Integer;
{$ENDIF}
PS : TPaintStruct;
ClientBoundRect: TRect;
begin
//DebugLn('[TWinControl.WMPaint] ',Name,':',ClassName,' ',DbgS(Msg.DC,8));
{$IFDEF VerboseResizeFlicker}
DebugLn('[TWinControl.WMPaint] ',DbgSName(Self),' Bounds=',dbgs(BoundsRect),' ClientRect=',dbgs(ClientRect));
{$ENDIF}
if ([csDestroying,csLoading]*ComponentState<>[]) or (not HandleAllocated) then
exit;
{$IFDEF VerboseDsgnPaintMsg}
if csDesigning in ComponentState then
DebugLn('TWinControl.WMPaint A ',Name,':',ClassName);
{$ENDIF}
Assert(False, Format('Trace:> [TWinControl.WMPaint] %s Msg.DC: 0x%x', [ClassName, Msg.DC]));
if (Msg.DC <> 0) then
begin
if not (csCustomPaint in ControlState) and (ControlCount = 0) then
begin
DefaultHandler(Msg);
end
else
PaintHandler(Msg);
end
else begin
// NOTE: not every interface uses this part
//DebugLn('TWinControl.WMPaint Painting doublebuffered ',Name,':',classname);
{$ifdef BUFFERED_WMPAINT}
DC := GetDC(0);
MemWidth:=Width;
MemHeight:=Height;
MemBitmap := CreateCompatibleBitmap(DC, MemWidth, MemHeight);
ReleaseDC(0, DC);
MemDC := CreateCompatibleDC(0);
OldBitmap := SelectObject(MemDC, MemBitmap);
{$ENDIF}
try
// Fetch a DC of the whole Handle (including client area)
DC := BeginPaint(Handle, PS);
if DC=0 then exit;
{$ifNdef BUFFERED_WMPAINT}
MemDC := DC;
{$ENDIF}
// erase background
Include(FWinControlFlags,wcfEraseBackground);
Perform(LM_ERASEBKGND, WParam(MemDC), 0);
Exclude(FWinControlFlags,wcfEraseBackground);
// create a paint message to paint the child controls.
// WMPaint expects the DC origin to be equal to the client origin of its
// parent
// -> Move the DC Origin to the client origin
if not GetClientBounds(Handle,ClientBoundRect) then exit;
MoveWindowOrgEx(MemDC,ClientBoundRect.Left,ClientBoundRect.Top);
// handle the paint message
Msg.DC := MemDC;
Perform(LM_PAINT, WParam(MemDC), 0);
Msg.DC := 0;
// restore the DC origin
MoveWindowOrgEx(MemDC,-ClientBoundRect.Left,-ClientBoundRect.Top);
{$ifdef BUFFERED_WMPAINT}
BitBlt(DC, 0,0, MemWidth, MemHeight, MemDC, 0, 0, SRCCOPY);
{$ENDIF}
EndPaint(Handle, PS);
finally
Exclude(FWinControlFlags,wcfEraseBackground);
{$ifdef BUFFERED_WMPAINT}
SelectObject(MemDC, OldBitmap);
DeleteDC(MemDC);
DeleteObject(MemBitmap);
{$ENDIF}
end;
end;
Assert(False, Format('Trace:< [TWinControl.WMPaint] %s', [ClassName]));
//DebugLn('[TWinControl.WMPaint] END ',Name,':',ClassName);
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMDestroy
Params: Msg: The destroy message
Returns: nothing
event handler.
------------------------------------------------------------------------------}
procedure TWinControl.WMDestroy(var Message: TLMDestroy);
begin
Assert(False, Format('Trace: [TWinControl.LMDestroy] %s', [ClassName]));
//DebugLn('TWinControl.WMDestroy ',Name,':',ClassName);
// Our widget/window doesn't exist anymore
Handle := 0;
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMMove
Params: Msg: The message
Returns: nothing
event handler.
------------------------------------------------------------------------------}
procedure TWinControl.WMMove(var Message: TLMMove);
var
NewWidth, NewHeight: integer;
begin
{$IFDEF VerboseSizeMsg}
DebugLn(['TWinControl.WMMove A ',DbgSName(Self),' Message=',Message.XPos,',',Message.YPos,
' BoundsRealized=',FBoundsRealized.Left,',',FBoundsRealized.Top,
' FromIntf=',Message.MoveType=Move_SourceIsInterface,
',',FBoundsRealized.Right-FBoundsRealized.Left,
'x',FBoundsRealized.Bottom-FBoundsRealized.Top]);
{$ENDIF}
NewWidth:=Width;
NewHeight:=Height;
if Message.MoveType=Move_SourceIsInterface then begin
// interface widget has moved
// -> update size and realized bounds
NewWidth:=FBoundsRealized.Right-FBoundsRealized.Left;
NewHeight:=FBoundsRealized.Bottom-FBoundsRealized.Top;
if HandleAllocated then
GetWindowSize(Handle,NewWidth,NewHeight);
FBoundsRealized:=Bounds(Message.XPos,Message.YPos,NewWidth,NewHeight);
end;
SetBoundsKeepBase(Message.XPos,Message.YPos,NewWidth,NewHeight,Parent<>nil);
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMSize
Params: Message: TLMSize
Returns: nothing
Event handler for size messages. This is called, whenever width, height,
clientwidth or clientheight have changed.
If the source of the message is the interface, the new size is stored
in FBoundsRealized to avoid sending a size message back to the interface.
------------------------------------------------------------------------------}
procedure TWinControl.WMSize(var Message: TLMSize);
var
NewLeft, NewTop: integer;
NewBoundsRealized: TRect;
begin
{$IFDEF VerboseSizeMsg}
{$IFDEF CHECK_POSITION}
if CheckPosition(Self) then
{$ENDIF}
DebugLn(['TWinControl.WMSize A ',Name,':',ClassName,' Message=',Message.Width,',',Message.Height,
' BoundsRealized=',dbgs(FBoundsRealized),' FromIntf=',(Message.SizeType and Size_SourceIsInterface)>0]);
{$ENDIF}
NewLeft:=Left;
NewTop:=Top;
if (Message.SizeType and Size_SourceIsInterface)>0 then
begin
// interface widget has resized
// -> update position and realized bounds
NewLeft := FBoundsRealized.Left;
NewTop := FBoundsRealized.Top;
if HandleAllocated then
GetWindowRelativePosition(Handle,NewLeft,NewTop);
//DebugLn('TWinControl.WMSize B ',Name,':',ClassName,' ',NewLeft,',',NewTop);
NewBoundsRealized:=Bounds(NewLeft,NewTop,Message.Width,Message.Height);
if CompareRect(@NewBoundsRealized,@FBoundsRealized) and
(not (wcfClientRectNeedsUpdate in FWinControlFlags)) then exit;
FBoundsRealized:=NewBoundsRealized;
InvalidatePreferredSize;
end;
SetBoundsKeepBase(NewLeft,NewTop,Message.Width,Message.Height,Parent<>nil);
if ClientRectNeedsInterfaceUpdate then
DoAdjustClientRectChange;
end;
{------------------------------------------------------------------------------
Method: TWinControl.CNKeyDown
Params: Msg: The message
Returns: nothing
event handler.
------------------------------------------------------------------------------}
procedure TWinControl.CNKeyDown(var Message: TLMKeyDown);
begin
//DebugLn('TWinControl.CNKeyDown ',Name,':',ClassName);
if DoKeyDownBeforeInterface(Message) then
Message.Result := 1
else
{inherited}; // there is nothing to inherit
end;
{------------------------------------------------------------------------------
procedure TWinControl.CNSysKeyDown(var Message: TLMKeyDown);
------------------------------------------------------------------------------}
procedure TWinControl.CNSysKeyDown(var Message: TLMKeyDown);
begin
if DoKeyDownBeforeInterface(Message) then
Message.Result := 1
else
{inherited}; // there is nothing to inherit
end;
{------------------------------------------------------------------------------
procedure TWinControl.CNSysKeyUp(var Message: TLMKeyUp);
------------------------------------------------------------------------------}
procedure TWinControl.CNSysKeyUp(var Message: TLMKeyUp);
begin
if DoKeyUpBeforeInterface(Message) then
Message.Result := 1
else
{inherited}; // there is nothing to inherit
end;
{------------------------------------------------------------------------------
Method: TWinControl.CNKeyUp
Params: Msg: The message
Returns: nothing
event handler.
------------------------------------------------------------------------------}
procedure TWinControl.CNKeyUp(var Message: TLMKeyUp);
begin
if DoKeyUpBeforeInterface(Message) then
Message.Result := 1
else
{inherited}; // there is nothing to inherit
end;
{------------------------------------------------------------------------------
Method: TWinControl.CNChar
Params: Msg: The message
Returns: nothing
event handler.
CNChar is sent by the interface before it has handled the keypress itself.
------------------------------------------------------------------------------}
procedure TWinControl.CNChar(var Message: TLMKeyUp);
var
c: TUTF8Char;
begin
//debugln('TWinControl.CNChar B ',DbgSName(Self),' ',dbgs(Message.CharCode),' ',dbgs(IntfSendsUTF8KeyPress));
if not IntfSendsUTF8KeyPress then
begin
// current interface does not (yet) send UTF8 key press notifications
// -> emulate
if (Message.CharCode < %11000000) then
begin
c:=chr(Message.CharCode);
IntfUTF8KeyPress(c,1,false);
if (length(c)<>1) or (c[1]<>chr(Message.CharCode)) then
begin
// character changed
if length(c)=1 then
Message.CharCode:=ord(c[1])
else
Message.CharCode:=0;
end;
end;
if Message.CharCode=0 then
begin
Message.Result := 1;
exit;
end;
end;
{$ifdef VerboseKeyboard}
debugln('TWinControl.CNChar A ',DbgSName(Self),' ',dbgs(Message.CharCode),' ',dbgs(IntfSendsUTF8KeyPress));
{$endif}
if DoKeyPress(Message) then
Message.Result := 1
else
{inherited}; // there is nothing to inherit
end;
procedure TWinControl.WMSysChar(var Message: TLMKeyUp);
begin
if SendDialogChar(Message) then
Message.Result := 1
else
{inherited}; // there is nothing to inherit
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMNofity
Params: Msg: The message
Returns: nothing
event handler.
------------------------------------------------------------------------------}
procedure TWInControl.WMNotify(var Message: TLMNotify);
Begin
if not DoControlMsg(Message.NMHdr^.hwndfrom,Message) then exit;
//Inherited;
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMShowWindow
Params: Msg: The message
Returns: nothing
event handler.
------------------------------------------------------------------------------}
procedure TWinControl.WMShowWindow(var Message: TLMShowWindow);
begin
Assert(False, Format('Trace: TODO: [TWinControl.LMShowWindow] %s', [ClassName]));
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMEnter
Params: Msg: The message
Returns: nothing
event handler.
------------------------------------------------------------------------------}
procedure TWinControl.WMEnter(var Message: TLMEnter);
begin
Assert(False, Format('Trace: TODO: [TWinControl.LMEnter] %s', [ClassName]));
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMEraseBkgnd
Params: Msg: The message
Returns: nothing
event handler.
------------------------------------------------------------------------------}
procedure TWinControl.WMEraseBkgnd(var Message: TLMEraseBkgnd);
begin
if (Message.DC<>0) and (wcfEraseBackground in FWinControlFlags) then
EraseBackground(Message.DC);
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMExit
Params: Msg: The message
Returns: nothing
event handler.
------------------------------------------------------------------------------}
procedure TWinControl.WMExit(var Message: TLMExit);
begin
Assert(False, Format('Trace: TODO: [TWinControl.LMExit] %s', [ClassName]));
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMMouseWheel
Params: Msg: The message
Returns: nothing
event handler.
------------------------------------------------------------------------------}
procedure TWinControl.WMMouseWheel(var Message: TLMMouseEvent);
Var
MousePos : TPoint;
begin
Assert(False, Format('Trace: [TWinControl.LMMouseWheel] %s', [ClassName]));
MousePos.X := Message.X;
MousePos.Y := Message.Y;
if DoMouseWheel(Message.State, Message.WheelDelta, MousePos) then
Message.Result := 1
else
inherited;
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMChar
Params: Msg: The message
Returns: nothing
event handler.
WMChar is sent by the interface after it has handled the keypress by itself.
------------------------------------------------------------------------------}
procedure TWinControl.WMChar(var Message: TLMChar);
begin
//debugln('TWinControl.WMChar ',DbgSName(Self),' ',dbgs(Message.CharCode));
if SendDialogChar(Message) then
Message.Result := 1;
Assert(False, Format('Trace:[TWinControl.WMChar] %s', [ClassName]));
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMKeyDown
Params: Msg: The message
Returns: nothing
Event handler for keys not handled by the interface
------------------------------------------------------------------------------}
Procedure TWinControl.WMKeyDown(Var Message: TLMKeyDown);
begin
if DoRemainingKeyDown(Message) then
Message.Result := 1;
end;
procedure TWinControl.WMSysKeyDown(var Message: TLMKeyDown);
begin
if DoRemainingKeyDown(Message) then
Message.Result := 1;
end;
{------------------------------------------------------------------------------
procedure TWinControl.WMSysKeyUp(var Message: TLMKeyUp);
------------------------------------------------------------------------------}
procedure TWinControl.WMSysKeyUp(var Message: TLMKeyUp);
begin
//debugln('TWinControl.WMSysKeyUp ',DbgSName(Self));
if DoRemainingKeyUp(Message) then
Message.Result := 1;
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMKeyUp
Params: Msg: The message
Returns: nothing
event handler.
------------------------------------------------------------------------------}
Procedure TWinControl.WMKeyUp(Var Message: TLMKeyUp);
begin
//debugln('TWinControl.WMKeyUp ',DbgSName(Self));
if DoRemainingKeyUp(Message) then
Message.Result := 1;
end;
{------------------------------------------------------------------------------
Function: TWinControl.HandleAllocated
Params: None
Returns: True is handle is allocated
Checks if a handle is allocated. I.E. if the control is mapped
------------------------------------------------------------------------------}
function TWinControl.HandleAllocated : Boolean;
begin
HandleAllocated := (FHandle <> 0);
end;
{------------------------------------------------------------------------------
Method: TWinControl.CreateHandle
Params: None
Returns: Nothing
Creates the handle ( = object) if not already done.
------------------------------------------------------------------------------}
procedure TWinControl.CreateHandle;
begin
if (not HandleAllocated) then CreateWnd;
end;
{------------------------------------------------------------------------------
Method: TWinControl.CreateWnd
Params: None
Returns: Nothing
Creates the interface object and assigns the handle
------------------------------------------------------------------------------}
procedure TWinControl.CreateWnd;
var
Params: TCreateParams;
i: Integer;
{ procedure WriteClientRect(const Prefix: string);
var r: TRect;
begin
LCLIntf.GetClientRect(Handle,r);
if csDesigning in ComponentState then
DebugLn('WriteClientRect ',Prefix,' ',Name,':',ClassName,' r=',r.Right,',',r.Bottom);
end;}
begin
//DebugLn('[TWinControl.CreateWnd] START ',DbgSName(Self));
if (csDestroying in ComponentState)
or (Parent<>nil) and (csDestroying in Parent.ComponentState) then begin
DebugLn('[TWinControl.CreateWnd] NOTE: csDestroying ',DbgSName(Self));
exit;
end;
if wcfInitializing in FWinControlFlags
then begin
DebugLn('[WARNING] Recursive call to CreateWnd for ',DbgSName(Self), ' while initializing');
Exit;
end;
if wcfCreatingHandle in FWinControlFlags
then begin
DebugLn('[WARNING] Recursive call to CreateWnd for ',DbgSName(Self), ' while creating handle');
Exit;
end;
if wcfCreatingChildHandles in FWinControlFlags
then begin
DebugLn('[WARNING] Recursive call to CreateWnd for ',DbgSName(Self), ' while creating children');
Exit;
end;
if [csLoading,csDesigning]*ComponentState=[csLoading] then begin
DebugLn('[HINT] TWinControl.CreateWnd creating Handle during loading ',DbgSName(Self),' csDesigning=',dbgs(csDesigning in ComponentState));
//DumpStack;
//RaiseGDBException('');
end;
DisableAlign;
DisableAutoSizing;
try
// Control is not visible at this moment. It will be showed in UpdateShowing
FShowing := False;
Exclude(FWinControlFlags, wcfHandleVisible);
Include(FWinControlFlags,wcfCreatingHandle);
try
CreateParams(Params);
with Params do begin
if (WndParent = 0) and (Style and WS_CHILD <> 0) then
RaiseGDBException('TWinControl.CreateWnd: no parent '+Name+':'+ClassName);
Assert((parent <> nil) or (WndParent = 0), 'TODO: find parent if parent=nil and WndParent <> 0');
end;
//DebugLn(['TWinControl.CreateWnd ',DbgSName(WidgetSetClass),' ',DbgSName(Self)]);
FHandle := TWSWinControlClass(WidgetSetClass).CreateHandle(Self, Params);
if not HandleAllocated then
RaiseGDBException('Handle creation failed creating '+DbgSName(Self));
//debugln('TWinControl.CreateWnd ',DbgSName(Self));
Constraints.UpdateInterfaceConstraints;
InvalidatePreferredSize;
TWSWinControlClass(WidgetSetClass).ConstraintsChange(Self);
//WriteClientRect('A');
if Parent <> nil then AddControl;
//WriteClientRect('B');
Include(FWinControlFlags, wcfInitializing);
InitializeWnd;
finally
Exclude(FWinControlFlags, wcfInitializing);
Exclude(FWinControlFlags, wcfCreatingHandle);
end;
Include(FWinControlFlags, wcfCreatingChildHandles);
try
//DebugLn('[TWinControl.CreateWnd] ',Name,':',ClassName,' ',Left,',',Top,',',Width,',',Height);
//WriteClientRect('C');
if FWinControls <> nil then begin
for i := 0 to FWinControls.Count - 1 do
with TWinControl(FWinControls.Items[i]) do
if IsControlVisible then HandleNeeded;
end;
ChildHandlesCreated;
finally
Exclude(FWinControlFlags,wcfCreatingChildHandles);
end;
// size this control
UpdateShowing;
AdjustSize;
if FControls<>nil then
for i:=0 to FControls.Count-1 do
TControl(FControls[i]).AdjustSize;
// realign childs
ReAlign;
finally
EnableAutoSizing;
EnableAlign;
end;
//DebugLn('[TWinControl.CreateWnd] END ',Name,':',Classname);
//WriteClientRect('D');
end;
{------------------------------------------------------------------------------
Method: TWinControl.InitializeWnd
Params: none
Returns: Nothing
Gets called after the window is created, but before the child controls are
created. Place cached property code here.
------------------------------------------------------------------------------}
procedure TWinControl.InitializeWnd;
var
CachedText: string;
begin
Assert(False, Format('Trace:[TWinControl.InitializeWnd] %s', [ClassName]));
// set all cached properties
//DebugLn('[TWinControl.InitializeWnd] ',Name,':',ClassName,':', FCaption,' ',dbgs(Left),',',dbgs(Top),',',dbgs(Width),',',dbgs(Height));
//First set the WinControl property.
//The win32 interface depends on it to determine where to send call backs.
SetProp(Handle,'WinControl',TWinControl(Self));
DisableAlign;
DisableAutoSizing;
try
{$IFDEF CHECK_POSITION}
if CheckPosition(Self) then
DebugLn('[TWinControl.InitializeWnd] A ',DbgSName(Self),
' OldRelBounds=',dbgs(FBoundsRealized),
' -> NewBounds=',dbgs(BoundsRect));
{$ENDIF}
if (Width>0) and (Height>0) then
DoSendBoundsToInterface;
if wcfColorChanged in FWinControlFlags then begin
// replace by update style call
TWSWinControlClass(WidgetSetClass).SetColor(Self);
FWinControlFlags:=FWinControlFlags-[wcfColorChanged];
end;
if wcfFontChanged in FWinControlFlags then begin
// replace by update style call
TWSWinControlClass(WidgetSetClass).SetFont(Self,Font);
FWinControlFlags:=FWinControlFlags-[wcfFontChanged];
end;
if not (csDesigning in ComponentState) then
EnableWindow(Handle, Enabled);
// Delay the setting of text until it is completely loaded
if not (csLoading in ComponentState) then
begin
if GetCachedText(CachedText) then
WSSetText(CachedText);
InvalidatePreferredSize;
end;
if csDesigning in ComponentState
then TWSWinControlClass(WidgetSetClass).SetCursor(Self, Screen.Cursors[crDefault])
else TWSWinControlClass(WidgetSetClass).SetCursor(Self, Screen.Cursors[Cursor]);
finally
EnableAutoSizing;
EnableAlign;
end;
// send pending OnResize
Resize;
end;
procedure TWinControl.FinalizeWnd;
var
S: string;
begin
if not HandleAllocated then
RaiseGDBException('TWinControl.FinalizeWnd Handle already destroyed');
// make sure our text is saved
if TWSWinControlClass(WidgetSetClass).GetText(Self, S)
then FCaption := S;
RemoveProp(Handle,'WinControl');
end;
{------------------------------------------------------------------------------
procedure TWinControl.ParentFormHandleInitialized;
Called after all childs handles of the ParentForm are created.
------------------------------------------------------------------------------}
procedure TWinControl.ParentFormHandleInitialized;
var
i: Integer;
begin
inherited ParentFormHandleInitialized;
// tell all wincontrols about the final end of the handle creation phase
if FWinControls <> nil then begin
for i := 0 to FWinControls.Count - 1 do
TWinControl(FWinControls.Items[i]).ParentFormHandleInitialized;
end;
// tell all controls about the final end of the handle creation phase
if FControls<>nil then begin
for i:=0 to FControls.Count-1 do
TControl(FControls[i]).ParentFormHandleInitialized;
end;
//debugln('TWinControl.ParentFormHandleInitialized A ',DbgSName(Self));
if cfAutoSizeNeeded in FControlFlags then AdjustSize;
end;
{------------------------------------------------------------------------------
procedure TWinControl.ChildHandlesCreated;
Called after all childs handles are created.
------------------------------------------------------------------------------}
procedure TWinControl.ChildHandlesCreated;
begin
Exclude(FWinControlFlags,wcfCreatingChildHandles);
end;
{------------------------------------------------------------------------------
function TWinControl.ParentHandlesAllocated: boolean;
Checks if all Handles of all Parents are created.
------------------------------------------------------------------------------}
function TWinControl.ParentHandlesAllocated: boolean;
var
CurControl: TWinControl;
begin
Result:=false;
CurControl:=Self;
while CurControl<>nil do begin
if (not CurControl.HandleAllocated)
or (csDestroying in CurControl.ComponentState)
or (csDestroyingHandle in CurControl.ControlState) then
exit;
CurControl:=CurControl.Parent;
end;
Result:=true;
end;
{------------------------------------------------------------------------------
procedure TWinControl.Loaded;
------------------------------------------------------------------------------}
procedure TWinControl.Loaded;
var
CachedText: string;
i: Integer;
AChild: TControl;
LoadedClientSize: TPoint;
CurControl: TWinControl;
begin
DisableAlign;
DisableAutoSizing;
try
//DebugLn(['TWinControl.Loaded ',DbgSName(Self),' cfWidthLoaded=',cfWidthLoaded in FControlFlags,' cfHeightLoaded=',cfHeightLoaded in FControlFlags,' ']);
if cfClientWidthLoaded in FControlFlags then
LoadedClientSize.X:=FLoadedClientSize.X
else begin
CurControl:=Self;
while CurControl<>nil do begin
LoadedClientSize.X:=CurControl.ClientWidth;
if LoadedClientSize.X>0 then break;
LoadedClientSize.X:=CurControl.Width;
if LoadedClientSize.X>0 then break;
CurControl:=CurControl.Parent;
end;
end;
if cfClientHeightLoaded in FControlFlags then
LoadedClientSize.Y:=FLoadedClientSize.Y
else begin
CurControl:=Self;
while CurControl<>nil do begin
LoadedClientSize.Y:=CurControl.ClientHeight;
if LoadedClientSize.Y>0 then break;
LoadedClientSize.Y:=CurControl.Height;
if LoadedClientSize.Y>0 then break;
CurControl:=CurControl.Parent;
end;
end;
for i:=0 to ControlCount-1 do begin
AChild:=Controls[i];
if AChild=nil then ;
AChild.FBaseParentClientSize:=LoadedClientSize;
//DebugLn(['TWinControl.Loaded Self=',DbgSName(Self),' AChild=',AChild,' AChild.FBaseParentClientSize=',dbgs(AChild.FBaseParentClientSize)]);
end;
if HandleAllocated then begin
// Set cached caption
if GetCachedText(CachedText) then
WSSetText(CachedText);
InvalidatePreferredSize;
if wcfColorChanged in FWinControlFlags then begin
TWSWinControlClass(WidgetSetClass).SetColor(Self);
Exclude(FWinControlFlags,wcfColorChanged);
end;
if wcfFontChanged in FWinControlFlags then begin
TWSWinControlClass(WidgetSetClass).SetFont(Self,Font);
NotifyControls(CM_PARENTCOLORCHANGED);
for i := 0 to ControlCount - 1 do
Controls[i].ParentFontChanged;
FWinControlFlags:=FWinControlFlags-[wcfFontChanged];
end;
end;
inherited Loaded;
FixupTabList;
RealizeBounds;
if HandleAllocated and ([csDestroying]*ComponentState=[]) then
DoSendShowHideToInterface;
finally
EnableAutoSizing;
EnableAlign;
end;
end;
procedure TWinControl.FormEndUpdated;
var
i: Integer;
begin
inherited FormEndUpdated;
for i:=0 to ControlCount-1 do
Controls[i].FormEndUpdated;
end;
{------------------------------------------------------------------------------
Method: TWinControl.DestroyWnd
Params: None
Returns: Nothing
Destroys the interface object.
------------------------------------------------------------------------------}
procedure TWinControl.DestroyWnd;
begin
if HandleAllocated then
begin
FinalizeWnd;
TWSWinControlClass(WidgetSetClass).DestroyHandle(Self);
Handle := 0;
end;
end;
{------------------------------------------------------------------------------
Method: TWinControl.HandleNeeded
Params: None
Returns: Nothing
Description of the procedure for the class.
------------------------------------------------------------------------------}
procedure TWinControl.HandleNeeded;
begin
if (not HandleAllocated) and (not (csDestroying in ComponentState)) then
begin
if Parent = Self
then begin
Assert(False, Format('Trace:[TWinControl.HandleNeeded] Somebody set Parent := Self in %s. DONT DO THAT !!', [Classname]));
end
else begin
if (Parent <> nil) then
begin
Parent.HandleNeeded;
// has parent triggered us to create our handle ?
if HandleAllocated then
exit;
end;
end;
Assert(not ParentDestroyingHandle, Format('WARNING:[TWinControl.HandleNeeded] creating handle for %s while destroying handles!', [ClassName]));
CreateHandle;
end;
end;
function TWinControl.BrushCreated: Boolean;
begin
Result:=FBrush<>nil;
end;
{------------------------------------------------------------------------------
Method: TWinControl.BeginUpdateBounds
Params: None
Returns: Nothing
increases the BoundsLockCount
------------------------------------------------------------------------------}
procedure TWinControl.BeginUpdateBounds;
begin
inc(FBoundsLockCount);
end;
{------------------------------------------------------------------------------
Method: TControl.EndUpdateBounds
Params: None
Returns: Nothing
decreases the BoundsLockCount
------------------------------------------------------------------------------}
procedure TWinControl.EndUpdateBounds;
procedure RaiseTooManyEndUpdates;
begin
raise Exception.Create('TWinControl.EndUpdateBounds '+DbgSName(Self)
+' too many calls.');
end;
begin
if FBoundsLockCount<=0 then RaiseTooManyEndUpdates;
dec(FBoundsLockCount);
if FBoundsLockCount=0 then begin
SetBounds(Left,Top,Width,Height);
end;
end;
procedure TWinControl.LockRealizeBounds;
begin
inc(FRealizeBoundsLockCount);
end;
procedure TWinControl.UnlockRealizeBounds;
begin
if FRealizeBoundsLockCount<=0 then
RaiseGDBException('UnlockRealizeBounds');
dec(FRealizeBoundsLockCount);
if FRealizeBoundsLockCount=0 then
RealizeBounds;
end;
{------------------------------------------------------------------------------
procedure TWinControl.DockDrop(DockObject: TDragDockObject; X, Y: Integer);
Docks the DockObject.Control onto this control.
X, Y are only default values. More important is the DockObject.DropAlign
property, which defines how to align DockObject.Control.
------------------------------------------------------------------------------}
procedure TWinControl.DockDrop(DockObject: TDragDockObject; X, Y: Integer);
var
DestRect: TRect;
//ParentForm: TCustomForm;
MappedLeftTop: TPoint;
NewBounds: TRect;
begin
// get dock destination rectangle and map it to the client area
DestRect := DockObject.DockRect;
MappedLeftTop:=ScreenToClient(DestRect.TopLeft);
OffsetRect(DestRect,
DestRect.Left-MappedLeftTop.X,DestRect.Top-MappedLeftTop.Y);
DebugLn('TWinControl.DockDrop A ',Name,' DockControl=',DbgSName(DockObject.Control),' DestRect=',dbgs(DestRect));
DisableAlign;
try
if (not UseDockManager) or (DockManager=nil) then begin
// Delphi ignores the DropAlign when no DockManager is available
// Why that?
DockObject.Control.Align:=DockObject.DropAlign;
if DockObject.IncreaseDockArea then begin
NewBounds := BoundsRect;
case DockObject.DropAlign of
alLeft:
dec(NewBounds.Left,DockObject.Control.Width);
alTop:
dec(NewBounds.Top,DockObject.Control.Height);
alRight:
inc(NewBounds.Right,DockObject.Control.Width);
alBottom:
inc(NewBounds.Bottom,DockObject.Control.Height);
end;
if NewBounds.Left<0 then
NewBounds.Left:=0;
if NewBounds.Top<0 then
NewBounds.Top:=0;
if NewBounds.Right>Screen.Width then
NewBounds.Right:=Screen.Width;
if NewBounds.Bottom>Screen.Height then
NewBounds.Bottom:=Screen.Height;
debugln('TWinControl.DockDrop IncreaseDockArea ',DbgSName(Self),' ',dbgs(NewBounds));
SetBoundsKeepBase(NewBounds.Left,NewBounds.Top,
NewBounds.Right-NewBounds.Left,
NewBounds.Bottom-NewBounds.Top);
end;
end;
DockObject.Control.Dock(Self, DestRect);
if UseDockManager and (DockManager <> nil) then
DockManager.InsertControl(DockObject.Control,
DockObject.DropAlign, DockObject.DropOnControl);
finally
EnableAlign;
end;
//ParentForm := GetParentForm(Self);
//if ParentForm<>nil then ParentForm.BringToFront;
if Assigned(FOnDockDrop) then
FOnDockDrop(Self, DockObject, X, Y);
end;
{------------------------------------------------------------------------------
Method: TControl.GetIsResizing
Params: None
Returns: Nothing
decreases the BoundsLockCount
------------------------------------------------------------------------------}
function TWinControl.GetIsResizing: boolean;
begin
Result:=BoundsLockCount>0;
end;
{------------------------------------------------------------------------------
function TWinControl.GetTabOrder: TTabOrder;
------------------------------------------------------------------------------}
function TWinControl.GetTabOrder: TTabOrder;
begin
if FParent <> nil then
Result := ListIndexOf(FParent.FTabList,Self)
else
Result := FTabOrder;
end;
{------------------------------------------------------------------------------
function TWinControl.GetVisibleDockClientCount: Integer;
------------------------------------------------------------------------------}
function TWinControl.GetVisibleDockClientCount: Integer;
var
i: integer;
begin
Result := 0;
if FDockClients=nil then exit;
for i:=FDockClients.Count-1 downto 0 do
if TControl(FDockClients[I]).Visible then inc(Result);
end;
{------------------------------------------------------------------------------
procedure TWinControl.SetChildSizing(const AValue: TControlChildSizing);
------------------------------------------------------------------------------}
procedure TWinControl.SetChildSizing(const AValue: TControlChildSizing);
begin
if (FChildSizing=AValue) then exit;
FChildSizing.Assign(AValue);
end;
{------------------------------------------------------------------------------
procedure TWinControl.SetDockSite(const NewDockSite: Boolean);
------------------------------------------------------------------------------}
procedure TWinControl.SetDockSite(const NewDockSite: Boolean);
begin
if FDockSite=NewDockSite then exit;
FDockSite := NewDockSite;
if not (csDesigning in ComponentState) then begin
RegisterDockSite(Self,NewDockSite);
if not NewDockSite then begin
FreeAndNil(FDockClients);
FDockClients := nil;
FDockManager := nil;
end
else begin
if FDockClients = nil then FDockClients := TFPList.Create;
FDockManager := CreateDockManager;
end;
end;
end;
{------------------------------------------------------------------------------
Method: TWinControl.SetBounds
Params: aLeft, aTop, aWidth, aHeight
Returns: Nothing
Sets the bounds of the control.
------------------------------------------------------------------------------}
procedure TWinControl.SetBounds(aLeft, aTop, aWidth, aHeight : integer);
procedure CheckDesignBounds;
begin
if FRealizeBoundsLockCount>0 then exit;
// the user changed the bounds
if aWidth<0 then
raise Exception.Create(
'TWinControl.SetBounds ('+DbgSName(Self)+'): Negative width '
+dbgs(aWidth)+' not allowed.');
if aHeight<0 then
raise Exception.Create(
'TWinControl.SetBounds ('+DbgSName(Self)+'): Negative height '
+dbgs(aHeight)+' not allowed.');
end;
var
NewBounds, OldBounds: TRect;
begin
{$IFDEF CHECK_POSITION}
//if csDesigning in ComponentState then
if CheckPosition(Self) then
DebugLn(['[TWinControl.SetBounds] START ',DbgSName(Self),
' Old=',dbgs(Bounds(Left,Top,Width,Height)),
' -> New=',dbgs(Bounds(ALeft,ATop,AWidth,AHeight)),
' Lock=',BoundsLockCount,
' Realized=',dbgs(FBoundsRealized)
]);
if CheckPosition(Self) and (AWidth=1) then
DumpStack;
{$ENDIF}
if BoundsLockCount<>0 then begin
//DebugLn(['TWinControl.SetBounds ',DbgSName(Self),' ignoring loop Cur=',dbgs(BoundsRect),' ',dbgs(Bounds(ALeft,ATop,AWidth,AHeight))]);
exit;
end;
OldBounds:=BoundsRect;
NewBounds:=Bounds(ALeft, ATop, AWidth, AHeight);
if not CompareRect(@NewBounds,@OldBounds) then begin
if [csDesigning,csDestroying,csLoading]*ComponentState=[csDesigning] then
CheckDesignBounds;
// LCL bounds are not up2date -> process new bounds
LockRealizeBounds;
try
{$IFDEF CHECK_POSITION}
//if csDesigning in ComponentState then
if CheckPosition(Self) then
DebugLn(['[TWinControl.SetBounds] Set LCL Bounds ',DbgSName(Self),
' OldBounds=',Dbgs(Bounds(Left,Top,Width,Height)),
' -> New=',Dbgs(Bounds(ALeft,ATop,AWidth,AHeight))]);
{$ENDIF}
inherited SetBounds(ALeft, ATop, AWidth, AHeight);
//NewBounds:=Bounds(Left, Top, Width, Height);
finally
UnlockRealizeBounds;
end;
end;
end;
{------------------------------------------------------------------------------
procedure TWinControl.CalculatePreferredSize(var PreferredWidth,
PreferredHeight: integer; WithThemeSpace" Boolean);
Calculates the default/preferred width and height for a TWinControl, which is
used by the LCL autosizing algorithms as default size. Only positive values
are valid. Negative or 0 are treated as undefined and the LCL uses other sizes
instead.
TWinControl overrides this:
If there are childs, their total preferred size is calculated.
If this value can not be computed (e.g. the childs depend too much on their
parent clientrect), then the interface is asked for the preferred size.
For example the preferred size of a TButton is the size, where the label fits
exactly. This depends heavily on the current theme and widgetset.
This value is independent of constraints and siblings, only the inner parts
are relevant.
WithThemeSpace: If true, adds space for stacking. For example: TRadioButton
has a minimum size. But for stacking multiple TRadioButtons there should be
some space around. This space is theme dependent, so it passed parameter to
the widgetset.
------------------------------------------------------------------------------}
procedure TWinControl.CalculatePreferredSize(var PreferredWidth,
PreferredHeight: integer; WithThemeSpace: Boolean);
var
Layout: TAutoSizeCtrlData;
NewClientWidth: Integer;
NewClientHeight: Integer;
OldClientRect: TRect;
NewWidth: Integer;
NewHeight: Integer;
begin
inherited CalculatePreferredSize(PreferredWidth,PreferredHeight,WithThemeSpace);
if HandleAllocated then begin
TWSWinControlClass(WidgetSetClass).GetPreferredSize(Self,
PreferredWidth, PreferredHeight, WithThemeSpace);
if (PreferredWidth>0) then
inc(PreferredWidth,BorderSpacing.InnerBorder*2);
if PreferredHeight>0 then
inc(PreferredHeight,BorderSpacing.InnerBorder*2);
end;
if ControlCount>0 then begin
// get the size requirements for the child controls
Layout:=nil;
try
Layout:=TAutoSizeCtrlData.Create(Self);
Layout.ComputePreferredClientArea(false,NewClientWidth,NewClientHeight);
finally
Layout.Free;
end;
// add the control border around the client area
OldClientRect := GetClientRect;
NewWidth:=Width-OldClientRect.Right+NewClientWidth;
NewHeight:=Height-OldClientRect.Bottom+NewClientHeight;
{$IFDEF VerboseAutoSize}
debugln('TWinControl.CalculatePreferredSize ',DbgSName(Self),
' HandleAllocated=',dbgs(HandleAllocated),
' Cur='+dbgs(Width)+'x'+dbgs(Height)+
' Client='+dbgs(OldClientRect.Right)+'x'+dbgs(OldClientRect.Bottom),
' NewWidth='+dbgs(NewWidth)+' NewHeight=',dbgs(NewHeight));
{$ENDIF}
PreferredWidth:=Max(PreferredWidth,NewWidth);
PreferredHeight:=Max(PreferredHeight,NewHeight);
end;
{$IFDEF VerboseAutoSize}
debugln('TWinControl.CalculatePreferredSize ',DbgSName(Self),
' HandleAllocated=',dbgs(HandleAllocated),
' Preferred=',dbgs(PreferredWidth),'x',dbgs(PreferredHeight));
{$ENDIF}
end;
{------------------------------------------------------------------------------
Method: TWinControl.RealGetText
Params: None
Returns: The text
Gets the text/caption of a control
------------------------------------------------------------------------------}
function TWinControl.RealGetText: TCaption;
begin
Result := '';
if not HandleAllocated
or (csLoading in ComponentState)
or (not TWSWinControlClass(WidgetSetClass).GetText(Self, Result))
then Result := inherited RealGetText;
end;
{------------------------------------------------------------------------------
Method: TWinControl.GetTextLen
Params: None
Returns: The length of the text
Gets the length of the text/caption of a control
------------------------------------------------------------------------------}
function TWinControl.GetTextLen: Integer;
begin
Result := 0;
if not HandleAllocated
or (csLoading in ComponentState)
or not TWSWinControlClass(WidgetSetClass).GetTextLen(Self, Result)
then Result := inherited GetTextLen;
end;
{------------------------------------------------------------------------------
Method: TWinControl.RealSetText
Params: Value: the text to be set
Returns: Nothing
Sets the text/caption of a control
------------------------------------------------------------------------------}
procedure TWinControl.RealSetText(const AValue: TCaption);
begin
if HandleAllocated and (not (csLoading in ComponentState)) then
begin
WSSetText(AValue);
InvalidatePreferredSize;
//DebugLn(['TWinControl.RealSetText ',DbgSName(Self),' ',AValue]);
AdjustSize;
end;
inherited RealSetText(AValue);
end;
{------------------------------------------------------------------------------
Method: TWinControl.GetDeviceContext
Params: WindowHandle: the windowhandle of this control
Returns: a Devicecontext
Get the devicecontext for this WinControl.
------------------------------------------------------------------------------}
function TWinControl.GetDeviceContext(var WindowHandle: HWnd): HDC;
begin
Result := GetDC(Handle);
//DebugLn('[TWinControl.GetDeviceContext] ',ClassName,' DC=',DbgS(Result,8),' Handle=',DbgS(FHandle));
if Result = 0
then raise EOutOfResources.CreateFmt(rsErrorCreatingDeviceContext, [Name,
ClassName]);
WindowHandle := Handle;
end;
{------------------------------------------------------------------------------
Method: TWinControl.CMVisibleChanged
Params: Message : not used
Returns: nothing
Performs actions when visibility has changed
------------------------------------------------------------------------------}
procedure TWinControl.CMVisibleChanged(var TheMessage : TLMessage);
begin
if not FVisible and (Parent <> nil)
then RemoveFocus(False);
if not (csDesigning in ComponentState)
or (csNoDesignVisible in ControlStyle)
then
UpdateControlState;
end;
procedure TWinControl.DoSendShowHideToInterface;
var
NewVisible: Boolean;
begin
NewVisible:=HandleObjectShouldBeVisible;
if NewVisible<>(wcfHandleVisible in FWinControlFlags) then begin
if NewVisible then
Include(FWinControlFlags,wcfHandleVisible)
else
Exclude(FWinControlFlags,wcfHandleVisible);
TWSWinControlClass(WidgetSetClass).ShowHide(Self);
end;
end;
procedure TWinControl.ControlsAligned;
begin
end;
procedure TWinControl.DoSendBoundsToInterface;
var
NewBounds: TRect;
{$IFDEF VerboseResizeFlicker}
OldBounds: TRect;
{$ENDIF}
begin
NewBounds:=Bounds(Left, Top, Width, Height);
{$IFDEF VerboseResizeFlicker}
if HandleAllocated then begin
GetWindowRelativePosition(Handle,OldBounds.Left,OldBounds.Top);
GetWindowSize(Handle,OldBounds.Right,OldBounds.Bottom);
inc(OldBounds.Right,OldBounds.Left);
inc(OldBounds.Bottom,OldBounds.Top);
end else
OldBounds:=NewBounds;
DebugLn(['[TWinControl.DoSendBoundsToInterface] ',DbgSName(Self),
' Old=',dbgs(OldBounds),
' New=',dbgs(NewBounds),
' PosChanged=',(OldBounds.Left<>NewBounds.Left) or (OldBounds.Top<>NewBounds.Top),
' SizeChanged=',(OldBounds.Right-OldBounds.Left<>NewBounds.Right-NewBounds.Left)
, (OldBounds.Bottom-OldBounds.Top<>NewBounds.Bottom-NewBounds.Top),
' CurClient=',FClientWidth,'x',FClientHeight
]);
{$ENDIF}
{$IFDEF CHECK_POSITION}
if CheckPosition(Self) then
DebugLn('[TWinControl.DoSendBoundsToInterface] A ',DbgSName(Self),
' OldRelBounds=',dbgs(FBoundsRealized),
' -> NewBounds=',dbgs(NewBounds));
{$ENDIF}
FBoundsRealized:=NewBounds;
TWSWinControlClass(WidgetSetClass).SetBounds(Self, Left, Top, Width, Height);
end;
procedure TWinControl.RealizeBounds;
var
NewBounds: TRect;
begin
NewBounds:=Bounds(Left, Top, Width, Height);
if HandleAllocated
and ([csLoading,csDestroying]*ComponentState=[])
and (not (csDestroyingHandle in ControlState))
and (not CompareRect(@NewBounds,@FBoundsRealized))
and (not IsAParentAligning) then
begin
// the new bounds were not yet sent to the InterfaceObject -> send them
{$IFDEF CHECK_POSITION}
//if csDesigning in ComponentState then
if CheckPosition(Self) then
DebugLn('[TWinControl.RealizeBounds] A ',DbgSName(Self),
' OldRelBounds=',dbgs(FBoundsRealized),
' -> NewBounds=',dbgs(NewBounds));
{$ENDIF}
BeginUpdateBounds;
try
DoSendBoundsToInterface;
finally
EndUpdateBounds;
end;
end;
end;
procedure TWinControl.RealizeBoundsRecursive;
var
i: Integer;
begin
RealizeBounds;
if FWinControls<>nil then begin
for i:=0 to FWinControls.Count-1 do
TWinControl(FWinControls[i]).RealizeBoundsRecursive;
end;
end;
{------------------------------------------------------------------------------
Method: TWinControl.CMShowingChanged
Params: Message : not used
Returns: nothing
Shows or hides a control
------------------------------------------------------------------------------}
procedure TWinControl.CMShowingChanged(var Message: TLMessage);
begin
if HandleAllocated and ([csDestroying,csLoading]*ComponentState=[]) then
DoSendShowHideToInterface;
end;
{------------------------------------------------------------------------------
Method: TWinControl.ShowControl
Params: AControl: Control to show
Returns: nothing
Askes the parent to show ourself.
------------------------------------------------------------------------------}
procedure TWinControl.ShowControl(AControl: TControl);
begin
if Parent <> nil then Parent.ShowControl(Self);
end;
{ $UNDEF CHECK_POSITION}
{$IFDEF ASSERT_IS_ON}
{$UNDEF ASSERT_IS_ON}
{$C-}
{$ENDIF}