lazarus/lcl/include/wincontrol.inc
maxim bbcff6b780 Merged revision(s) 53790 #b77917bbc2 from trunk:
LCL: TWinControl: refreshing RawImage data after painting on canvas. Issue #25448
........

git-svn-id: branches/fixes_1_6@53794 -
2016-12-28 21:36:26 +00:00

8587 lines
301 KiB
PHP

{%MainUnit ../controls.pp}
{******************************************************************************
TWinControl
******************************************************************************
*****************************************************************************
This file is part of the Lazarus Component Library (LCL)
See the file COPYING.modifiedLGPL.txt, included in this distribution,
for details about the license.
*****************************************************************************
}
{$IFOPT C-}
// Uncomment for local trace
// {$C+}
// {$DEFINE ASSERT_IS_ON}
{$ENDIF}
{off $DEFINE VerboseAutoSizeCtrlData}
{off $DEFINE VerboseMouseBugfix}
{off $DEFINE VerboseCanAutoSize}
{off $DEFINE VerboseIntfSizing}
{off $DEFINE VerboseClientRectBugFix}
{$IFDEF VerboseClientRectBugFix}
const CheckClientRectName = 'LCLInterfaceRadioGroup';
{$ENDIF}
{off $DEFINE VerboseSizeMsg}
{off $DEFINE CHECK_POSITION}
{$IFDEF CHECK_POSITION}
const CheckPostionClassName = 'xxxTBreakPropertyDlg';
const CheckPostionName = 'FakeStatusBar';
const CheckPostionParentName = 'xxxEnvVarsPage';
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}
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;
function IsNotAligned(AControl: TControl; ASide: TAnchorKind): boolean;
begin
Result:=(AControl.Align=alNone);
if not Result then Exit;
if ASide in [akLeft, akRight] then
Result:=(AControl.Anchors*[akLeft, akRight]=[akLeft])
and (AControl.AnchorSide[akLeft].Control=nil)
else
Result:=(AControl.Anchors*[akTop, akBottom]=[akTop])
and (AControl.AnchorSide[akBottom].Control=nil);
end;
{------------------------------------------------------------------------------
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 Children array.
A TAutoSizeBox can be a table. Then it has both Children 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;
Children: 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 ResizeChildren(ChildSizing: TControlChildSizing;
Orientation: TAutoSizeBoxOrientation;
TargetSize: integer);
procedure ComputeLeftTops(Orientation: TAutoSizeBoxOrientation);
// for tables
procedure AllocateTable(ColCount, RowCount: Integer);
procedure SetTableControls(ListOfControls: TFPList;
ChildSizing: TControlChildSizing;
BiDiMode: TBiDiMode);
procedure SumTable;
procedure ResizeTable(ChildSizing: TControlChildSizing;
TargetWidth, TargetHeight: integer);
procedure AlignToRight(TargetWidth: integer);
procedure ComputeTableControlBounds(ChildSizing: TControlChildSizing;
BiDiMode: TBiDiMode);
function SetTableControlBounds(ChildSizing: TControlChildSizing
): boolean;// true if changed
function AlignControlsInTable(ListOfControls: TFPList;
ChildSizing: TControlChildSizing;
BiDiMode: TBiDiMode;
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 compute the preferred
size of a control given the preferred sizes of its children.
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 GetChildren(AControl: TControl): TAutoSizeCtrlData;
procedure DoMoveNonAlignedChildren(Side: TAnchorKind;
var MoveDiff: integer; FindMinimum: boolean);
procedure SetupNonAlignedChildren(MoveNonAlignedChildrenLeft,
MoveNonAlignedChildrenTop: boolean);
procedure AlignChildren;
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;
BaseBounds: TRect;
BaseParentClientSize: TSize;
constructor Create(AControl: TControl; IsParent: boolean = true);
destructor Destroy; override;
procedure Clear;
procedure ComputePreferredClientArea(MoveNonAlignedChildrenLeft,
MoveNonAlignedChildrenTop: boolean;
out MoveNonAlignedToLeft, MoveNonAlignedToTop,
PreferredClientWidth, PreferredClientHeight: integer);
procedure FixControlProperties(Child: TControl);
procedure ClearSides;
procedure SetFixedLeftTop(ChildData: TAutoSizeCtrlData; Side: TAnchorKind;
NewLeftTop: integer);
property Children[AControl: TControl]: TAutoSizeCtrlData read GetChildren; default;
procedure WriteDebugReport(const Title, Prefix: string; OnlyVisible: boolean = true);
end;
const
SizeBoxOrthogonal: array[TAutoSizeBoxOrientation] of TAutoSizeBoxOrientation
= (asboVertical,asboHorizontal);
{AutoSizeSideDataStateNames: array[TAutoSizeSideDataState] of shortstring = (
'assdfInvalid',
'assdfComputing',
'assdfUncomputable',
'assdfValid'
);}
{$IFNDEF DisableChecks}
AutoSizeSideDistDirectionNames: array[TAutoSizeSideDistDirection] of shortstring = (
'assddLeftTop',
'assddRightBottom'
);
{$ENDIF}
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.GetChildren(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,false);
FChilds.Add(Result);
end;
end;
procedure TAutoSizeCtrlData.AlignChildren;
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 := Children[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: ; // Delphi compatibility: multiple alClient controls overlap
end;
{DebugLn(['DoAlign AlignBoundaryControls:',
' Left=',DbgSName(AlignBoundaryControls[akLeft].Control),
' Top=',DbgSName(AlignBoundaryControls[akTop].Control),
' Right=',DbgSName(AlignBoundaryControls[akRight].Control),
' Bottom=',DbgSName(AlignBoundaryControls[akBottom].Control) ]);}
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:=Children[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;
inc(ChildData.Sides[a].Space,AdjustedClientBorders[a]);
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 if a in Child.Anchors then begin
// anchored to parent via BaseBounds
if a in [akLeft,akTop] then begin
ChildData.Sides[a].Side:=asrRight;
end else begin
ChildData.Sides[a].Side:=asrLeft;
end;
case a of
akTop: ChildData.Sides[a].Space:=ChildData.BaseBounds.Top;
akLeft: ChildData.Sides[a].Space:=ChildData.BaseBounds.Left;
akRight: ChildData.Sides[a].Space:=
ChildData.BaseParentClientSize.cx-ChildData.BaseBounds.Right;
akBottom: ChildData.Sides[a].Space:=
ChildData.BaseParentClientSize.cy-ChildData.BaseBounds.Bottom;
end;
end else begin
// not anchored => use borderspacing
if a in [akLeft,akTop] then
ChildData.Sides[a].Side:=asrRight
else
ChildData.Sides[a].Side:=asrLeft;
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;
inc(ChildData.Sides[a].Space,AdjustedClientBorders[a]);
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;
FoundSides: TAnchors;
AddPreferredSize: Boolean;
begin
if ChildData.Sides[Side].DistanceState[Direction]
in [assdfValid,assdfUncomputable]
then
exit(crSuccess); // already computed
if ChildData.Sides[Side].DistanceState[Direction]=assdfComputing then begin
{$IFNDEF DisableChecks}
DebugLn(['TAutoSizeCtrlData.ComputePositions.ComputePosition CIRCLE detected ',DbgSName(ChildData.Control),' ',dbgs(Side),' ',AutoSizeSideDistDirectionNames[Direction]]);
{$ENDIF}
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,alClient] then
CurAnchors:=CurAnchors+AnchorAlign[Child.Align];
if (Side in CurAnchors) then begin
// this side is anchored
SiblingData:=ChildData.Sides[Side].CtrlData;
NewDist:=0;
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
AddPreferredSize:=true;
if OppositeSide in CurAnchors then begin
// compute opposite side first
Result:=ComputePosition(ChildData,OppositeSide,Direction);
if Result<>crSuccess then begin
{$IFNDEF DisableChecks}
DebugLn(['ComputePosition FAILED opposite side: ',DbgSName(Child),' ',dbgs(Side),' ',AutoSizeSideDistDirectionNames[Direction]]);
{$ENDIF}
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];
if (ChildData.Sides[OppositeSide].CtrlData<>nil)
and (ChildData.Sides[OppositeSide].CtrlData<>Self)
then begin
// opposite side is anchored to a sibling
if ((OppositeSide in [akLeft,akTop])
and (ChildData.Sides[OppositeSide].Side<>asrRight))
or ((OppositeSide in [akRight,akBottom])
and (ChildData.Sides[OppositeSide].Side<>asrLeft))
then
AddPreferredSize:=false;
end;
end else begin
NewDist:=ChildData.Sides[OppositeSide].Space;
end;
if AddPreferredSize then begin
if Side in [akLeft,akRight] then
inc(NewDist,ChildData.PreferredSize[asboHorizontal])
else
inc(NewDist,ChildData.PreferredSize[asboVertical]);
end;
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: // Note: asrLeft=asrTop
if Side in [akLeft,akRight] then begin
Include(NeededSiblingSides,akLeft);
end else begin
Include(NeededSiblingSides,akTop);
end;
asrRight: // Note: 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;
FoundSides:=[];
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
{$IFNDEF DisableChecks}
DebugLn(['ComputePosition breaking CIRCLE ',DbgSName(Child),' - ',DbgSName(SiblingData.Control),' ',dbgs(a),' ',AutoSizeSideDistDirectionNames[Direction]]);
{$ENDIF}
Child.Anchors:=Child.Anchors-[Side];
Result:=crFixedCircled;
end;
if Result<>crSuccess then begin
{$IFNDEF DisableChecks}
DebugLn(['ComputePosition FAILED sibling dependency: ',DbgSName(Child),' - ',DbgSName(SiblingData.Control),' Side=',dbgs(Side),' a=',dbgs(a),' ',AutoSizeSideDistDirectionNames[Direction]]);
{$ENDIF}
exit;
end;
if SiblingData.Sides[a].DistanceState[Direction]=assdfValid then
Include(FoundSides,a);
end;
end;
if FoundSides=[] then begin
ChildData.Sides[Side].DistanceState[Direction]:=assdfUncomputable;
exit(crSuccess);
end;
// this side is anchored to a sibling and some 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
if FoundSides=[akLeft,akRight] then begin
NewDist:=((SiblingData.Sides[akLeft].Distance[Direction]
+SiblingData.Sides[akRight].Distance[Direction]) div 2);
end else if (FoundSides=[akLeft]) then begin
NewDist:=SiblingData.Sides[akLeft].Distance[Direction]
+(SiblingData.PreferredSize[asboHorizontal] div 2);
end else begin
NewDist:=SiblingData.Sides[akRight].Distance[Direction]
-(SiblingData.PreferredSize[asboHorizontal] div 2);
end;
//DebugLn(['ComputePosition BEFORE ',DbgSName(Child),' center to ',DbgSName(SiblingData.Control),' Side=',dbgs(Side),' FoundSides=',dbgs(FoundSides),' NewDist=',NewDist,' Direction=',AutoSizeSideDistDirectionNames[Direction],' PreferredSize=',ChildData.PreferredSize[asboHorizontal]]);
dec(NewDist,ChildData.PreferredSize[asboHorizontal] div 2);
// use at least the size of the child
if (Side=akLeft)=(Direction=assddRightBottom) then
NewDist:=Max(NewDist,ChildData.PreferredSize[asboHorizontal]);
//DebugLn(['ComputePosition AFTER ',DbgSName(Child),' center to ',DbgSName(SiblingData.Control),' Side=',dbgs(Side),' FoundSides=',dbgs(FoundSides),' NewDist=',NewDist,' Direction=',AutoSizeSideDistDirectionNames[Direction],' PreferredSize=',ChildData.PreferredSize[asboHorizontal]]);
end else begin
if FoundSides=[akTop,akBottom] then begin
NewDist:=((SiblingData.Sides[akTop].Distance[Direction]
+SiblingData.Sides[akBottom].Distance[Direction]) div 2);
end else if (FoundSides=[akTop]) then begin
NewDist:=SiblingData.Sides[akTop].Distance[Direction]
+(SiblingData.PreferredSize[asboVertical] div 2);
end else begin
NewDist:=SiblingData.Sides[akBottom].Distance[Direction]
-(SiblingData.PreferredSize[asboVertical] div 2);
end;
dec(NewDist,ChildData.PreferredSize[asboVertical] div 2);
// use at least the size of the child
if (Side=akTop)=(Direction=assddRightBottom) then
NewDist:=Max(NewDist,ChildData.PreferredSize[asboVertical]);
end;
end;
ChildData.Sides[Side].Distance[Direction]:=NewDist;
ChildData.Sides[Side].DistanceState[Direction]:=assdfValid;
if (OppositeSide in CurAnchors)
and ((Direction=assddLeftTop) <> (Side in [akLeft,akTop])) then begin
// the opposite side is anchored too
// use the maximum of both anchors
Result:=ComputePosition(ChildData,OppositeSide,Direction);
if Result<>crSuccess then begin
//DebugLn(['ComputePosition (side anchored) 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];
CurSize:=0;
if ((OppositeSide in [akLeft,akTop])
and (ChildData.Sides[OppositeSide].Side=asrRight))
or ((OppositeSide in [akRight,akBottom])
and (ChildData.Sides[OppositeSide].Side=asrLeft))
then begin
if Side in [akLeft,akRight] then
CurSize:=ChildData.PreferredSize[asboHorizontal]
else
CurSize:=ChildData.PreferredSize[asboVertical];
end;
inc(NewDist,CurSize);
// check if opposite side needs a bigger distance
if ChildData.Sides[Side].Distance[Direction]<NewDist then
ChildData.Sides[Side].Distance[Direction]:=NewDist;
end;
assdfUncomputable: ; // no problem, there is already a value
else
raise Exception.Create('TAutoSizeCtrlData.ComputePositions.ComputePosition assdfValid,assdfUncomputable');
end;
end;
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 (side not anchored) 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
{$IFNDEF DisableChecks}
DebugLn(['TAutoSizeCtrlData.ComputePositions.ComputePosition ',DbgSName(Child),' Side=',dbgs(Side),' Direction=',AutoSizeSideDistDirectionNames[Direction]]);
{$ENDIF}
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:=Children[Child];
if not ChildData.Visible then continue;
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
if ComputePosition(ChildData,a,assddLeftTop)<>crSuccess then begin
{$IFNDEF DisableChecks}
DebugLn(['TAutoSizeCtrlData.ComputePositions Failed to compute LeftTop ',DbgSName(Child),' ',dbgs(a)]);
{$ENDIF}
exit;
end;
if ComputePosition(ChildData,a,assddRightBottom)<>crSuccess then begin
{$IFNDEF DisableChecks}
DebugLn(['TAutoSizeCtrlData.ComputePositions Failed to compute RightBottom ',DbgSName(Child),' ',dbgs(a)]);
{$ENDIF}
exit;
end;
end;
end;
//WriteDebugReport('ComputePositons',' ');
Result:=true;
end;
constructor TAutoSizeCtrlData.Create(AControl: TControl; IsParent: boolean);
const
BigInteger = High(Integer) div 4;
var
CurBorders: TRect;
a: TAnchorKind;
AdjustedClientRect: TRect;
r: 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;
BaseBounds:=Control.BaseBounds;
if (BaseBounds.Left=BaseBounds.Right)
and (BaseBounds.Top=BaseBounds.Bottom) then
BaseBounds:=Control.BoundsRect;
BaseParentClientSize:=Control.BaseParentClientSize;
if (WinControl<>nil) and IsParent 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[akBottom]:=BigInteger-AdjustedClientRect.Bottom;
end else begin
for a:=low(TAnchorKind) to high(TAnchorKind) do
AdjustedClientBorders[a]:=0;
if (BaseParentClientSize.cx=0) and (BaseParentClientSize.cy=0) then begin
r:=Control.Parent.GetLogicalClientRect;
BaseParentClientSize.cx:=r.Right;
BaseParentClientSize.cy:=r.Bottom;
end;
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.DoMoveNonAlignedChildren(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:=Children[Child];
if not ChildData.Visible then continue;
if IsNotAligned(Child, Side) then begin
// this is a non aligned control
//DebugLn(['TAutoSizeCtrlData.DoMoveNonAlignedChilds Child=',DbgSName(Child),' Side=',dbgs(Side)]);
if FindMinimum then begin
AddSpace:=Child.BorderSpacing.GetSideSpace(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 else if (Child.Align=alCustom)
and (Side in AnchorAlign[alCustom]) then begin
if FindMinimum then begin
// no auto move
end else begin
// don't move alCustom, but use them for bounds computation
SetFixedLeftTop(ChildData,Side,Child.GetSidePosition(Side));
end;
end;
end;
end;
procedure TAutoSizeCtrlData.SetupNonAlignedChildren(MoveNonAlignedChildrenLeft,
MoveNonAlignedChildrenTop: 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;
i: Integer;
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;
DoMoveNonAlignedChildren(akLeft,MoveDiff,true);
//DebugLn(['TAutoSizeCtrlData.ComputePreferredClientArea akLeft MoveDiff=',MoveDiff]);
if not MoveNonAlignedChildrenLeft then MoveDiff:=0;
DoMoveNonAlignedChildren(akLeft,MoveDiff,false);
MoveDiff:=0;
DoMoveNonAlignedChildren(akTop,MoveDiff,true);
//DebugLn(['TAutoSizeCtrlData.ComputePreferredClientArea akTop MoveDiff=',MoveDiff]);
if not MoveNonAlignedChildrenTop then MoveDiff:=0;
DoMoveNonAlignedChildren(akTop,MoveDiff,false);
end else begin
// there is an automatic layout for non aligned children
// use the layout engine, but with static values
ChildSizing:=nil;
Box:=nil;
AlignList:=TFPList.Create;
try
for i:=0 to WinControl.ControlCount-1 do begin
Child:=WinControl.Controls[i];
if Child.IsControlVisible and IsNotAligned(Child) then
AlignList.Add(Child);
end;
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,WinControl.BiDiMode,
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.Children[asboVertical][y];
for x:=0 to RowBox.ChildCount[asboHorizontal]-1 do begin
ControlBox:=RowBox.Children[asboHorizontal][x];
Child:=ControlBox.Control;
if Child=nil then continue;
NewBounds:=ControlBox.NewControlBounds;
//DebugLn(['TAutoSizeCtrlData.SetupNonAlignedChilds ',DbgSName(Child),' ',dbgs(NewBounds)]);
ChildData:=Children[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(
MoveNonAlignedChildrenLeft, MoveNonAlignedChildrenTop: boolean; out
MoveNonAlignedToLeft, MoveNonAlignedToTop, PreferredClientWidth,
PreferredClientHeight: integer);
{ if MoveNonAlignedChilds=true then all non-aligned children 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:=Children[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 Child.Name='OtherInfoGroupBox' then debugln(['InitPreferredSizes ',DbgSName(Child),' Bounds=',dbgs(Child.BoundsRect),' Anchors=',dbgs(Child.Anchors),' CurAnchors=',dbgs(CurAnchors),' UseW=',UseCurrentWidth,' UseH=',UseCurrentHeight,' Pref=',CurPreferredWidth,'x',CurPreferredHeight]);
if UseCurrentWidth then
NewWidth:=Child.Width
else if (CurPreferredWidth>0)
or ((CurPreferredWidth=0) and (csAutoSize0x0 in Child.ControlStyle)) then
NewWidth:=CurPreferredWidth
else
NewWidth:=Max(1,Child.GetDefaultWidth);
NewWidth:=Child.Constraints.MinMaxWidth(NewWidth);
if UseCurrentHeight then
NewHeight:=Child.Height
else if (CurPreferredHeight>0)
or ((CurPreferredHeight=0) and (csAutoSize0x0 in Child.ControlStyle)) then
NewHeight:=CurPreferredHeight
else
NewHeight:=Max(1,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=nil then begin
// invalid anchor
// => anchor to parent
ChildData.Sides[a].CtrlData:=Self;
if a in [akLeft,akTop] then
ChildData.Sides[a].Side:=asrLeft
else
ChildData.Sides[a].Side:=asrRight;
exit;
end;
if ReferenceControl=Control then
ChildData.Sides[a].CtrlData:=Self
else if (ReferenceControl<>nil) and (ReferenceControl.Parent=Control) then
ChildData.Sides[a].CtrlData:=Children[ReferenceControl];
ChildData.Sides[a].Side:=ReferenceSide;
//if ChildData.Sides[a].CtrlData<>nil then DebugLn(['GetSideAnchor 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;
MoveNonAlignedToLeft:=0;
MoveNonAlignedToTop:=0;
if ChildCount=0 then exit;
// fix control properties
// check if there are visible children
VisibleCount:=0;
for i:=0 to ChildCount-1 do begin
Child:=WinControl.Controls[i];
FixControlProperties(Child);
ChildData:=Children[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:=Children[Child];
ChildData.ClearSides;
if not ChildData.Visible then continue;
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;
//WriteDebugReport('anchored','');
SetupNonAlignedChildren(MoveNonAlignedChildrenLeft,MoveNonAlignedChildrenTop);
//WriteDebugReport('nonaligned','');
// setup the dependencies for Aligned controls
AlignChildren;
//WriteDebugReport('aligned','');
// setup space for dependencies
SetupSpace;
{$IFDEF VerboseAutoSizeCtrlData}
WriteDebugReport('Space completed','');
{$ENDIF}
// calculate the needed positions for all children
until ComputePositions;
{$IFDEF VerboseAutoSizeCtrlData}
if WinControl.ClassName='TScrollBox' then
WriteDebugReport('Positions completed','');
{$ENDIF}
// compute needed clientwidth/clientheight
for i:=0 to ChildCount-1 do begin
Child:=WinControl.Controls[i];
ChildData:=Children[Child];
if not ChildData.Visible then continue;
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;
// compute needed MoveNonAlignedToLeft,MoveNonAlignedToTop
if MoveNonAlignedChildrenLeft or MoveNonAlignedChildrenTop then
begin
MoveNonAlignedToLeft:=Low(integer);
MoveNonAlignedToTop:=Low(integer);
for i:=0 to ChildCount-1 do
begin
Child:=WinControl.Controls[i];
ChildData:=Children[Child];
if not Child.IsControlVisible then continue;
if IsNotAligned(Child, akLeft) then
begin
if MoveNonAlignedChildrenLeft
and (ChildData.Sides[akLeft].DistanceState[assddLeftTop]=assdfValid) then
MoveNonAlignedToLeft:=Max(MoveNonAlignedToLeft,
Child.Left-ChildData.Sides[akLeft].Distance[assddLeftTop]);
{ the below is only correct, if PreferredClientWidth is realized.
if (ChildData.Sides[akLeft].DistanceState[assddRightBottom]=assdfValid) then
MoveNonAlignedToLeft:=Min(MoveNonAlignedToLeft,
Child.Left
-(PreferredClientWidth
-ChildData.Sides[akLeft].Distance[assddRightBottom]));}
end;
if IsNotAligned(Child, akTop) then
begin
if MoveNonAlignedChildrenTop
and (ChildData.Sides[akTop].DistanceState[assddLeftTop]=assdfValid) then
MoveNonAlignedToTop:=Max(MoveNonAlignedToTop,
Child.Top-ChildData.Sides[akTop].Distance[assddLeftTop]);
{ the below is only correct, if PreferredClientWidth is realized.
if (ChildData.Sides[akTop].DistanceState[assddRightBottom]=assdfValid) then
MoveNonAlignedToTop:=Min(MoveNonAlignedToTop,
Child.Top
-(PreferredClientHeight
-ChildData.Sides[akTop].Distance[assddRightBottom]));}
end;
end;
if MoveNonAlignedToLeft=Low(integer) then MoveNonAlignedToLeft:=0;
if MoveNonAlignedToTop=Low(integer) then MoveNonAlignedToTop:=0;
end;
{$IFDEF VerboseAutoSizeCtrlData}
//if WinControl.ClassName='TProjectVersionInfoOptionsFrame' then
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
{$IFNDEF DisableChecks}
DebugLn(['TAutoSizeCtrlData.FixControlProperties ',DbgSName(Child),' a=',dbgs(a),' old=',DbgSName(Child.AnchorSide[a].Control),' new=nil']);
{$ENDIF}
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
{$IFNDEF DisableChecks}
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']);
{$ENDIF}
Child.AnchorSide[a].Control:=nil;
end;
if Child.AnchorSide[a].Side=asrCenter then begin
// an aligned control can not be centered
{$IFNDEF DisableChecks}
DebugLn(['TAutoSizeCtrlData.FixControlProperties aligned control can not be centered ',DbgSName(Child),' a=',dbgs(a)]);
{$ENDIF}
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
{$IFNDEF DisableChecks}
if OppositeAnchor[a] in Child.Anchors then
DebugLn(['TAutoSizeCtrlData.FixControlProperties control is center-anchored -> unanchor opposite side: ',DbgSName(Child),' a=',dbgs(a)]);
{$ENDIF}
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;
OnlyVisible: boolean);
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
if WinControl.Controls[i].Visible or (not OnlyVisible) then
Children[WinControl.Controls[i]].WriteDebugReport('',Prefix+dbgs(i)+': ');
end;
{ TAutoSizeBox }
procedure TAutoSizeBox.SetControl(AControl: TControl);
var
Border: TRect;
AutoSize0x0: Boolean;
IsPrefWidthValid: Boolean;
IsPrefHeightValid: Boolean;
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
);
//DebugLn(['TAutoSizeBox.SetControl ',DbgSName(Control),' ',PreferredSize[asboHorizontal]]);
AutoSize0x0:=csAutoSize0x0 in Control.ControlStyle;
IsPrefWidthValid:=(PreferredSize[asboHorizontal]>0)
or (AutoSize0x0 and (PreferredSize[asboHorizontal]=0));
IsPrefHeightValid:=(PreferredSize[asboVertical]>0)
or (AutoSize0x0 and (PreferredSize[asboVertical]=0));
// apply constraints
if IsPrefWidthValid then
PreferredSize[asboHorizontal]:=
Control.Constraints.MinMaxWidth(PreferredSize[asboHorizontal]);
if IsPrefHeightValid then
PreferredSize[asboVertical]:=
Control.Constraints.MinMaxHeight(PreferredSize[asboVertical]);
if IsPrefWidthValid
and (Control.AutoSize or (Control.BorderSpacing.CellAlignHorizontal<>ccaFill))
then begin
// the control.width is fixed to its preferred width
MaximumSize[asboHorizontal]:=PreferredSize[asboHorizontal];
end;
if IsPrefHeightValid
and (Control.AutoSize or (Control.BorderSpacing.CellAlignVertical<>ccaFill))
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 not IsPrefWidthValid then
PreferredSize[asboHorizontal]:=
Control.Constraints.MinMaxWidth(Control.GetControlClassDefaultSize.CX);
if not IsPrefHeightValid then
PreferredSize[asboVertical]:=
Control.Constraints.MinMaxHeight(Control.GetControlClassDefaultSize.CX);
//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(Children[Orientation],Size);
if Size>0 then
FillChar(Children[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;
Children[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;
Children[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:=Children[asboVertical][y];
for x:=0 to ColCount-1 do begin
ColBox:=Children[asboHorizontal][x];
CellBox:=TAutoSizeBox.Create;
RowBox.Children[asboHorizontal][x]:=CellBox;
ColBox.Children[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; BiDiMode: TBiDiMode);
var
i: Integer;
Row: LongInt;
Col: LongInt;
ChildControl: TControl;
ChildBox: TAutoSizeBox;
RowCount: LongInt;
ColCount: Integer;
LineMax: LongInt;
begin
// allocate table
case ChildSizing.Layout of
cclLeftToRightThenTopToBottom:
begin
ColCount:=Max(1,Min(ChildSizing.ControlsPerLine,ListOfControls.Count));
RowCount:=((ListOfControls.Count-1) div ColCount)+1;
end;
cclTopToBottomThenLeftToRight:
begin
RowCount:=Max(1,min(ChildSizing.ControlsPerLine,ListOfControls.Count));
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
LineMax:=ChildCount[asboHorizontal];
Row:=i div LineMax;
Col:=i mod LineMax;
if (BiDiMode=bdRightToLeft) then
Col:=LineMax-Col-1;
ChildBox:=Children[asboHorizontal][Col].Children[asboVertical][Row];
ChildBox.SetControl(ChildControl);
ChildBox.ApplyChildsizingBorders(ChildSizing);
end;
cclTopToBottomThenLeftToRight:
begin
LineMax:=ChildCount[asboVertical];
Col:=i div LineMax;
Row:=i mod LineMax;
if (BiDiMode=bdRightToLeft) then
Col:=LineMax-Col-1;
ChildBox:=Children[asboVertical][Row].Children[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:=Children[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 orthogonal minimum is the maximum of all minimums
// total orthogonal maximum is the minimum of all maximums
// total orthogonal 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:=Children[Orientation][i];
// add border in Orientation
CurBorder:=CurChild.BorderLeftTop[Orientation];
if i>0 then
CurBorder:=Max(Children[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:=Children[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:=Children[asboVertical][y];
RowBox.SumLine(asboHorizontal,true);
end;
// sum items in columns
for x:=0 to ChildCount[asboHorizontal]-1 do begin
ColBox:=Children[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;
s: LongInt;
begin
CurLeftTop:=0;
for i:=0 to ChildCount[Orientation]-1 do begin
Child:=Children[Orientation][i];
if i=0 then
inc(CurLeftTop,Child.BorderLeftTop[Orientation]);
Child.LeftTop[Orientation]:=CurLeftTop;
inc(CurLeftTop,Child.PreferredSize[Orientation]);
s:=Child.BorderRightBottom[Orientation];
if i<ChildCount[Orientation]-1 then
s:=Max(s,Children[Orientation][i+1].BorderLeftTop[Orientation]);
inc(CurLeftTop,s);
end;
end;
procedure TAutoSizeBox.ResizeChildren(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 children of this Orientation
var
i: Integer;
Child: TAutoSizeBox;
s: LongInt;
begin
Result:=0;
for i:=0 to ChildCount[Orientation]-1 do begin
Child:=Children[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]);
s:=Child.BorderRightBottom[Orientation];
if i<ChildCount[Orientation]-1 then
s:=Max(s,Children[Orientation][i+1].BorderLeftTop[Orientation]);
inc(Result,s);
end;
end;
procedure GetChildMaxResize(out Factor: TResizeFactor;
out ResizeableCount: integer);
// returns the number of children/gaps, that can grow (ResizeableCount)
// and the maximum factor, by which the children/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:=Children[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:=Children[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 children/gaps, that can shrink (ResizeableCount)
// and the maximum factor, by which the children/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:=Children[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
CurScale:=double(Child.MinimumSize[Orientation])
/Child.PreferredSize[Orientation];
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
CurScale:=double(Child.MinimumSize[Orientation])
/Child.PreferredSize[Orientation];
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:=Children[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:=Children[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 children
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;
end;
procedure TAutoSizeBox.ResizeTable(ChildSizing: TControlChildSizing;
TargetWidth, TargetHeight: integer);
begin
// resize rows and columns
ResizeChildren(ChildSizing,asboHorizontal,TargetWidth);
ComputeLeftTops(asboHorizontal);
ResizeChildren(ChildSizing,asboVertical,TargetHeight);
ComputeLeftTops(asboVertical);
end;
procedure TAutoSizeBox.AlignToRight(TargetWidth: integer);
function GetChildTotalSize(Orientation: TAutoSizeBoxOrientation): integer;
// computes the total preferred size of all children of this Orientation
var
i: Integer;
Child: TAutoSizeBox;
begin
Result:=0;
for i:=0 to ChildCount[Orientation]-1 do begin
Child:=Children[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;
var
Orientation: TAutoSizeBoxOrientation;
i: Integer;
Child: TAutoSizeBox;
dx: Integer;
begin
Orientation:=asboHorizontal;
dx:=TargetWidth-GetChildTotalSize(Orientation);
for i:=ChildCount[Orientation]-1 downto 0 do begin
Child:=Children[Orientation][i];
inc(Child.LeftTop[Orientation],dx);
end;
end;
procedure TAutoSizeBox.ComputeTableControlBounds(
ChildSizing: TControlChildSizing; BiDiMode: TBiDiMode);
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:=Children[asboVertical][y];
for x:=0 to RowBox.ChildCount[asboHorizontal]-1 do begin
ControlBox:=RowBox.Children[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:
if (CurControl.BorderSpacing.CellAlignHorizontal=ccaRightBottom)
=(BidiMode=bdLeftToRight)
then
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;
{$IFDEF CHECK_POSITION}
if CheckPosition(CurControl) then
DebugLn(['TAutoSizeBox.ComputeTableControlBounds ',DbgSName(CurControl),
' CellBounds=',dbgs(CellBounds),
' Preferred=',ControlBox.PreferredSize[asboHorizontal],'x',ControlBox.PreferredSize[asboVertical],
' NewBounds=',dbgs(NewBounds)]);
{$ENDIF}
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:=Children[asboVertical][y];
for x:=0 to RowBox.ChildCount[asboHorizontal]-1 do begin
ControlBox:=RowBox.Children[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.SetBoundsKeepBase(NewBounds.Left,
NewBounds.Top,
NewBounds.Right-NewBounds.Left,
NewBounds.Bottom-NewBounds.Top);
end;
end;
end;
end;
function TAutoSizeBox.AlignControlsInTable(ListOfControls: TFPList;
ChildSizing: TControlChildSizing; BiDiMode: TBiDiMode;
TargetWidth, TargetHeight: integer;
Apply: boolean): boolean;
// true if a control was modified
begin
SetTableControls(ListOfControls,ChildSizing,BiDiMode);
//WriteDebugReport('after SetTableControls');
SumTable;
//WriteDebugReport('after SumTable');
ResizeTable(ChildSizing,TargetWidth,TargetHeight);
//WriteDebugReport('after ResizeTable');
if BiDiMode=bdRightToLeft then
AlignToRight(TargetWidth);
//WriteDebugReport('after AlignToRight');
ComputeTableControlBounds(ChildSizing,BiDiMode);
//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:=Children[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.Children[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:=Children[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].Children[o][Index[o]]:=nil;
Clear;
inherited Destroy;
end;
procedure TAutoSizeBox.Clear;
var
o: TAutoSizeBoxOrientation;
i: Integer;
begin
// free all children
for o:=Low(TAutoSizeBoxOrientation) to high(TAutoSizeBoxOrientation) do
for i:=0 to ChildCount[o]-1 do
Children[o][i].Free;
// free children arrays
for o:=Low(TAutoSizeBoxOrientation) to high(TAutoSizeBoxOrientation) do
ReallocMem(Children[o],0);
end;
{------------------------------------------------------------------------------
function TWinControl.AutoSizePhases: TControlAutoSizePhases;
------------------------------------------------------------------------------}
function TWinControl.AutoSizePhases: TControlAutoSizePhases;
begin
if Parent<>nil then
Result:=Parent.AutoSizePhases
else begin
Result:=[];
if ([wcfCreatingHandle,wcfCreatingChildHandles]*FWinControlFlags<>[]) then
Include(Result,caspCreatingHandles);
if fAutoSizingAll then
Include(Result,caspComputingBounds);
if wcfRealizingBounds in FWinControlFlags then
Include(Result,caspRealizingBounds);
if wcfUpdateShowing in FWinControlFlags then
Include(Result,caspShowing);
if FAutoSizingLockCount>0 then
Include(Result,caspChangingProperties);
end;
end;
{------------------------------------------------------------------------------
function TWinControl.AutoSizeDelayed: boolean;
------------------------------------------------------------------------------}
function TWinControl.AutoSizeDelayed: boolean;
begin
Result:=(csDestroyingHandle in ControlState)
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
if not HandleAllocated then
debugln('TWinControl.AutoSizeDelayed Self='+DbgSName(Self)+' not HandleAllocated');
end;
{$ENDIF}
end;
function TWinControl.AutoSizeDelayedReport: string;
begin
if csDestroyingHandle in ControlState then
Result:='csDestroyingHandle'
else
Result:=inherited AutoSizeDelayedReport;
end;
{------------------------------------------------------------------------------
TWinControl AutoSizeDelayedHandle
Returns true if AutoSize should be skipped / delayed because of its handle.
A TWinControl needs a parent handle.
------------------------------------------------------------------------------}
function TWinControl.AutoSizeDelayedHandle: Boolean;
begin
Result := (Parent = nil) and (ParentWindow = 0);
end;
{------------------------------------------------------------------------------
TWinControl AdjustClientRect
------------------------------------------------------------------------------}
procedure TWinControl.AdjustClientRect(var ARect: TRect);
begin
// Can be overriden.
// It's called often, so don't put expensive code here, or cache the result
end;
procedure TWinControl.GetAdjustedLogicalClientRect(out ARect: TRect);
begin
if not (wcfAdjustedLogicalClientRectValid in FWinControlFlags) then begin
FAdjustClientRect:=GetLogicalClientRect;
AdjustClientRect(FAdjustClientRect);
Include(FWinControlFlags,wcfAdjustedLogicalClientRectValid);
end;
ARect:=FAdjustClientRect;
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
case AAlign of
alTop: begin
Result := (Control1.Top < Control2.Top)
or ( (Control1.Top = Control2.Top)
and (Control1.FBaseBounds.Top < Control2.FBaseBounds.Top));
end;
alLeft: begin
Result := (Control1.Left < Control2.Left)
or ( (Control1.Left = Control2.Left)
and (Control1.FBaseBounds.Left < Control2.FBaseBounds.Left));
end;
// 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: begin
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));
end;
alRight: begin
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;
alCustom: begin
// CustomAlignInsertBefore returns true when Control2 is inserted before Control1
// We return true when Control1 is inserted before Control2
// So swap controls
Result := CustomAlignInsertBefore(Control2, Control1);
end;
else
Result := False;
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 FAlignOrder.Count - 1 do
begin
Control := TControl(FAlignOrder[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;
ParentClientWidth: integer;
ParentClientHeight: integer;
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;
procedure DoPosition(Control: TControl; AAlign: TAlign; AControlIndex: Integer);
var
NewLeft, NewTop, NewWidth, NewHeight: Integer;
ParentBaseClientSize: TSize;
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;
AlignInfo: TAlignInfo; // alCustom
PrefWidth: integer;
PrefHeight: integer;
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 begin
//DebugLn(['GetAnchorSidePosition ',DbgSName(Control),' ReferenceControl=',DbgSName(ReferenceControl)]);
Result:=Position;
end;
//if CheckPosition(Control) and (Kind=akRight) 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=',DbgS(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;
if AutoSize then begin
GetPreferredSize(PrefWidth,PrefHeight);
if PrefWidth>0 then NewWidth:=PrefWidth;
if PrefHeight>0 then NewHeight:=PrefHeight;
end;
ConstraintWidth(NewLeft,NewWidth);
ConstraintHeight(NewTop,NewHeight);
end;
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.cx=0)
and (ParentBaseClientSize.cy=0) then
ParentBaseClientSize:=Size(ParentClientWidth,ParentClientHeight);
// get base bounds of Control
CurBaseBounds:=Control.FBaseBounds;
if not (cfBaseBoundsValid in FControlFlags) 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.cx)+','+dbgs(ParentBaseClientSize.cy),
' 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.cx-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.cx-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.cx)
-(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.cy-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.cy-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.cy)
-(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',
' Self=',DbgSName(Self),
' Align=',DbgS(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;
case AAlign of
alLeft,alTop,alRight,alBottom,alClient: 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=',DbgS(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;
alCustom: begin
AlignInfo.AlignList := AlignList;
AlignInfo.Align := alCustom;
AlignInfo.ControlIndex := AControlIndex;
CustomAlignPosition(Control, NewLeft, NewTop, NewWidth, NewHeight, RemainingClientRect, AlignInfo);
end;
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.SetBoundsKeepBase(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=',DbgS(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=',DbgS(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, I);
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;
OldRemainingClientRect: TRect;
OldRemainingBorderSpace: TRect;
MaxTries: LongInt;
r: TRect;
begin
//DebugLn(['TWinControl.AlignControls ',DbgSName(Self),' ',not (wcfAligningControls in FWinControlFlags)]);
if wcfAligningControls in FWinControlFlags then exit;
Include(FWinControlFlags,wcfAligningControls);
try
//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;
// first let the DockManager align controls
if DockSite and UseDockManager and (DockManager<>nil) then
DockManager.ResetBounds(false);
AdjustClientRect(RemainingClientRect);
r:=GetLogicalClientRect;
ParentClientWidth:=r.Right;
ParentClientHeight:=r.Bottom;
if NeedAlignWork then
begin
//DebugLn(['TWinControl.AlignControls ',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;
end;
function TWinControl.CustomAlignInsertBefore(AControl1, AControl2: TControl): Boolean;
begin
Result := Assigned(FOnAlignInsertBefore)
and FOnAlignInsertBefore(Self, AControl1, AControl2);
end;
procedure TWinControl.CustomAlignPosition(AControl: TControl; var ANewLeft, ANewTop,
ANewWidth, ANewHeight: Integer; var AlignRect: TRect; AlignInfo: TAlignInfo);
begin
if Assigned(FOnAlignPosition)
then FOnAlignPosition(Self, AControl, ANewLeft, ANewTop, ANewWidth, ANewHeight, AlignRect, AlignInfo);
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.InvalidatePreferredChildSizes;
var
AControl: TControl;
i: Integer;
begin
for i:=0 to ControlCount-1 do begin
AControl:=Controls[i];
Exclude(AControl.FControlFlags,cfPreferredSizeValid);
Exclude(AControl.FControlFlags,cfPreferredMinSizeValid);
if AControl is TWinControl then
Exclude(TWinControl(AControl).FWinControlFlags,wcfAdjustedLogicalClientRectValid);
if AControl is TWinControl then
TWinControl(AControl).InvalidatePreferredChildSizes;
end;
end;
{-------------------------------------------------------------------------------
procedure TWinControl.DoAutoSize;
Shrink or enlarge to fit children.
-------------------------------------------------------------------------------}
procedure TWinControl.DoAutoSize;
var
HasVisibleChilds: boolean;
procedure GetMoveDiffForNonAlignedChilds(const CurClientRect: TRect;
out dx, dy: integer);
// how much can non-aligned-children be moved up and left
// non-aligned-children: no fixed anchoring or autosizing,
// (Align=alNone, visible, AnchorSide[].Control=nil)
// borderspacing is used
// e.g. dx=10 means all non-align-children should be moved 10 pixels to the left
var
NewClientWidth, NewClientHeight: integer;
Layout: TAutoSizeCtrlData;
begin
if ChildSizing.Layout<>cclNone then begin
dx:=0;
dy:=0;
exit;
end;
// get the move requirements for the child controls
Layout:=nil;
try
Layout:=TAutoSizeCtrlData.Create(Self);
Layout.ComputePreferredClientArea(
not (csAutoSizeKeepChildLeft in ControlStyle),
not (csAutoSizeKeepChildTop in ControlStyle),
dx,dy,NewClientWidth,NewClientHeight);
if (NewClientWidth<>0) or (NewClientHeight<>0) then ;
//if (dx<>0) or (dy<>0) then DebugLn(['GetMoveDiffForNonAlignedChilds ',DbgSName(Self),' dx=',dx,' dy=',dy]);
finally
Layout.Free;
end;
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 VerboseAllAutoSize}
debugln('TWinControl.DoAutoSize ',DbgSName(Self));
{$ENDIF}
if not (caspComputingBounds in AutoSizePhases) then begin
{$IFDEF VerboseAllAutoSize}
DebugLn(['TWinControl.DoAutoSize DELAYED AutoSizePhases=',dbgs(AutoSizePhases)]);
{$ENDIF}
AdjustSize;
exit;
end;
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.DoAutoSize'){$ENDIF};
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:=WidthIsAnchored;
HeightIsFixed:=HeightIsAnchored;
// move free children as much as possible to left and top (all free children the same)
if HasVisibleChilds then begin
CurClientRect:=GetLogicalClientRect;
AdjustClientRect(CurClientRect);
// get minimum left, top of non aligned children
GetMoveDiffForNonAlignedChilds(CurClientRect,dx,dy);
//DebugLn(['TWinControl.DoAutoSize ',DbgSName(Self),' CurClientRect=',dbgs(CurClientRect)]);
if (dx<>0) or (dy<>0) then begin
// move all free children 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)
or ((PreferredWidth=0) and (not (csAutoSize0x0 in ControlStyle))) then
PreferredWidth:=Constraints.MinMaxWidth(Width);
if HeightIsFixed or (PreferredHeight<0)
or ((PreferredHeight=0) and (not (csAutoSize0x0 in ControlStyle))) then
PreferredHeight:=Constraints.MinMaxHeight(Height);
// set new size
{$IF defined(VerboseAutoSize) or defined(VerboseAllAutoSize)}
debugln(['TWinControl.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)]);
SetBoundsKeepBase(NewLeft,NewTop,PreferredWidth,PreferredHeight);
end;
finally
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.DoAutoSize'){$ENDIF};
end;
end;
procedure TWinControl.DoAllAutoSize;
function CheckHandleAllocated(AWinControl: TWinControl): boolean;
// true if a handle was missing
var
i: Integer;
ChildWinControl: TWinControl;
begin
if AWinControl.HandleObjectShouldBeVisible and (not AWinControl.HandleAllocated) then
begin
{$IFDEF VerboseAllAutoSize}
DebugLn(['TWinControl.DoAllAutoSize CREATE HANDLE ',DbgSName(AWinControl)]);
{$ENDIF}
AWinControl.HandleNeeded;
Exit(True);
end;
Result := False;
for i := 0 to AWinControl.ControlCount - 1 do
begin
ChildWinControl := TWinControl(AWinControl.Controls[i]);
if (ChildWinControl is TWinControl) and CheckHandleAllocated(ChildWinControl) then
Result:=true;
end;
end;
procedure ClearRequests(AControl: TControl);
var
i: Integer;
begin
Exclude(AControl.FControlFlags,cfAutoSizeNeeded);
if AControl is TWinControl then
for i:=0 to TWinControl(AControl).ControlCount-1 do
ClearRequests(TWinControl(AControl).Controls[i]);
end;
procedure UpdateShowingRecursive(AWinControl: TWinControl;
OnlyChildren: boolean);
var
i: Integer;
begin
// first make the children visible
if AWinControl.FControls<>nil then
for i:=0 to AWinControl.FControls.Count-1 do
if TObject(AWinControl.FControls[i]) is TWinControl then
UpdateShowingRecursive(TWinControl(AWinControl.FControls[i]),false);
// then make the control visible
if not OnlyChildren and AWinControl.HandleObjectShouldBeVisible and not AWinControl.Showing then
AWinControl.UpdateShowing;
end;
var
RealizeCounter: Integer;
UpdateShowingCounter: Integer;
begin
if wcfAllAutoSizing in FWinControlFlags then exit;
if AutoSizeDelayed then exit;
{$IFDEF VerboseAllAutoSize}
DebugLn(['TWinControl.DoAllAutoSize START ',DbgSName(Self),' ',dbgs(BoundsRect)]);
{$ENDIF}
Include(FWinControlFlags,wcfAllAutoSizing);
try
// create needed handles
if HandleObjectShouldBeVisible then begin
if CheckHandleAllocated(Self) then begin
// a new handle was created
end;
end else begin
// no autosize possible => remove needed flags
ClearRequests(Self);
exit;
end;
RealizeCounter:=0;
UpdateShowingCounter:=0;
while (not AutoSizeDelayed) do
begin
// compute all sizes for LCL objects without touching the widgetset
{$IFDEF VerboseAllAutoSize}
DebugLn(['TWinControl.DoAllAutoSize COMPUTE BOUNDS ',DbgSName(Self),' old=',dbgs(BoundsRect)]);
{$ENDIF}
inherited DoAllAutoSize;
if cfAutoSizeNeeded in FControlFlags then RaiseGDBException('');
AllAutoSized;
// send all new bounds to widgetset
{$IFDEF VerboseAllAutoSize}
DebugLn(['TWinControl.DoAllAutoSize REALIZE BOUNDS ',DbgSName(Self),' lclbounds=',dbgs(BoundsRect)]);
{$ENDIF}
inc(RealizeCounter);
if RealizeCounter=100 then
Include(FWinControlFlags,wcfKillIntfSetBounds);
RealizeBoundsRecursive;
if (cfAutoSizeNeeded in FControlFlags) then continue; // repeat computing bounds
RealizeCounter:=0;
inc(UpdateShowingCounter);
// make child handles visible
{$IFDEF VerboseAllAutoSize}
DebugLn(['TWinControl.DoAllAutoSize UPDATESHOWING children ',DbgSName(Self),' lclbounds=',dbgs(BoundsRect)]);
{$ENDIF}
Include(FWinControlFlags,wcfUpdateShowing);
try
UpdateShowingRecursive(Self,true);
finally
Exclude(FWinControlFlags,wcfUpdateShowing);
end;
// check if another turn is needed
if not (cfAutoSizeNeeded in FControlFlags) then break; // complete
end;
{$IFDEF VerboseAllAutoSize}
DebugLn(['TWinControl.DoAllAutoSize END ',DbgSName(Self),' ',dbgs(BoundsRect)]);
{$ENDIF}
finally
FWinControlFlags:=FWinControlFlags-[wcfAllAutoSizing,wcfKillIntfSetBounds];
end;
// make handle visible => this can trigger events like Form.OnShow where
// application does arbitrary stuff
{$IFDEF VerboseAllAutoSize}
DebugLn(['TWinControl.DoAllAutoSize UPDATESHOWING self ',DbgSName(Self),' lclbounds=',dbgs(BoundsRect)]);
{$ENDIF}
if not (wcfUpdateShowing in FWinControlFlags) then
begin
Include(FWinControlFlags, wcfUpdateShowing);
try
if HandleObjectShouldBeVisible and not Showing then
UpdateShowing
else begin
{$IFDEF VerboseAllAutoSize}
DebugLn(['TWinControl.DoAllAutoSize not UPDATESHOWING self ',DbgSName(Self),' because HandleObjectShouldBeVisible=',HandleObjectShouldBeVisible,' Showing=',Showing]);
{$ENDIF}
end;
finally
Exclude(FWinControlFlags, wcfUpdateShowing);
end;
end;
end;
procedure TWinControl.AllAutoSized;
begin
// see TCustomForm.AllAutoSized
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
TWSWinControlClass(WidgetSetClass).DefaultWndHandler(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 CanSetFocus
CanSetFocus should be prefered over CanFocus if used in CanSetFocus/SetFocus
combination
if MyControl.CanSetFocus then
MyControl.SetFocus;
because it checks also if the parent form can receive focus and thus prevents
the "cannot focus an invisible window" LCL exception.
------------------------------------------------------------------------------}
function TWinControl.CanSetFocus: Boolean;
var
Control: TWinControl;
begin
Control := Self;
while True do
begin
// test if all are visible and enabled
if not (Control.IsControlVisible and Control.Enabled) then
Exit(False);
if not Assigned(Control.Parent) then
Break;
Control := Control.Parent;
end;
Result := Control is TCustomForm;//the very top parent must be a form
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
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.DisableAlign'){$ENDIF};
end;
{-------------------------------------------------------------------------------
TWinControl DoAdjustClientRectChange
Asks the interface if clientrect has changed since last AlignControl
and calls AdjustSize on change.
-------------------------------------------------------------------------------}
procedure TWinControl.DoAdjustClientRectChange(const InvalidateRect: Boolean = True);
var
R: TRect;
begin
if InvalidateRect then
InvalidateClientRectCache(False);
R := GetClientRect;
AdjustClientRect(R);
//if CheckPosition(Self) then
//DebugLn(['TWinControl.DoAdjustClientRectChange ',DbgSName(Self),' new=',dbgs(r),' old=',dbgs(FAdjustClientRectRealized),' ',CompareRect(@r,@FAdjustClientRectRealized)]);
if not CompareRect(@R, @FAdjustClientRectRealized) then
begin
// client rect changed since last AlignControl
{$IF defined(VerboseAllAutoSize) or defined(VerboseClientRectBugFix) or defined(VerboseIntfSizing) or defined(VerboseOnResize)}
DebugLn(['TWinControl.DoAdjustClientRectChange ClientRect changed ',DbgSName(Self),
' Old=',Dbgs(FAdjustClientRectRealized),' New=',DbgS(R)]);
{$ENDIF}
FAdjustClientRectRealized := R;
AdjustSize;
Resize;
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}
if Name=CheckClientRectName then begin
DebugLn(['TWinControl.InvalidateClientRectCache ',DbgSName(Self)]);
//DumpStack;
end;
{$ENDIF}
Exclude(FWinControlFlags,wcfAdjustedLogicalClientRectValid);
Include(FWinControlFlags,wcfClientRectNeedsUpdate);
if WithChildControls then begin
// invalidate clients too
if Assigned(FControls) then
for I := 0 to FControls.Count - 1 do
if TObject(FControls.Items[I]) is TWinControl then
TWinControl(FControls.Items[I]).InvalidateClientRectCache(true);
end;
InvalidatePreferredSize;
end;
{-------------------------------------------------------------------------------
TWinControl ClientRectNeedsInterfaceUpdate
The clientrect is cached. Check if cache is valid.
-------------------------------------------------------------------------------}
function TWinControl.ClientRectNeedsInterfaceUpdate: boolean;
var
InterfaceWidth, InterfaceHeight: integer;
IntfClientRect: TRect;
begin
if (not HandleAllocated) or (csDestroyingHandle in ControlState)
or (csDestroying in ComponentState)
then
exit(false);
if wcfClientRectNeedsUpdate in FWinControlFlags then
exit(true);
// get the current interface bounds
LCLIntf.GetWindowSize(Handle,InterfaceWidth,InterfaceHeight);
LCLIntf.GetClientRect(Handle,IntfClientRect);
// The LCL is not always in sync with the interface.
// Add the difference between LCL size and interface size to the
// interface clientrect
inc(IntfClientRect.Right,Width-InterfaceWidth);
inc(IntfClientRect.Bottom,Height-InterfaceHeight);
Result:=(FClientWidth<>IntfClientRect.Right)
or (FClientHeight<>IntfClientRect.Bottom);
{$IFDEF VerboseClientRectBugFix}
if (Name=CheckClientRectName) and Result then
DebugLn(['TWinControl.ClientRectNeedsInterfaceUpdate ',DbgSName(Self),' ',dbgs(IntfClientRect)]);
{$ENDIF}
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
children 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);
var
OldWidth: LongInt;
OldHeight: LongInt;
begin
//DbgOut('[TWinControl.DoSetBounds] ',Name,':',ClassName,' OldHeight=',DbgS(FHeight),' NewHeight=',DbgS(AHeight));
OldWidth:=Width;
OldHeight:=Height;
inherited DoSetBounds(ALeft, ATop, AWidth, AHeight);
// adapt Clientrect
inc(FClientWidth,Width-OldWidth);
if FClientWidth<0 then FClientWidth:=0;
inc(FClientHeight,Height-OldHeight);
if FClientHeight<0 then FClientHeight:=0;
Exclude(FWinControlFlags,wcfAdjustedLogicalClientRectValid);
end;
{------------------------------------------------------------------------------
TWinControl EnableAlign
------------------------------------------------------------------------------}
procedure TWinControl.EnableAlign;
begin
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.DisableAlign'){$ENDIF};
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;
procedure TWinControl.AutoAdjustLayout(AMode: TLayoutAdjustmentPolicy;
const AFromDPI, AToDPI, AOldFormWidth, ANewFormWidth: Integer);
var
i: Integer;
begin
inherited AutoAdjustLayout(AMode, AFromDPI, AToDPI, AOldFormWidth, ANewFormWidth);
for i:=0 to ControlCount-1 do
Controls[i].AutoAdjustLayout(AMode, AFromDPI, AToDPI, AOldFormWidth, ANewFormWidth);
end;
{------------------------------------------------------------------------------
TWinControl.CanTab
------------------------------------------------------------------------------}
function TWinControl.CanTab: Boolean;
begin
Result := CanFocus and TWSWinControlClass(WidgetSetClass).CanFocus(Self);
end;
function TWinControl.DoDragMsg(ADragMessage: TDragMessage; APosition: TPoint; ADragObject: TDragObject; ATarget: TControl; ADocking: Boolean):LRESULT;
var
TargetControl: TControl;
begin
case ADragMessage of
dmFindTarget:
begin
{$IFDEF VerboseDrag}
DebugLn('TWinControl.DoDragMsg dmFindTarget ',Name,':',ClassName,' Start DragMsg.DragRec^.Pos=',IntToStr(APosition.X),',',IntToStr(APosition.Y));
{$ENDIF}
TargetControl := ControlAtPos(ScreentoClient(APosition),
[capfAllowWinControls,capfRecursive]);
if TargetControl = nil then TargetControl := Self;
{$IFDEF VerboseDrag}
DebugLn('TWinControl.DoDragMsg dmFindTarget ',Name,':',ClassName,' end Result=',TargetControl.Name,':',TargetControl.ClassName);
{$ENDIF}
Result := LRESULT(TargetControl);
end;
else
Result := inherited;
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;
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
if Parent <> nil then
Result := inherited GetClientOrigin;
end;
{-------------------------------------------------------------------------------
TWinControl GetClientRect
Result: TRect
returns the client area. Starting at 0,0.
-------------------------------------------------------------------------------}
function TWinControl.GetClientRect: TRect;
procedure StoreClientRect(NewClientRect: TRect);
var
ClientSizeChanged: boolean;
begin
if wcfClientRectNeedsUpdate in FWinControlFlags then begin
ClientSizeChanged:=(FClientWidth<>NewClientRect.Right)
or (FClientHeight<>NewClientRect.Bottom);
if ClientSizeChanged then begin
FClientWidth:=NewClientRect.Right;
FClientHeight:=NewClientRect.Bottom;
{$IF defined(VerboseNewAutoSize) or defined(CHECK_POSITION)}
{$IFDEF CHECK_POSITION}
if CheckPosition(Self) then
{$ENDIF}
DebugLn(['StoreClientRect ',Name,':',ClassName,' ',FClientWidth,',',FClientHeight,' HandleAllocated=',HandleAllocated]);
{$ENDIF}
{$IFDEF VerboseClientRectBugFix}
DebugLn(['StoreClientRect ',DbgSName(Self),' ',FClientWidth,',',FClientHeight,' HandleAllocated=',HandleAllocated,' wcfBoundsRealized=',wcfBoundsRealized in FWinControlFlags]);
{$ENDIF}
Exclude(FWinControlFlags,wcfAdjustedLogicalClientRectValid);
end;
Exclude(FWinControlFlags,wcfClientRectNeedsUpdate);
end;
end;
procedure GetDefaults(var r: TRect);
begin
r:=inherited GetClientRect;
if csLoading in ComponentState then begin
if cfClientWidthLoaded in FControlFlags then
r.Right:=FLoadedClientSize.cx;
if cfClientHeightLoaded in FControlFlags then
r.Bottom:=FLoadedClientSize.cy;
end;
end;
var
InterfaceWidth, InterfaceHeight: integer;
begin
if wcfClientRectNeedsUpdate in FWinControlFlags then begin
//DebugLn(['TWinControl.GetClientRect ',DbgSName(Self),' ',HandleAllocated,' ',wcfBoundsRealized in FWinControlFlags]);
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);
{$IF defined(VerboseNewAutoSize) or defined(CHECK_POSITION)}
{$IFDEF CHECK_POSITION}
if CheckPosition(Self) then
{$ENDIF}
debugln('TWinControl.GetClientRect ',DbgSName(Self),' Interface=',dbgs(InterfaceWidth),',',dbgs(InterfaceHeight),' Result=',dbgs(Result),' Bounds=',dbgs(BoundsRect));
{$ENDIF}
{$IFDEF VerboseClientRectBugFix}
//if Name=CheckClientRectName then
debugln('TWinControl.GetClientRect ',DbgSName(Self),' InterfaceSize=',dbgs(InterfaceWidth),',',dbgs(InterfaceHeight),' Result=',dbgs(Result),' Bounds=',dbgs(BoundsRect));
{$ENDIF}
if (Width<>InterfaceWidth)
or (Height<>InterfaceHeight) then
begin
// the LCL is not in sync with the interface
if wcfBoundsRealized in FWinControlFlags then
begin
// no bounds were sent yet to the interface and it didn't initialize
// them on its own
// => the client bounds from the interface are not yet ready
// they will probably change
// to avoid resizes it is better use the defaults
GetDefaults(Result);
end else begin
// -> 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
inc(Result.Right,Width-InterfaceWidth);
inc(Result.Bottom,Height-InterfaceHeight);
end;
end;
end else begin
// no handle and no interface help => use defaults
GetDefaults(Result);
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.GetChildrenRect(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.GetChildrenRect(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 GetChildrenRect(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;
Perform(CM_BORDERCHANGED, 0, 0);
end;
procedure TWinControl.SetParentWindow(const AValue: HWND);
begin
if (ParentWindow = AValue) or Assigned(Parent) then Exit;
FParentWindow := AValue;
if HandleAllocated then
if (AValue <> 0) then
LCLIntf.SetParent(Handle, AValue)
else
DestroyHandle;
UpdateControlState;
end;
{------------------------------------------------------------------------------
TWinControl.SetChildZPosition
Set the position of the child control in the TWinControl(s)
------------------------------------------------------------------------------}
procedure TWinControl.SetChildZPosition(const AChild: TControl;
const APosition: Integer);
var
OldPos, NewPos: Integer;
IsWinControl: boolean;
i: Integer;
WinControls: TFPList;
begin
if AChild = nil
then begin
DebugLn('WARNING: TWinControl.SetChildZPosition: Child = nil');
Exit;
end;
IsWinControl := AChild is TWincontrol;
if FControls = nil then
begin
DebugLn('WARNING: TWinControl.SetChildZPosition: Unknown child');
Exit;
end;
OldPos := FControls.IndexOf(AChild);
if OldPos<0 then begin
DebugLn('WARNING: TWinControl.SetChildZPosition: Not a child');
Exit;
end;
NewPos := APosition;
if NewPos < 0 then
NewPos := 0;
if NewPos >= FControls.Count then
NewPos := FControls.Count - 1;
if NewPos = OldPos then Exit;
FControls.Move(OldPos, NewPos);
if IsWinControl then
begin
if HandleAllocated and TWinControl(AChild).HandleAllocated then
begin
// ignore children without handle
WinControls:=TFPList.Create;
try
for i:=FControls.Count-1 downto 0 do
begin
if (TObject(FControls[i]) is TWinControl) then
begin
WinControls.Add(FControls[i]);
end else begin
if i<OldPos then dec(OldPos);
if i<NewPos then dec(NewPos);
end;
end;
TWSWinControlClass(WidgetSetClass).SetChildZPosition(Self,
TWinControl(AChild), OldPos, NewPos, WinControls);
finally
WinControls.Free;
end;
end;
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);
Perform(CM_TABSTOPCHANGED, 0, 0);
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;
if NewTabOrder <> FTabOrder then
begin
if FTabOrder <> - 1 then
ListDelete(FParent.FTabList,FTabOrder);
if NewTabOrder <> -1 then
begin
if NewTabOrder = Count then
ListAdd(FParent.FTabList,Self)
else
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
Check control's handle visibility.
If handle should become visible the handle and child handles are created.
The
------------------------------------------------------------------------------}
procedure TWinControl.UpdateShowing;
procedure ChangeShowing(bShow: Boolean);
begin
if FShowing = bShow then Exit;
FShowing := bShow;
try
{$IFDEF VerboseShowing}
DebugLn(['ChangeShowing ',DbgSName(Self),' new FShowing=',FShowing]);
{$ENDIF}
Perform(CM_SHOWINGCHANGED, 0, 0); // see TWinControl.CMShowingChanged
finally
if FShowing<>(wcfHandleVisible in FWinControlFlags) then
begin
FShowing := wcfHandleVisible in FWinControlFlags;
DebugLn(['TWinControl.UpdateShowing.ChangeShowing failed for ',DbgSName(Self),', Showing reset to ',FShowing]);
end;
end;
end;
var
bShow: Boolean;
n: Integer;
begin
bShow := HandleObjectShouldBeVisible;
if bShow then
begin
if not HandleAllocated then CreateHandle;
if Assigned(FControls) then
begin
for n := 0 to FControls.Count - 1 do
if TObject(FControls[n]) is TWinControl then
TWinControl(FControls[n]).UpdateShowing;
end;
end;
if not HandleAllocated then
begin
{$IFDEF VerboseShowing}
if bShow then
DebugLn(['TWinControl.UpdateShowing ',DbgSName(Self),' handle not allocated']);
{$ENDIF}
Exit;
end;
if FShowing = bShow then Exit;
//DebugLn(['TWinControl.UpdateShowing ',dbgsName(Self),' FShowing=',dbgs(FShowing),' bShow=',dbgs(bShow), ' IsWindowVisible=', IsWindowVisible(FHandle)]);
if bShow then
begin
// the Handle should become visible
// delay this until all other autosizing has been processed
if AutoSizeDelayed or (not (caspShowing in AutoSizePhases)) then
begin
{$IFDEF VerboseShowing}
if AutoSizeDelayed then DebugLn(['TWinControl.UpdateShowing ',DbgSName(Self),' SKIPPING because AutoSizeDelayed: ',AutoSizeDelayedReport]);
if (not (caspShowing in AutoSizePhases)) then DebugLn(['TWinControl.UpdateShowing ',DbgSName(Self),' SKIPPING because wrong phase']);
{$ENDIF}
exit;
end;
end;
ChangeShowing(bShow);
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;
var
NewFocus: TWinControl;
ParentForm: TCustomForm;
begin
Result := True;
ParentForm := GetParentForm(Self);
if ParentForm = nil then
Exit;
NewFocus := ParentForm.FindNextControl(Self, ForwardTab, True, False);
if NewFocus = nil then
Exit;
NewFocus.SetFocus;
Result := NewFocus.Focused;
end;
{------------------------------------------------------------------------------
TWinControl SelectNext
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
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;
Result := nil;
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;
procedure TWinControl.ScaleBy(Multiplier, Divider: Integer);
begin
ChangeScale(Multiplier, Divider);
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
// debugln(['TWinControl.FindNextControl TabOrderList ',dbgs(i),' ',DbgSName(TObject(List[i]))]);
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;
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;
// if we reached the start then exit because we traversed the loop and
// did not find any control
if I = J then
break;
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
I, J: Integer;
Control: TWinControl;
List: TFPList;
WinControls: TFPList;
begin
if FControls <> nil then
begin
List := TFPList.Create;
WinControls:=TFPList.Create;
try
for i:=0 to FControls.Count-1 do
if TObject(FControls[i]) is TWinControl then
WinControls.Add(FControls[i]);
List.Count := WinControls.Count;
for I := 0 to WinControls.Count - 1 do
begin
Control := TWinControl(WinControls[I]);
J := Control.FTabOrder;
if (J >= 0) and (J < WinControls.Count) then
List[J] := Control;
end;
for I := 0 to List.Count - 1 do
begin
Control := TWinControl(List[I]);
if Control <> nil then
Control.UpdateTabOrder(TTabOrder(I));
end;
finally
List.Free;
WinControls.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]);
// The tab order list should exclude injected LCL-CustomDrawn controls
if lWinControl.CanFocus and (not LCLIntf.IsCDIntfControl(lWinControl)) then
List.Add(lWinControl);
lWinControl.GetTabOrderList(List);
end;
end;
{------------------------------------------------------------------------------
TWinControl IsControlMouseMsg
------------------------------------------------------------------------------}
function TWinControl.IsControlMouseMsg(var TheMessage): Boolean;
var
MouseMessage: TLMMouse absolute TheMessage;
MouseEventMessage: TLMMouseEvent;
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 children, in case they overlap
Control := ControlAtPos(SmallPointToPoint(MouseMessage.Pos),
[capfAllowWinControls]);
if Control is TWinControl then
begin
// there is a TWinControl child at this position
// TWinControl children get their own messages
// => ignore here
Control := nil;
end;
end;
//DebugLn(['TWinControl.IsControlMouseMsg B ',DbgSName(Self),' Control=',DbgSName(Control),' Msg=',TheMessage.Msg]);
Result := False;
if Control <> nil then
begin
// map mouse coordinates to control
ScrolledOffset := GetClientScrollOffset;
P.X := MouseMessage.XPos - Control.Left + ScrolledOffset.X;
P.Y := MouseMessage.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=',MouseMessage.Pos.X,',',MouseMessage.Pos.Y,
' Control=',Control.Left,',',Control.Top,
' ClientBounds=',ClientBounds.Left,',',ClientBounds.Top,
' Scrolled=',GetClientScrollOffset.X,',',GetClientScrollOffset.Y,
' P=',P.X,',',P.Y]
);
{$ENDIF}
end;
if MouseMessage.Msg = LM_MOUSEWHEEL then
begin
MouseEventMessage := TLMMouseEvent(TheMessage);
MouseEventMessage.X := P.X;
MouseEventMessage.Y := P.Y;
Control.Dispatch(MouseEventMessage);
MouseMessage.Result := MouseEventMessage.Result;
Result := (MouseMessage.Result <> 0);
end
else
begin
MouseMessage.Result := Control.Perform(MouseMessage.Msg, WParam(MouseMessage.Keys),
LParam(Integer(PointToSmallPoint(P))));
Result := True;
end;
end;
end;
procedure TWinControl.FontChanged(Sender: TObject);
begin
if HandleAllocated and ([csLoading, csDestroying] * ComponentState = []) then
begin
TWSWinControlClass(WidgetSetClass).SetFont(Self, Font);
Exclude(FWinControlFlags, wcfFontChanged);
end
else
Include(FWinControlFlags, wcfFontChanged);
inherited FontChanged(Sender);
NotifyControls(CM_PARENTFONTCHANGED);
end;
procedure TWinControl.SetColor(Value: TColor);
begin
if Value = Color then Exit;
inherited SetColor(Value);
if BrushCreated then
if Color = clDefault then
FBrush.Color := GetDefaultColor(dctBrush)
else
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 VerboseResizeFlicker}
DebugLn('TWinControl.PaintHandler A ',Name,':',ClassName);
{$ENDIF}
{$IFDEF VerboseDsgnPaintMsg}
if csDesigning in ComponentState then
DebugLn('TWinControl.PaintHandler A ',Name,':',ClassName);
{$ENDIF}
//DebugLn(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
//if Name='GroupBox1' then
//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 Name='GroupBox1' then
//debugln(['TWinControl.PaintHandler ControlsNeedsClipping=',ControlsNeedsClipping,' ControlCount=',ControlCount]);
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 ...');
if FDockSite and FUseDockManager and Assigned(DockManager) then
DockManager.PaintSite(DC);
PaintControls(DC, nil);
finally
if TheMessage.DC = 0 then
EndPaint(Handle, PS);
end;
//DebugLn(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
{$ifdef DEBUG_WINDOW_ORG}
DebugLn(':> [TWinControl.PaintControls] A');
{$endif}
//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
{$ifdef DEBUG_WINDOW_ORG}
DebugLn(':> [TWinControl.PaintControls] B');
{$endif}
I := 0;
if First <> nil then
begin
I := FControls.IndexOf(First);
if I < 0 then I := 0;
end;
//debugln(['TWinControl.PaintControls ',DbgSName(Self),' ClientRect=',dbgs(ClientRect)]);
Count := FControls.Count;
while I < Count do
begin
TempControl := TControl(FControls.Items[I]);
{$ifdef DEBUG_WINDOW_ORG}
if Name='GroupBox1' then
DebugLn(
Format(':> [TWinControl.PaintControls] C DC=%d TempControl=%s Left=%d Top=%d Width=%d Height=%d IsVisible=%s RectVisible=%s',
[DC, DbgSName(TempControl),
TempControl.Left, TempControl.Top, TempControl.Width, TempControl.Height,
dbgs(IsVisible),
dbgs(RectVisible(DC, TempControl.BoundsRect))
]));
{$endif}
if not (TempControl is TWinControl) then begin
//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, TempControl.BoundsRect) then
begin
if csPaintCopy in Self.ControlState then
Include(FControlState, csPaintCopy);
SaveIndex := SaveDC(DC);
{$ifdef DEBUG_WINDOW_ORG}
DebugLn(
Format(':> [TWinControl.PaintControls] Control=%s Left=%d Top=%d Width=%d Height=%d',
[Self.Name, Left, Top, Width, Height]));
{$endif}
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;
end;
Inc(I);
end;
end;
//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));
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 BrushCreated then exit;
FBrush := TBrush.Create;
if Color = clDefault then
FBrush.Color := GetDefaultColor(dctBrush)
else
FBrush.Color := Color;
end;
procedure TWinControl.ScaleControls(Multiplier, Divider: Integer);
var
i: Integer;
begin
for i := 0 to ControlCount - 1 do
Controls[i].ChangeScale(Multiplier, Divider);
end;
procedure TWinControl.ChangeScale(Multiplier, Divider: Integer);
var
i: Integer;
begin
if Multiplier <> Divider then
begin
DisableAlign;
try
ScaleControls(Multiplier, Divider);
inherited;
for i := 0 to ControlCount - 1 do
Controls[i].UpdateAnchorRules;
finally
EnableAlign;
end;
end;
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, HBRUSH(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
IncLCLRefCount;
try
Result := (RepeatCount > 0) and not SystemKey and DoUTF8KeyPress(UTF8Key);
finally
DecLCLRefCount;
end;
end;
function TWinControl.IntfGetDropFilesTarget: TWinControl;
begin
Result:=Self;
repeat
Result:=GetFirstParentForm(Result);
if Result=nil then exit;
if TCustomForm(Result).AllowDropFiles then exit;
Result:=Result.Parent;
until Result=nil;
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);
ACanvas.Changed;
end;
procedure TWinControl.SetShape(AShape: TBitmap);
begin
if not HandleAllocated then
Exit;
if (AShape <> nil) and (AShape.Width = Width) and (AShape.Height = Height) then
TWSWinControlClass(WidgetSetClass).SetShape(Self, AShape.Handle)
else
if AShape = nil then
TWSWinControlClass(WidgetSetClass).SetShape(Self, 0)
end;
procedure TWinControl.SetShape(AShape: TRegion);
begin
LCLIntf.SetWindowRgn(Handle, AShape.Reference.Handle, True);
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 := GetChildrenRect(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)]);
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;
if FControls<>nil then
begin
// check wincontrols
if (capfAllowWinControls in Flags) then
for I := FControls.Count - 1 downto 0 do
if (TObject(FControls[i]) is TWinControl)
and GetControlAtPos(TControl(FControls[I])) then
Break;
// check controls
if (LControl = nil) then
for I := FControls.Count - 1 downto 0 do
if (not (TObject(FControls[i]) is TWinControl))
and GetControlAtPos(TControl(FControls[I])) then
Break;
end;
Result := LControl;
// check recursive sub children
if (capfRecursive in Flags) and (Result is TWinControl) and
(TWinControl(Result).ControlCount > 0) then
begin
// in LCL ClientOrigin contains the scroll offset. At least this is so
// for win32 and gtk2
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
Result := FControls.IndexOf(AControl)
else
Result := -1;
end;
{-------------------------------------------------------------------------------
function TWinControl.GetControlIndex(AControl: TControl): integer;
-------------------------------------------------------------------------------}
procedure TWinControl.SetControlIndex(AControl: TControl; NewIndex: integer);
begin
SetChildZPosition(AControl, NewIndex);
end;
{------------------------------------------------------------------------------
TWinControl DestroyHandle
------------------------------------------------------------------------------}
procedure TWinControl.DestroyHandle;
var
i: integer;
AWinControl: TWinControl;
begin
//DebugLn(['TWinControl.DestroyHandle START ',DbgSName(Self)]);
if not HandleAllocated then begin
DebugLn('Warning: TWinControl.DestroyHandle ',Name,':',ClassName,' Handle not Allocated');
//RaiseGDBException('');
end;
// First destroy all children handles
//DebugLn(['TWinControl.DestroyHandle DESTROY CHILDS ',DbgSName(Self)]);
Include(FControlState, csDestroyingHandle);
try
if FControls <> nil then begin
for i:= 0 to FControls.Count - 1 do begin
//DebugLn([' ',i,' ',DbgSName(TObject(FWinControls[i]))]);
AWinControl:=TWinControl(FControls[i]);
if (AWinControl is TWinControl) and AWinControl.HandleAllocated then
AWinControl.DestroyHandle;
end;
end;
//DebugLn(['TWinControl.DestroyHandle DESTROY SELF ',DbgSName(Self)]);
DestroyWnd;
finally
Exclude(FControlState, csDestroyingHandle);
end;
//DebugLn(['TWinControl.DestroyHandle END ',DbgSName(Self)]);
end;
{------------------------------------------------------------------------------
TWinControl WndPRoc
------------------------------------------------------------------------------}
procedure TWinControl.WndProc(var Message: TLMessage);
var
Form: TCustomForm;
begin
//debugln(['TWinControl.WndProc ',DbgSName(Self),' ',Message.Msg]);
//DebugLn(Format('Trace:[TWinControl.WndPRoc] %s(%s) --> Message = %d', [ClassName, Name, Message.Msg]));
case Message.Msg of
LM_SETFOCUS:
begin
//DebugLn(Format('Trace:[TWinControl.WndPRoc] %s --> LM_SETFOCUS', [ClassName]));
{$IFDEF VerboseFocus}
DebugLn('TWinControl.WndProc LM_SetFocus ',DbgSName(Self));
{$ENDIF}
Form := GetParentForm(Self);
if Assigned(Form) and not (csDestroyingHandle in ControlState) and not (csDestroying in ComponentState) then
begin
if not Form.SetFocusedControl(Self) then
begin
{$IFDEF VerboseFocus}
DebugLn('TWinControl.WndProc LM_SetFocus ',DbgSName(Self),' form=',DbgSName(Form),' Form.SetFocusedControl FAILED');
{$ENDIF}
Exit;
end;
Message.Result := 0;
end;
{$IFDEF VerboseFocus}
DebugLn('TWinControl.WndProc AFTER form LM_SetFocus ',DbgSName(Self));
{$ENDIF}
end;
LM_KILLFOCUS:
begin
//DebugLn(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
Assigned(ControlAtPos(ScreenToClient(SmallPointToPoint(TLMNCHitTest(Message).Pos)), False)) then
Message.Result := HTCLIENT;
Exit;
end;
// exclude only LM_MOUSEENTER, LM_MOUSELEAVE
LM_MOUSEFIRST..LM_MOUSELAST,
LM_MOUSEFIRST2..LM_RBUTTONQUADCLK,
LM_XBUTTONTRIPLECLK..LM_MOUSELAST2:
begin
{$IFDEF VerboseMouseBugfix}
DebugLn('TWinControl.WndPRoc A ',Name,':',ClassName);
{$ENDIF}
//if Message.Msg=LM_RBUTTONUP then begin DebugLn(['TWinControl.WndProc ',DbgSName(Self)]); DumpStack end;
DoBeforeMouseMessage;
if IsControlMouseMSG(Message) then
Exit
else
begin
if FDockSite and FUseDockManager and Assigned(DockManager) then
DockManager.MessageHandler(Self, Message);
end;
{$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);
CM_MOUSEENTER,
CM_MOUSELEAVE:
begin
if FDockSite and FUseDockManager and Assigned(DockManager) then
DockManager.MessageHandler(Self, Message);
end;
CM_TEXTCHANGED, CM_VISIBLECHANGED, LM_SIZE, LM_MOVE:
begin
// forward message to the dock manager is we are docked
if (HostDockSite <> nil) and (HostDockSite.UseDockManager) and
Assigned(HostDockSite.DockManager) then
HostDockSite.DockManager.MessageHandler(Self, Message);
end;
end;
inherited WndProc(Message);
end;
procedure TWinControl.WSSetText(const AText: String);
begin
TWSWinControlClass(WidgetSetClass).SetText(Self, AText);
end;
{------------------------------------------------------------------------------
procedure TWinControl.DoAddDockClient(Client: TControl; const ARect: TRect);
Default method for adding a dock client. Become the new parent and break
old anchored controls.
------------------------------------------------------------------------------}
procedure TWinControl.DoAddDockClient(Client: TControl; const ARect: TRect);
begin
//DebugLn(['TWinControl.DoAddDockClient ',DbgSName(Self),' Client=',DbgSName(Client),' OldParent=',DbgSName(Client.Parent),' Client.AnchoredControlCount=',Client.AnchoredControlCount]);
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
if State = dsDragMove then
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)
{$IFDEF VerboseDocking}
DebugLn(['TWinControl.DoRemoveDockClient ',DbgSName(Self),' ',DbgSName(Client)]);
{$ENDIF}
end;
{------------------------------------------------------------------------------
function TWinControl.DoUnDock(NewTarget: TWinControl; Client: TControl
): Boolean;
------------------------------------------------------------------------------}
function TWinControl.DoUnDock(NewTarget: TWinControl; Client: TControl;
KeepDockSiteSize: Boolean): Boolean;
var
NewBounds: TRect;
begin
{$IFDEF VerboseDocking}
DebugLn('TWinControl.DoUnDock ',Name,' NewTarget=',DbgSName(NewTarget),' Client=',DbgSName(Client));
{$ENDIF}
Result := True;
if Assigned(FOnUnDock) then
begin
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;
Result := Result and DoUndockClientMsg(NewTarget, Client);
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);
const
ADockMargin = 10;
begin
GetWindowRect(Handle, InfluenceRect);
//Margins to test docking (enlarged surface for test)
InfluenceRect.Left := InfluenceRect.Left-ADockMargin;
InfluenceRect.Top := InfluenceRect.Top-ADockMargin;
InfluenceRect.Right := InfluenceRect.Right+ADockMargin;
InfluenceRect.Bottom := InfluenceRect.Bottom+ADockMargin;
if Assigned(FOnGetSiteInfo) then
FOnGetSiteInfo(Self, Client, InfluenceRect, MousePos, CanDock);
end;
{------------------------------------------------------------------------------
function TWinControl.GetParentHandle: HWND;
------------------------------------------------------------------------------}
function TWinControl.GetParentHandle: HWND;
begin
if Parent <> nil then
Result := Parent.Handle
else
Result := ParentWindow;
end;
{------------------------------------------------------------------------------
function TWinControl.GetTopParentHandle: HWND;
------------------------------------------------------------------------------}
function TWinControl.GetTopParentHandle: HWND;
var
AWinControl: TWinControl;
begin
AWinControl := Self;
while AWinControl.Parent <> nil do
AWinControl := AWinControl.Parent;
if AWinControl.ParentWindow = 0 then
Result := AWinControl.Handle
else
Result := AWinControl.ParentWindow;
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 (DockManager = nil) and DockSite and UseDockManager then
// this control can dock other controls, so it needs a TDockManager
Result := DefaultDockManagerClass.Create(Self)
else
Result := DockManager;
end;
procedure TWinControl.SetDockManager(AMgr: TDockManager);
begin
//use FDockManager only here!
if Assigned(DockManager) and (DockManager <> AMgr) then
if FDockManager.AutoFreeByControl then
FDockManager.Free;
FDockManager := AMgr; //can be nil
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=[])
and (DockManager=nil) then
DockManager := CreateDockManager;
end;
procedure TWinControl.DoFloatMsg(ADockSource: TDragDockObject);
var
WasVisible: Boolean;
begin
if FloatingDockSiteClass = ClassType then
begin
WasVisible := Visible;
try
Dock(nil, ADockSource.DockRect);
finally
if WasVisible then BringToFront;
end;
end
else
inherited DoFloatMsg(ADockSource);
end;
function TWinControl.GetDockCaption(AControl: TControl): String;
begin
Result := AControl.GetDefaultDockCaption;
DoGetDockCaption(AControl, Result);
end;
procedure TWinControl.UpdateDockCaption(Exclude: TControl);
begin
{ Called when this is a hostdocksite and either the list of docked clients have
changed or one of their captions.
Exclude an currently undocking control. }
end;
procedure TWinControl.DoGetDockCaption(AControl: TControl; var ACaption: String);
begin
if Assigned(FOnGetDockCaption) then
OnGetDockCaption(Self, AControl, ACaption);
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
//DebugLn(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 KeyDown
------------------------------------------------------------------------------}
procedure TWinControl.KeyDown(var Key: Word; Shift: TShiftState);
begin
if Assigned(FOnKeyDown) then FOnKeyDown(Self, Key, Shift);
if Key <> 0 then
DoCallKeyEventHandler(chtOnKeyDown, 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;
{------------------------------------------------------------------------------
TWinControl DoKeyDownBeforeInterface
returns true if handled
------------------------------------------------------------------------------}
function TWinControl.DoKeyDownBeforeInterface(var Message: TLMKey; IsRecurseCall: Boolean): Boolean;
function IsShortCut: Boolean;
var
AParent: TWinControl;
APopupMenu: TPopupMenu;
begin
Result := False;
// check popup menu
APopupMenu := PopupMenu;
if Assigned(APopupMenu) and APopupMenu.IsShortCut(Message) then
Exit(True);
if IsRecurseCall then
Exit;
// let each parent form handle shortcuts
AParent := Parent;
while Assigned(AParent) do
begin
if (AParent is TCustomForm) and TCustomForm(AParent).IsShortcut(Message) then
Exit(True);
AParent := AParent.Parent;
end;
// let application handle shortcut
if Assigned(Application) and Application.IsShortcut(Message) then
Exit(True);
end;
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);
if not IsRecurseCall then
begin
// let application handle the key
if Assigned(Application) then
begin
Application.NotifyKeyDownBeforeHandler(Self, CharCode, ShiftState);
if CharCode = VK_UNKNOWN then Exit;
end;
// let each parent form with keypreview handle the key
AParent := Parent;
while Assigned(AParent) do
begin
if (AParent is TCustomForm) then
begin
F := TCustomForm(AParent);
if (F.KeyPreview) and (F.DoKeyDownBeforeInterface(Message, True)) then Exit;
end;
AParent := AParent.Parent;
end;
if CharCode = VK_UNKNOWN then Exit;
ShiftState := KeyDataToShiftState(KeyData);
// let drag object handle the key
if DragManager.IsDragging then
begin
DragManager.KeyDown(CharCode, ShiftState);
if CharCode = VK_UNKNOWN then Exit;
end;
end;
// let user handle the key
if not (csNoStdEvents in ControlStyle) then
begin
KeyDownBeforeInterface(CharCode, ShiftState);
if CharCode = VK_UNKNOWN then Exit;
end;
// check the shortcuts
if IsShortCut then Exit;
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
// for Delphi compatibility send it to all controls,
// even those that can not focus or are disabled
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;
begin
Result := True;
ShiftState := KeyDataToShiftState(Message.KeyData);
// let parent(s) handle key from child key
if Assigned(Parent) and 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 Assigned(Application) 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
Result := False;
ParentForm := GetParentForm(Self);
if ParentForm <> nil then
begin
Result := ParentForm.DialogChar(Message);
if Result then
Message.CharCode := VK_UNKNOWN;
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;
Result := False;
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 Assigned(AParent) 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 DragManager.IsDragging then
begin
DragManager.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 or WS_CLIPCHILDREN;
Params.ExStyle := 0;
if csAcceptsControls in ControlStyle then
Params.ExStyle := Params.ExStyle or WS_EX_CONTROLPARENT;
if BorderStyle = bsSingle then
Params.ExStyle := Params.ExStyle or WS_EX_CLIENTEDGE;
if TabStop then
Params.Style := Params.Style or WS_TABSTOP;
if (Parent <> nil) then
Params.WndParent := Parent.Handle
else
Params.WndParent := ParentWindow;
Params.X := Left;
Params.Y := Top;
Params.Width := Width;
Params.Height := Height;
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 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.FParent<>nil then
raise EInvalidOperation.Create('control has already a parent');
if AControl = Self then
raise EInvalidOperation.Create(rsAControlCanNotHaveItselfAsParent);
ListInsert(FControls, Index, AControl);
if AControl is TWinControl then
begin
ListAdd(FTabList, AControl);
if (csDesigning in ComponentState) and (not (csLoading in ComponentState))
and AControl.CanTab then
TWinControl(AControl).TabStop := true;
end;
AControl.FParent := Self;
if AControl.FAutoSizingLockCount>0 then
begin
// the AControl has disabled autosizing => disable it for the parent=self too
//DebugLn([Space(FAutoSizingLockCount*2+2),'TWinControl.Insert ',DbgSName(Self),' Control=',DbgSName(AControl),' disable Parent']);
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TControl.DisableAutoSizing'){$ENDIF};
end;
end;
{------------------------------------------------------------------------------
TWinControl ReAlign
Realign all children
------------------------------------------------------------------------------}
procedure TWinControl.ReAlign;
begin
AdjustSize;
end;
procedure TWinControl.ScrollBy_WS(DeltaX, DeltaY: Integer);
begin
if HandleAllocated then
TWSWinControlClass(WidgetSetClass).ScrollBy(Self, DeltaX, DeltaY)
else
raise Exception.Create('TWinControl.ScrollBy_WS: Handle not allocated');
end;
procedure TWinControl.ScrollBy(DeltaX, DeltaY: Integer);
var
i: Integer;
begin
// scroll inner controls
DisableAutoSizing;
try
for i := 0 to ControlCount - 1 do
with Controls[i] do
SetBounds(Left + DeltaX, Top + DeltaY, Width, Height);
finally
EnableAutoSizing;
end;
end;
{------------------------------------------------------------------------------
TWinControl Remove
------------------------------------------------------------------------------}
procedure TWinControl.Remove(AControl : TControl);
begin
if AControl <> nil then
begin
//DebugLn(Format('trace:[TWinControl.Remove] %s(%S) --> Remove: %s(%s)', [ClassName, Name, AControl.ClassName, AControl.Name]));
if AControl is TWinControl then
ListRemove(FTabList, AControl);
ListRemove(FControls, AControl);
ListRemove(FAlignOrder, AControl);
AControl.FParent := nil;
if AControl.FAutoSizingLockCount>0 then
begin
// AControl has disabled autosizing and thus for its parent=Self too
// end disable autosize for parent=self
//DebugLn([Space(FAutoSizingLockCount*2),'TWinControl.Remove ',DbgSName(Self),' Control=',DbgSName(AControl),' enable Parent']);
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TControl.DisableAutoSizing'){$ENDIF};
end;
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,BiDiMode,
r.Right,r.Bottom,true);
finally
Box.Free;
end;
end;
class procedure TWinControl.WSRegisterClass;
begin
inherited WSRegisterClass;
RegisterWinControl;
RegisterPropertyToSkip(TWinControl, 'ParentDoubleBuffered', 'VCL compatibility property', '');
RegisterPropertyToSkip(TWinControl, 'ImeMode', 'VCL compatibility property', '');
RegisterPropertyToSkip(TWinControl, 'ImeName', 'VCL compatibility property', '');
end;
function TWinControl.IsClientHeightStored: boolean;
begin
// The ClientHeight is needed to restore children anchored akBottom
Result:=ControlCount>0;
end;
function TWinControl.IsClientWidthStored: boolean;
begin
// The ClientWidth is needed to restore children 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
Called by: RecreateWnd, TCustomTabControl.ShowCurrentPage,
TWinControl.SetParentWindow, TWinControl.InsertControl,
TWinControl.CMVisibleChanged
------------------------------------------------------------------------------}
procedure TWinControl.UpdateControlState;
begin
if HandleObjectShouldBeVisible then
AdjustSize // this will trigger DoAllAutoSize, which calls UpdateShowing
else
UpdateShowing; // hide immediately
end;
{------------------------------------------------------------------------------
TWinControl InsertControl
------------------------------------------------------------------------------}
procedure TWinControl.InsertControl(AControl: TControl);
begin
InsertControl(AControl, ControlCount);
end;
procedure TWinControl.InsertControl(AControl: TControl; Index: integer);
begin
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.InsertControl'){$ENDIF};
try
AControl.ValidateContainer(Self);
Perform(CM_CONTROLLISTCHANGE, WParam(AControl), LParam(True));
Insert(AControl,Index);
AControl.UpdateAlignIndex;
if not (csReading in AControl.ComponentState) then
begin
AControl.Perform(CM_PARENTCOLORCHANGED, 0, 0);
AControl.Perform(CM_PARENTSHOWHINTCHANGED, 0, 0);
AControl.Perform(CM_PARENTBIDIMODECHANGED, 0, 0);
AControl.Perform(CM_PARENTFONTCHANGED, 0, 0);
AControl.UpdateBaseBounds(false,true,false);
if AControl is TWinControl then
TWinControl(AControl).UpdateControlState
else
if HandleAllocated then
AControl.Invalidate;
//DebugLn('TWinControl.InsertControl ',Name,':',ClassName);
end;
AdjustSize;
Perform(CM_CONTROLCHANGE, WParam(AControl), LParam(True));
finally
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.InsertControl'){$ENDIF};
end;
//debugln(['TWinControl.InsertControl ',DbgSName(Self),' ',csDesigning in ComponentState,' ',DbgSName(AControl),' ',csDesigning in AControl.ComponentState]);
end;
{------------------------------------------------------------------------------
TWinControl removeControl
------------------------------------------------------------------------------}
procedure TWinControl.RemoveControl(AControl: TControl);
var
AWinControl: TWinControl;
AGrControl: TGraphicControl;
begin
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.RemoveControl'){$ENDIF};
try
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
begin
if AControl is TGraphicControl then
begin
AGrControl := TGraphicControl(AControl);
if (AGrControl.Canvas<>nil) then
TControlCanvas(AGrControl.Canvas).FreeHandle;
end;
if HandleAllocated then
AControl.InvalidateControl(AControl.IsVisible, False, True);
end;
Remove(AControl);
Perform(CM_CONTROLLISTCHANGE, WParam(AControl), LParam(False));
if not (csDestroying in ComponentState) then
begin
InvalidatePreferredSize;
AdjustSize;
end;
finally
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.RemoveControl'){$ENDIF};
end;
end;
function TWinControl.GetEnumeratorControls: TWinControlEnumerator;
begin
Result:=TWinControlEnumerator.Create(Self,true);
end;
function TWinControl.GetEnumeratorControlsReverse: TWinControlEnumerator;
begin
Result:=TWinControlEnumerator.Create(Self,false);
end;
{------------------------------------------------------------------------------
TWinControl AlignControl
------------------------------------------------------------------------------}
procedure TWinControl.AlignControl(AControl: TControl);
var
ARect: TRect;
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;
// only called by DoAllAutoSize, so no check needed
DisableAlign;
try
// store
ARect := GetClientRect;
AdjustClientRect(ARect);
FAdjustClientRectRealized:=ARect;
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
EnableAlign;
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 not BrushCreated then
CreateBrush;
Result := FBrush;
end;
{------------------------------------------------------------------------------
TWinControl GetControl
------------------------------------------------------------------------------}
function TWinControl.GetControl(const Index: Integer): TControl;
begin
Result := TControl(FControls[Index]);
end;
{------------------------------------------------------------------------------
TWinControl GetControlCount
------------------------------------------------------------------------------}
function TWinControl.GetControlCount: Integer;
begin
if FControls <> nil then
Result := FControls.Count
else
Result := 0;
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
Constructor 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.
FTabOrder := -1;
FTabStop := False;
InvalidateClientRectCache(false);
end;
{------------------------------------------------------------------------------
TWinControl CreateParented
------------------------------------------------------------------------------}
constructor TWinControl.CreateParented(AParentWindow: HWND);
begin
FParentWindow := AParentWindow;
Create(nil);
end;
{------------------------------------------------------------------------------
TWinControl CreateParentedControl
------------------------------------------------------------------------------}
class function TWinControl.CreateParentedControl(AParentWindow: HWND
): TWinControl;
begin
Result := CreateParented(AParentWindow);
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); // this sets Control.Parent to nil
//DebugLn(['TWinControl.Destroy ',DbgSName(Control.HostDockSite)]);
if Control.HostDockSite = Self then
Control.HostDockSite := nil;
// don't free the control, controls are freed by the owner
n := ControlCount;
end;
// undock controls that use this as HostDockSite
while DockClientCount>0 do begin
Control:=DockClients[DockClientCount-1];
//DebugLn(['TWinControl.Destroy ',DbgSName(Self),' undocking ',DbgSName(Control)]);
Control.HostDockSite:=nil;
end;
FreeAndNil(FAlignOrder);
FreeThenNil(FBrush);
FreeThenNil(FChildSizing);
if (FDockManager<>nil) then
if FDockManager.AutoFreeByControl then
FreeThenNil(FDockManager)
else
FDockManager:=nil;
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;
DisableAlign;
for i:=0 to ControlCount-1 do begin
CurControl:=Controls[i];
CurControl.Left:=AWidth-CurControl.Left-CurControl.Width;
CurControl.Anchors := BidiFlipAnchors(CurControl, True);
end;
EnableAlign;
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;
inherited;
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 CMBiDiModeChanged(Message);
NotifyControls(CM_PARENTBIDIMODECHANGED);
if HandleAllocated and (Message.wParam = 0) then
TWSWinControlClass(WidgetSetClass).SetBiDiMode(Self,
UseRightToLeftAlignment, UseRightToLeftReading, UseRightToLeftScrollBar);
AdjustSize;
end;
procedure TWinControl.CMBorderChanged(var Message: TLMessage);
begin
DoAdjustClientRectChange;
AdjustSize;
Invalidate;
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMSetFocus
Params: Message
Returns: Nothing
SetFocus event handler
------------------------------------------------------------------------------}
procedure TWinControl.WMSetFocus(var Message: TLMSetFocus);
begin
{$IFDEF VerboseFocus}
DebugLn('TWinControl.WMSetFocus A ',Name,':',ClassName);
{$ENDIF}
end;
{------------------------------------------------------------------------------
Method: TWinControl.LMKillFocus
Params: Msg: The message
Returns: nothing
event handler.
------------------------------------------------------------------------------}
procedure TWinControl.WMKillFocus(var Message: TLMKillFocus);
var
ParentForm: TCustomForm;
begin
//DebugLn('TWinControl.WMKillFocus A ',Name,':',ClassName);
//DebugLn(Format('Trace: %s', [ClassName]));
if [csLoading,csDestroying,csDesigning]*ComponentState=[] then
begin
ParentForm := GetParentForm(Self);
if Assigned(ParentForm) and ParentForm.Active then
EditingDone;
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] ',DbgSName(Self),' ',DbgS(Msg.DC));
{$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}
//if Name='GroupBox1' then
// debugln(['TWinControl.WMPaint ',DbgSName(Self),' DoubleBuffered=',DoubleBuffered,' Msg.DC=',dbgs(Msg.DC),' csCustomPaint=',csCustomPaint in ControlState,' ControlCount=',ControlCount,' ClientRect=',dbgs(ClientRect)]);
if (Msg.DC <> 0) or not DoubleBuffered 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;
//DebugLn(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
//DebugLn(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;
NewBoundsRealized: TRect;
TopParent: TControl;
procedure RaiseLoop;
begin
raise Exception.Create('TWinControl.WMMove loop detected: '+DbgSName(Self)+' BoundsRealized='+dbgs(FBoundsRealized)+' NewBoundsRealized='+dbgs(NewBoundsRealized));
end;
begin
{$IF defined (VerboseSizeMsg) or defined(VerboseIntfSizing)}
if (Message.MoveType and Move_SourceIsInterface)>0 then
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 and Move_SourceIsInterface)>0 then
begin
if not (wcfBoundsRealized in FWinControlFlags) then exit;
// interface widget has moved
// -> update size and realized bounds
NewWidth := FBoundsRealized.Right - FBoundsRealized.Left;
NewHeight := FBoundsRealized.Bottom - FBoundsRealized.Top;
// skip size update when window is minimized
if HandleAllocated and (not IsIconic(Handle)) then
GetWindowSize(Handle, NewWidth, NewHeight);
NewBoundsRealized:=Bounds(Message.XPos, Message.YPos, NewWidth, NewHeight);
if CompareRect(@NewBoundsRealized,@FBoundsRealized) then exit;
TopParent:=GetTopParent;
if (TopParent is TWinControl) and (wcfKillIntfSetBounds in TWinControl(TopParent).FWinControlFlags) then
RaiseLoop;
FBoundsRealized := NewBoundsRealized;
if ([caspCreatingHandles,caspComputingBounds]*AutoSizePhases<>[]) then
begin
// while the LCL is creating handles the widgetset may send default bounds
// we have not yet told the widgetset the final bounds
// => the InvalidatePreferredSize and the InvalidateClientRectCache
// (invoked by the widgetset) may trigger a further loop in the auto
// size algorithm to take care of the new bounds
// => do not call SetBounds, as this will set the Bounds to the widgetset
// default values.
//DebugLn(['TWinControl.WMMove from intf ignored, because phases=',dbgs(AutoSizePhases),' boundsrealized=',wcfBoundsRealized in FWinControlFlags]);
exit;
end;
end;
if Assigned(Parent) then
SetBoundsKeepBase(Message.XPos, Message.YPos, NewWidth, NewHeight)
else
SetBounds(Message.XPos, Message.YPos, NewWidth, NewHeight);
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;
TopParent: TControl;
OldClientSize: TSize;
NewClientSize: TSize;
procedure RaiseLoop;
var
s: String;
begin
s:='TWinControl.WMSize loop detected, the widgetset does not like the LCL bounds or sends unneeded wmsize messages: '+DbgSName(Self)+' BoundsRealized='+dbgs(FBoundsRealized)+' NewBoundsRealized='+dbgs(NewBoundsRealized);
if (OldClientSize.cx<>NewClientSize.cx)
or (OldClientSize.cy<>NewClientSize.cy)
then
s:=s+' OldClientSize='+dbgs(OldClientSize)+' NewClientSize='+dbgs(NewClientSize);
raise Exception.Create(s);
end;
begin
{$IF defined(VerboseSizeMsg) or defined(CHECK_POSITION) or defined(VerboseIntfSizing)}
{$IFDEF CHECK_POSITION}
if CheckPosition(Self) then
{$ENDIF}
if (Message.SizeType and Size_SourceIsInterface) > 0 then
DebugLn(['TWinControl.WMSize A ',Name,':',ClassName,' Message=',Message.Width,',',Message.Height,
' BoundsRealized=',dbgs(FBoundsRealized),
' WChg=',FBoundsRealized.Right-FBoundsRealized.Left<>Message.Width,
' HChg=',FBoundsRealized.Bottom-FBoundsRealized.Top<>Message.Height,
' FromIntf=',(Message.SizeType and Size_SourceIsInterface)>0,' ClientRectInvalid=',ClientRectNeedsInterfaceUpdate]);
{$ENDIF}
NewLeft := Left;
NewTop := Top;
if ((Message.SizeType and Size_SourceIsInterface) > 0) then
begin
// interface widget has resized
// -> update realized position and realized bounds
{$IFDEF VerboseAllAutoSize}
DebugLn(['TWinControl.WMSize FromIntf ',dbgsname(Self),' Message=',Message.Width,',',Message.Height,
' BoundsRealized=',dbgs(FBoundsRealized),
' wcfClientRectNeedsUpdate=',wcfClientRectNeedsUpdate in FWinControlFlags]);
{$ENDIF}
if not (wcfBoundsRealized in FWinControlFlags) then exit;
{$IFDEF VerboseClientRectBugFix}
//if Name=CheckClientRectName then
DebugLn(['TWinControl.WMSize FromIntf ',dbgsname(Self),' Message=',Message.Width,',',Message.Height,
' BoundsRealized=',dbgs(FBoundsRealized),
' wcfClientRectNeedsUpdate=',wcfClientRectNeedsUpdate in FWinControlFlags]);
{$ENDIF}
NewLeft := FBoundsRealized.Left;
NewTop := FBoundsRealized.Top;
if HandleAllocated then
GetWindowRelativePosition(Handle, NewLeft, NewTop);
//if CheckPosition(Self) then
//DebugLn(['TWinControl.WMSize GetWindowRelativePosition: ',DbgSName(Self),' ',NewLeft,',',NewTop,' ClientRectNeedsInterfaceUpdate=',ClientRectNeedsInterfaceUpdate]);
NewBoundsRealized := Bounds(NewLeft, NewTop, Message.Width, Message.Height);
OldClientSize := Size(0, 0);
NewClientSize := Size(0, 0);
if CompareRect(@NewBoundsRealized, @FBoundsRealized) then
begin
if not (wcfClientRectNeedsUpdate in FWinControlFlags) then
begin
OldClientSize := Size(FClientWidth, FClientHeight);
NewClientSize := Size(ClientWidth, ClientHeight);
if (OldClientSize.cx = NewClientSize.cx) and
(OldClientSize.cy = NewClientSize.cy) then
Exit;
end;
end;
{$IFDEF VerboseAllAutoSize}
{$IFDEF CHECK_POSITION}
if CheckPosition(Self) then
{$ENDIF}
DebugLn(['TWinControl.WMSize Changed From Intf ',dbgsname(Self),' Message=',Message.Width,',',Message.Height,
' BoundsRealized=',dbgs(FBoundsRealized),
' wcfClientRectNeedsUpdate=',wcfClientRectNeedsUpdate in FWinControlFlags,
' ClientRectNeedsInterfaceUpdate=',ClientRectNeedsInterfaceUpdate]);
{$ENDIF}
TopParent := GetTopParent;
if (TopParent is TWinControl) and (wcfKillIntfSetBounds in TWinControl(TopParent).FWinControlFlags) then
RaiseLoop;
FBoundsRealized := NewBoundsRealized;
//DebugLn(['TWinControl.WMSize ',DbgSName(Self),' phases=',dbgs(AutoSizePhases)]);
if ([caspCreatingHandles, caspComputingBounds] * AutoSizePhases <> []) then
begin
// while the LCL is creating handles the widgetset may send default bounds
// we have not yet told the widgetset the final bounds
// => the InvalidatePreferredSize and the InvalidateClientRectCache
// (invoked by the widgetset) may trigger a further loop in the auto
// size algorithm to take care of the new bounds
// => do not call SetBounds, as this will set the Bounds to the widgetset
// default values.
{$IFDEF CHECK_POSITION}
if CheckPosition(Self) then
{$ENDIF}
// DebugLn(['TWinControl.WMSize from intf ignored, because phases=',dbgs(AutoSizePhases),' boundsrealized=',wcfBoundsRealized in FWinControlFlags]);
Exit;
end;
if Assigned(Parent) then
InvalidatePreferredSize;
end;
if Assigned(Parent) then
SetBoundsKeepBase(NewLeft, NewTop, Message.Width, Message.Height)
else
SetBounds(NewLeft, NewTop, Message.Width, Message.Height);
//if CheckPosition(Self) then
//debugln(['TWinControl.WMSize ',DbgSName(Self),' ClientRectNeedsInterfaceUpdate=',ClientRectNeedsInterfaceUpdate]);
if ((Message.SizeType and Size_SourceIsInterface) > 0) and ClientRectNeedsInterfaceUpdate then
DoAdjustClientRectChange;
{$IFDEF VerboseClientRectBugFix}
{$IFDEF CHECK_POSITION}
if CheckPosition(Self) then
{$ENDIF}
if ((Message.SizeType and Size_SourceIsInterface) > 0) then
DebugLn(['TWinControl.WMSize END ',dbgsname(Self),' Message=',Message.Width,',',Message.Height,
' BoundsRealized=',dbgs(FBoundsRealized),' ClientRect=',dbgs(ClientRect),
' ']);
{$ENDIF}
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMWindowPosChanged
Params: Message: TLMWindowPosChanged
Returns: nothing
Event handler for size/move messages. This is called, whenever left, top,
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 SetBounds back to the interface.
------------------------------------------------------------------------------}
procedure TWinControl.WMWindowPosChanged(var Message: TLMWindowPosChanged);
var
NewLeft, NewTop, NewWidth, NewHeight: integer;
NewBoundsRealized: TRect;
TopParent: TControl;
procedure RaiseLoop;
begin
raise Exception.Create('TWinControl.WMWindowPosChanged loop detected: '+DbgSName(Self)+' BoundsRealized='+dbgs(FBoundsRealized)+' NewBoundsRealized='+dbgs(NewBoundsRealized));
end;
begin
if not Assigned(Message.WindowPos) or
((Message.WindowPos^.flags and SWP_SourceIsInterface) = 0) then
begin
inherited WMWindowPosChanged(Message);
Exit;
end;
{$IFDEF VerboseAllAutoSize}
DebugLn(DbgSName(Self) + ' : ' + DbgSWindowPosFlags(Message.WindowPos^.flags));
{$ENDIF}
NewLeft := Message.WindowPos^.x;
NewTop := Message.WindowPos^.y;
NewWidth := Message.WindowPos^.cx;
NewHeight := Message.WindowPos^.cy;
{$IF defined(VerboseSizeMsg) or defined(CHECK_POSITION) or defined(VerboseIntfSizing)}
{$IFDEF CHECK_POSITION}
if CheckPosition(Self) then
{$ENDIF}
DebugLn(['TWinControl.WMWindowPosChanged START ',DbgSName(Self),' Message=',NewLeft,',',NewTop,',',NewWidth,'x',NewHeight,
' BoundsRealized=',dbgs(FBoundsRealized),' FromIntf=',(Message.WindowPos^.flags and SWP_SourceIsInterface)>0,' ClientRectInvalid=',ClientRectNeedsInterfaceUpdate]);
{$ENDIF}
// interface widget has moved/resized
// -> update realized bounds
{$IFDEF VerboseAllAutoSize}
DebugLn(['TWinControl.WMWindowPosChanged FROM INTF ',dbgsname(Self),' Message=',NewLeft,',',NewTop,',',NewWidth,'x',NewHeight,
' BoundsRealized=',dbgs(FBoundsRealized),
' wcfClientRectNeedsUpdate=',wcfClientRectNeedsUpdate in FWinControlFlags]);
{$ENDIF}
//DebugLn('TWinControl.WMSize B ',Name,':',ClassName,' ',NewLeft,',',NewTop);
NewBoundsRealized := Bounds(NewLeft, NewTop, NewWidth, NewHeight);
if CompareRect(@NewBoundsRealized,@FBoundsRealized)
and (not (wcfClientRectNeedsUpdate in FWinControlFlags)) then
exit;
{$IFDEF VerboseAllAutoSize}
DebugLn(['TWinControl.WMWindowPosChanged CHANGED BY INTF ',dbgsname(Self),' Message=',NewLeft,',',NewTop,',',NewWidth,'x',NewHeight,
' BoundsRealized=',dbgs(FBoundsRealized),
' wcfClientRectNeedsUpdate=',wcfClientRectNeedsUpdate in FWinControlFlags]);
{$ENDIF}
TopParent:=GetTopParent;
if (TopParent is TWinControl)
and (wcfKillIntfSetBounds in TWinControl(TopParent).FWinControlFlags)
then
RaiseLoop;
FBoundsRealized := NewBoundsRealized;
//DebugLn(['TWinControl.WMSize ',DbgSName(Self),' phases=',dbgs(AutoSizePhases)]);
if ([caspCreatingHandles,caspComputingBounds]*AutoSizePhases<>[])
or (not (wcfBoundsRealized in FWinControlFlags))
then begin
// while the LCL is creating handles the widgetset may send default bounds
// we have not yet told the widgetset the final bounds
// => the InvalidatePreferredSize and the InvalidateClientRectCache
// (invoked by the widgetset) may trigger a further loop in the auto
// size algorithm to take care of the new bounds
// => do not call SetBounds, as this will set the Bounds to the widgetset
// default values.
//DebugLn(['TWinControl.WMSize from intf ignored, because phases=',dbgs(AutoSizePhases),' boundsrealized=',wcfBoundsRealized in FWinControlFlags]);
exit;
end;
if Parent<>nil then
InvalidatePreferredSize;
if Parent<>nil then
SetBoundsKeepBase(NewLeft, NewTop, NewWidth, NewHeight)
else
SetBounds(NewLeft, NewTop, NewWidth, NewHeight);
if ((Message.WindowPos^.flags and SWP_SourceIsInterface) > 0)
and 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, False) 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, False) 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 Widgetset.GetLCLCapability(lcSendsUTF8KeyPress) = LCL_CAPABILITY_NO 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
inherited;
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMShowWindow
Params: Msg: The message
Returns: nothing
event handler.
------------------------------------------------------------------------------}
procedure TWinControl.WMShowWindow(var Message: TLMShowWindow);
begin
// DebugLn(['TWinControl.LMShowWindow ', dbgsName(self)]);
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMEnter
Params: Msg: The message
Returns: nothing
event handler.
------------------------------------------------------------------------------}
procedure TWinControl.WMEnter(var Message: TLMEnter);
begin
//DebugLn(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
begin
EraseBackground(Message.DC);
Message.Result := 1;
end;
end;
{------------------------------------------------------------------------------
Method: TWinControl.WMExit
Params: Msg: The message
Returns: nothing
event handler.
------------------------------------------------------------------------------}
procedure TWinControl.WMExit(var Message: TLMExit);
begin
//DebugLn(Format('Trace: TODO: [TWinControl.LMExit] %s', [ClassName]));
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;
//DebugLn(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;
AWinControl: TWinControl;
{ 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 Assigned(Parent) and (csDestroying in Parent.ComponentState) then
begin
DebugLn('[TWinControl.CreateWnd] NOTE: csDestroying ',DbgSName(Self));
RaiseGDBException('');
exit;
end;
if wcfInitializing in FWinControlFlags then
begin
DebugLn('[WARNING] Recursive call to CreateWnd for ',DbgSName(Self), ' while initializing');
RaiseGDBException('');
Exit;
end;
if wcfCreatingHandle in FWinControlFlags then
begin
DebugLn('[WARNING] Recursive call to CreateWnd for ',DbgSName(Self), ' while creating handle');
RaiseGDBException('');
Exit;
end;
if wcfCreatingChildHandles in FWinControlFlags then
begin
DebugLn('[WARNING] Recursive call to CreateWnd for ',DbgSName(Self), ' while creating children');
RaiseGDBException('');
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;
FBoundsRealized := Rect(0,0,0,0);
Exclude(FWinControlFlags, wcfBoundsRealized);
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.CreateWnd'){$ENDIF};
try
if Assigned(Parent) and not Parent.HandleAllocated then
begin
// first create the parent handle
Parent.HandleNeeded;
if HandleAllocated then exit;
DebugLn(['WARNING: TWinControl.CreateWnd: parent created handles, but not ours']);
end;
// Control is not visible at this moment. It will be shown 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
begin
DebugLn(['TWinControl.CreateWnd ',DbgSName(Self),' Parent=',DbgSName(Parent),' ERROR WndParent=0']);
raise EInvalidOperation.CreateFmt(rsControlHasNoParentWindow, [Name]);
end;
end;
//DebugLn(['TWinControl.CreateWnd Creating handle ... ',DbgSName(WidgetSetClass),' ',DbgSName(Self)]);
FHandle := TWSWinControlClass(WidgetSetClass).CreateHandle(Self, Params);
if not HandleAllocated then
begin
if WidgetSet.LCLPlatform=lpNoGUI then
RaiseGDBException('TWinControl.CreateWnd: The nogui widgetset does not support visual controls.')
else
RaiseGDBException('TWinControl.CreateWnd: Handle creation failed creating '+DbgSName(Self));
end;
//debugln('TWinControl.CreateWnd update constraints ... ',DbgSName(Self));
TWSWinControlClass(WidgetSetClass).SetBiDiMode(Self,
UseRightToLeftAlignment, UseRightToLeftReading, UseRightToLeftScrollBar);
Constraints.UpdateInterfaceConstraints;
InvalidateClientRectCache(False);
TWSWinControlClass(WidgetSetClass).ConstraintsChange(Self);
//WriteClientRect('A');
if Assigned(Parent) then
AddControl
else
if ParentWindow <> 0 then
LCLIntf.SetParent(FHandle, ParentWindow);
//WriteClientRect('B');
Include(FWinControlFlags, wcfInitializing);
//DebugLn(['TWinControl.CreateWnd initializing window ...']);
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 FControls <> nil then
begin
for i := 0 to FControls.Count - 1 do
begin
AWinControl := TWinControl(FControls.Items[i]);
//DebugLn(['TWinControl.CreateWnd create child handles self=',DbgSName(Self),' Child=',DbgSName(AWinControl)]);
if (AWinControl is TWinControl) and AWinControl.IsControlVisible then
AWinControl.HandleNeeded;
end;
end;
ChildHandlesCreated;
finally
Exclude(FWinControlFlags, wcfCreatingChildHandles);
end;
InvalidatePreferredSize;
if Assigned(FControls) then
for i := 0 to FControls.Count - 1 do
TControl(FControls[i]).InvalidatePreferredSize;
// size this control
AdjustSize;
finally
//DebugLn(['TWinControl.CreateWnd created ',DbgSName(Self),' enable autosizing ...']);
(* If an error occured and FHandle was not created,
then EnableAutoSizing must not be called.
EnableAutoSizing will need the Handle, and trigger another attempt to create it.
This leads to an endless loop/recursion.
As a side effect the current control will be left in autosize-disabled *)
if FHandle <> 0 then
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.CreateWnd'){$ENDIF};
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
//DebugLn(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 some interfaces depends on it
SetProp(Handle,'WinControl',TWinControl(Self));
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.InitializeWnd'){$ENDIF};
try
{$IFDEF CHECK_POSITION}
if CheckPosition(Self) then
DebugLn('[TWinControl.InitializeWnd] A ',DbgSName(Self),
' OldRelBounds=',dbgs(FBoundsRealized),
' -> NewBounds=',dbgs(BoundsRect));
{$ENDIF}
if wcfColorChanged in FWinControlFlags then
begin
// replace by update style call
TWSWinControlClass(WidgetSetClass).SetColor(Self);
Exclude(FWinControlFlags, wcfColorChanged);
end;
if wcfFontChanged in FWinControlFlags then
begin
// replace by update style call
TWSWinControlClass(WidgetSetClass).SetFont(Self,Font);
Exclude(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{$IFDEF DebugDisableAutoSizing}('TWinControl.InitializeWnd'){$ENDIF};
end;
// send pending OnResize
{$IFDEF VerboseOnResize}
debugln(['TWinControl.InitializeWnd ',DbgSName(Self),' calling Resize ...']);
{$ENDIF}
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;
// if color has changed make sure it will be restored
if FColor <> {$ifdef UseCLDefault}clDefault{$else}clWindow{$endif} then
Include(FWinControlFlags,wcfColorChanged);
RemoveProp(Handle,'WinControl');
FAdjustClientRectRealized := Rect(0,0,0,0);
end;
{------------------------------------------------------------------------------
procedure TWinControl.ParentFormHandleInitialized;
Called after all children handles of the ParentForm are created.
------------------------------------------------------------------------------}
procedure TWinControl.ParentFormHandleInitialized;
var
i: Integer;
begin
inherited ParentFormHandleInitialized;
// 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));
end;
{------------------------------------------------------------------------------
procedure TWinControl.ChildHandlesCreated;
Called after all children handles are created.
------------------------------------------------------------------------------}
procedure TWinControl.ChildHandlesCreated;
begin
Exclude(FWinControlFlags,wcfCreatingChildHandles);
end;
function TWinControl.GetMouseCapture: Boolean;
begin
Result:=HandleAllocated and (GetCaptureControl=Self);
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: TSize;
CurControl: TWinControl;
begin
//DebugLn(['TWinControl.Loaded START ',DbgSName(Self),' cfWidthLoaded=',cfWidthLoaded in FControlFlags,' cfHeightLoaded=',cfHeightLoaded in FControlFlags,' ']);
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.Loaded'){$ENDIF};
try
//DebugLn(['TWinControl.Loaded ',DbgSName(Self),' cfWidthLoaded=',cfWidthLoaded in FControlFlags,' cfHeightLoaded=',cfHeightLoaded in FControlFlags,' ']);
if cfClientWidthLoaded in FControlFlags then
LoadedClientSize.cx:=FLoadedClientSize.cx
else begin
CurControl:=Self;
while CurControl<>nil do begin
LoadedClientSize.cx:=CurControl.ClientWidth;
if LoadedClientSize.cx>0 then break;
LoadedClientSize.cx:=CurControl.Width;
if LoadedClientSize.cx>0 then break;
CurControl:=CurControl.Parent;
end;
end;
if cfClientHeightLoaded in FControlFlags then
LoadedClientSize.cy:=FLoadedClientSize.cy
else begin
CurControl:=Self;
while CurControl<>nil do begin
LoadedClientSize.cy:=CurControl.ClientHeight;
if LoadedClientSize.cy>0 then break;
LoadedClientSize.cy:=CurControl.Height;
if LoadedClientSize.cy>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);
NotifyControls(CM_PARENTCOLORCHANGED);
Exclude(FWinControlFlags, wcfColorChanged);
end;
if wcfFontChanged in FWinControlFlags then
begin
TWSWinControlClass(WidgetSetClass).SetFont(Self,Font);
NotifyControls(CM_PARENTFONTCHANGED);
FWinControlFlags:=FWinControlFlags-[wcfFontChanged];
end;
end;
inherited Loaded;
FixupTabList;
finally
//DebugLn(['TWinControl.Loaded enableautosizing ',DbgSName(Self),' ',dbgs(BoundsRect)]);
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.Loaded'){$ENDIF};
//DebugLn(['TWinControl.Loaded END ',DbgSName(Self),' ',dbgs(BoundsRect)]);
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;
var
i: integer;
begin
if HandleAllocated then
begin
//DebugLn(['TWinControl.DestroyWnd ',DbgSName(Self)]);
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.DestroyWnd'){$ENDIF};
try
FinalizeWnd;
if FControls <> nil then
for i := 0 to FControls.Count - 1 do
TControl(FControls[i]).DoOnParentHandleDestruction;
TWSWinControlClass(WidgetSetClass).DestroyHandle(Self);
Handle := 0;
Exclude(FWinControlFlags,wcfBoundsRealized);
// Maybe handle is not needed at moment but later it will be created once
// again. To propely initialize control after we need to restore color
// and font. Request update.
FWinControlFlags := FWinControlFlags + [wcfColorChanged, wcfFontChanged];
finally
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.DestroyWnd'){$ENDIF};
end;
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
//DebugLn(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;
CreateHandle;
end;
end;
function TWinControl.BrushCreated: Boolean;
begin
Result := Assigned(FBrush);
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;
begin
if FBoundsLockCount <= 0 then
raise EInvalidOperation.CreateFmt('TWinControl.EndUpdateBounds %s too many calls.', [DbgSName(Self)]);
dec(FBoundsLockCount);
if FBoundsLockCount = 0 then
SetBounds(Left, Top, Width, Height);
end;
procedure TWinControl.LockRealizeBounds;
begin
inc(FRealizeBoundsLockCount);
end;
procedure TWinControl.UnlockRealizeBounds;
begin
if FRealizeBoundsLockCount<=0 then
RaiseGDBException('UnlockRealizeBounds');
dec(FRealizeBoundsLockCount);
if (FRealizeBoundsLockCount=0)
and (not AutoSizeDelayed) and (caspRealizingBounds in AutoSizePhases)
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(DragDockObject: TDragDockObject; X, Y: Integer);
begin
if DoDockClientMsg(DragDockObject, Point(X, Y)) and Assigned(FOnDockDrop) then
FOnDockDrop(Self, DragDockObject, 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);
If NewDockSite=true it means, this control can dock other controls.
------------------------------------------------------------------------------}
procedure TWinControl.SetDockSite(const NewDockSite: Boolean);
begin
if FDockSite=NewDockSite then exit;
FDockSite := NewDockSite;
if not (csDesigning in ComponentState) then begin
DragManager.RegisterDockSite(Self,NewDockSite);
if not NewDockSite then begin
FreeAndNil(FDockClients);
FDockClients := nil;
DockManager := nil;
end
else begin
if FDockClients = nil then FDockClients := TFPList.Create;
DockManager := CreateDockManager;
end;
end;
end;
function TWinControl.DoDockClientMsg(DragDockObject: TDragDockObject;
aPosition: TPoint): boolean;
var
DestRect: TRect;
Form: TCustomForm;
begin
with DragDockObject do begin
DestRect := DockRect;
DisableAlign;
try
{$IFDEF VerboseDocking}
Debugln(['TWinControl.DoDockClientMsg ',DbgSName(Self),' Control=',DbgSName(DragDockObject.Control),' DestRect=',dbgs(DestRect)]);
{$ENDIF}
DragDockObject.Control.Dock(Self, DestRect);
if FUseDockManager and (DockManager <> nil) then
DockManager.InsertControl(DragDockObject);
finally
EnableAlign;
end;
Form := GetParentForm(Self);
if Form <> nil then Form.BringToFront;
Result := true;
end;
end;
function TWinControl.DoUndockClientMsg(NewTarget, Client: TControl): boolean;
begin
Result := True;
{$IFDEF VerboseDocking}
DebugLn(['TWinControl.DoUnDockClientMsg ',DbgSName(Self),' Client=',DbgSName(Client),' Client.Parent=',DbgSName(Client.Parent)]);
{$ENDIF}
if FUseDockManager and (DockManager <> nil) then
DockManager.RemoveControl(Client);
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 EInvalidOperation.CreateFmt('TWinControl.SetBounds (%s): Negative width %d not allowed.', [DbgSName(Self), AWidth]);
if AHeight < 0 then
raise EInvalidOperation.CreateFmt('TWinControl.SetBounds (%s): Negative height %d not allowed.', [DbgSName(Self), AHeight]);
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)
]);
{$ENDIF}
if BoundsLockCount <> 0 then
Exit;
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
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.SetBounds'){$ENDIF};
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);
//DebugLn(['TWinControl.SetBounds ',DbgSName(Self),' FUseDockManager=',FUseDockManager,' ',DbgSName(DockManager)]);
finally
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.SetBounds'){$ENDIF};
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 (exception: csAutoSize0x0).
TWinControl overrides this:
If there are children, their total preferred size is calculated.
If this value can not be computed (e.g. the children 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);
{$IFDEF VerboseCalculatePreferredSize}
procedure trav(aControl: TControl; Prefix: string);
var
w: integer;
h: integer;
i: Integer;
begin
if not aControl.IsVisible then exit;
if aControl<>Self then begin
aControl.GetPreferredSize(w,h,true,true);
debugln([Prefix,'Child ',DbgSName(aControl),' ',dbgs(aControl.BoundsRect),' Pref=',w,'x',h]);
end;
if aControl is TWinControl then
for i:=0 to TWinControl(aControl).ControlCount-1 do
trav(TWinControl(aControl).Controls[i],Prefix+' ');
end;
function IsVerbose: boolean;
begin
Result:=(Name='MainScrollBox');
end;
{$ENDIF}
var
Layout: TAutoSizeCtrlData;
NewClientWidth: Integer;
NewClientHeight: Integer;
CurClientRect: TRect;
NewMoveLeft, NewMoveRight: integer;
FrameWidth: integer;
FrameHeight: integer;
begin
inherited CalculatePreferredSize(PreferredWidth,PreferredHeight,WithThemeSpace);
if HandleAllocated then begin
TWSWinControlClass(WidgetSetClass).GetPreferredSize(Self,
PreferredWidth, PreferredHeight, WithThemeSpace);
{$IFDEF VerboseCalculatePreferredSize}
if IsVerbose then debugln(['TWinControl.CalculatePreferredSize Widget ',DbgSName(Self),' ',DbgSName(WidgetSetClass),' Pref=',PreferredWidth,'x',PreferredHeight]);
{$ENDIF}
end;
if ControlCount>0 then begin
// Beware: ControlCount>0 does not mean that there are visible children
// get the size requirements for the child controls
Layout:=nil;
try
Layout:=TAutoSizeCtrlData.Create(Self);
Layout.ComputePreferredClientArea(false,false,NewMoveLeft,NewMoveRight,
NewClientWidth,NewClientHeight);
//if (Name='OtherInfoGroupBox') or (Name='ProjectVersionInfoOptionsFrame') then
// debugln(['TWinControl.CalculatePreferredSize NewClientWidth=',NewClientWidth,' NewClientHeight=',NewClientHeight]);
if (NewMoveLeft<>0) or (NewMoveRight<>0) then ;
finally
Layout.Free;
end;
// add clientarea frame
GetPreferredSizeClientFrame(FrameWidth,FrameHeight);
{$IF defined(VerboseAutoSize) or defined(VerboseAllAutoSize) or defined(VerboseCalculatePreferredSize)}
{$IFDEF VerboseCalculatePreferredSize}
if IsVerbose then
trav(Self,' ');
if IsVerbose then
{$ENDIF}
//if (Name='OtherInfoGroupBox') or (Name='ProjectVersionInfoOptionsFrame') then
debugln(['TWinControl.CalculatePreferredSize ',DbgSName(Self),
' HandleAllocated=',HandleAllocated,
' Cur=',Width,'x',Height,
' Client=',ClientWidth,'x',ClientHeight,
' PrefClient=',NewClientWidth,'x',NewClientHeight]);
{$ENDIF}
if NewClientWidth>0 then
PreferredWidth:=Max(PreferredWidth,NewClientWidth+FrameWidth);
if NewClientHeight>0 then
PreferredHeight:=Max(PreferredHeight,NewClientHeight+FrameHeight);
end;
// add borderspacing
if (PreferredWidth>0)
or ((PreferredWidth=0) and (csAutoSize0x0 in ControlStyle)) then
inc(PreferredWidth,BorderSpacing.InnerBorder*2);
if (PreferredHeight>0)
or ((PreferredHeight=0) and (csAutoSize0x0 in ControlStyle)) then
inc(PreferredHeight,BorderSpacing.InnerBorder*2);
{$IF defined(VerboseAutoSize) or defined(VerboseCalculatePreferredSize)}
{$IFDEF VerboseCalculatePreferredSize}
if IsVerbose then
{$ENDIF}
debugln(['TWinControl.CalculatePreferredSize ',DbgSName(Self),
' HandleAllocated=',dbgs(HandleAllocated),
' ClientFrame=',FrameWidth,'x',FrameHeight,
' Preferred=',dbgs(PreferredWidth),'x',dbgs(PreferredHeight)]);
{$ENDIF}
end;
procedure TWinControl.GetPreferredSizeClientFrame(out aWidth, aHeight: integer);
begin
aWidth:=Width-ClientWidth;
aHeight:=Height-ClientHeight;
end;
{------------------------------------------------------------------------------
Method: TWinControl.RealGetText
Params: None
Returns: The text
Gets the text/caption of a control
------------------------------------------------------------------------------}
function TWinControl.RealGetText: TCaption;
begin
Result := '';
{$IFDEF VerboseTWinControlRealText}
DebugLn(['TWinControl.RealGetText ',DbgSName(Self),' HandleAllocated=',HandleAllocated,' csLoading=',csLoading in ComponentState,' ']);
if not HandleAllocated
or (csLoading in ComponentState) then begin
DebugLn(['TWinControl.RealGetText using inherited RealGetText']);
Result := inherited RealGetText;
end else begin
DebugLn(['TWinControl.RealGetText using ',DbgSName(WidgetSetClass),' GetText']);
if (not TWSWinControlClass(WidgetSetClass).GetText(Self, Result)) then begin
DebugLn(['TWinControl.RealGetText FAILED, using RealGetText']);
Result := inherited RealGetText;
end;
end;
DebugLn(['TWinControl.RealGetText Result="',Result,'"']);
{$ELSE}
if not HandleAllocated
or (csLoading in ComponentState)
or (not TWSWinControlClass(WidgetSetClass).GetText(Self, Result))
then Result := inherited RealGetText;
{$ENDIF}
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
{$IFDEF VerboseTWinControlRealText}
DebugLn(['TWinControl.RealSetText ',DbgSName(Self),' AValue="',AValue,'" HandleAllocated=',HandleAllocated,' csLoading=',csLoading in ComponentState]);
{$ENDIF}
if HandleAllocated and (not (csLoading in ComponentState)) then
begin
WSSetText(AValue);
InvalidatePreferredSize;
inherited RealSetText(AValue);
AdjustSize;
end
else inherited RealSetText(AValue);
{$IFDEF VerboseTWinControlRealText}
DebugLn(['TWinControl.RealSetText ',DbgSName(Self),' END']);
{$ENDIF}
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 Message : TLMessage);
begin
if not FVisible and Assigned(Parent) then
RemoveFocus(False);
if not (csDesigning in ComponentState) or (csNoDesignVisible in ControlStyle) then
UpdateControlState;
end;
procedure TWinControl.CMEnter(var Message: TLMessage);
begin
DoEnter;
end;
procedure TWinControl.CMExit(var Message: TLMessage);
begin
DoExit;
end;
procedure TWinControl.WMContextMenu(var Message: TLMContextMenu);
var
Child: TControl;
begin
// Check if at the click place we have a control and if so then pass the
// message to it.
// Don't check csDesigning here - let a child control check it.
if (Message.Result <> 0) then
Exit;
if Message.XPos <> -1 then
begin
// don't allow disabled and don't search wincontrols - they receive their
// message themself
Child := ControlAtPos(ScreenToClient(SmallPointToPoint(Message.Pos)), [capfHasScrollOffset]);
if Assigned(Child) then
with Message do
begin
Result := Child.Perform(Msg, WParam(hWnd), LParam(Integer(Pos)));
if (Result <> 0) then
Exit;
end;
end;
inherited;
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);
{$IF defined(VerboseNewAutoSize) or defined(VerboseIntfSizing) or defined(VerboseShowing)}
DebugLn(['TWinControl.DoSendShowHideToInterface ',DbgSName(Self),' FBoundsRealized=',dbgs(FBoundsRealized),' New=',HandleObjectShouldBeVisible]);
{$ENDIF}
TWSWinControlClass(WidgetSetClass).ShowHide(Self);
end;
end;
procedure TWinControl.ControlsAligned;
begin
end;
procedure TWinControl.DoSendBoundsToInterface;
var
NewBounds: TRect;
OldClientRect: TRect;
NewClientRect: TRect;
{$IF defined(VerboseResizeFlicker) or defined(VerboseSizeMsg)}
OldBounds: TRect;
{$ENDIF}
begin
if (Parent = nil) and (not HandleObjectShouldBeVisible) then
begin
{ do not move invisible forms
Reason: It is common to do this:
Form1:=TForm1.Create(nil);
Form1.Top:=100;
Form1.Left:=100;
Form1.Show;
This moves the form around and confuses some windowmanagers.
Only send the last bounds. }
Exit;
end;
NewBounds := Bounds(Left, Top, Width, Height);
{$IF defined(VerboseResizeFlicker) or defined(VerboseSizeMsg)}
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=w',(OldBounds.Right-OldBounds.Left<>NewBounds.Right-NewBounds.Left),
',h', (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),
' ClientRect=',dbgs(ClientRect));
{$ENDIF}
{$IFDEF VerboseClientRectBugFix}
//if Name=CheckClientRectName then
DebugLn('[TWinControl.DoSendBoundsToInterface] A ',DbgSName(Self),
' OldRelBounds=',dbgs(FBoundsRealized),
' -> NewBounds=',dbgs(NewBounds)
//,' Parent.Bounds=',dbgs(Parent.BoundsRect)
//,' Parent.ClientRect=',dbgs(Parent.ClientRect)
);
{$ENDIF}
{$IFDEF VerboseIntfSizing}
if Visible then begin
DebugLn('[TWinControl.DoSendBoundsToInterface] A ',DbgSName(Self),
' OldRelBounds=',dbgs(FBoundsRealized),
' -> NewBounds=',dbgs(NewBounds));
end;
{$ENDIF}
FBoundsRealized := NewBounds;
OldClientRect := ClientRect; // during a resize this is the anticipated new ClientRect
Include(FWinControlFlags, wcfBoundsRealized); // Note: set before calling widgetset, because used in WMSize
//if Parent=nil then DebugLn(['TWinControl.DoSendBoundsToInterface ',DbgSName(Self),' ',dbgs(BoundsRect)]);
// this can trigger WMSize messages
TWSWinControlClass(WidgetSetClass).SetBounds(Self, Left, Top, Width, Height);
NewClientRect := ClientRect;
if Visible and (not CompareRect(@OldClientRect,@NewClientRect)) then
begin
// the widgetset has changed the clientrect in an unexpected way
{$IFDEF VerboseIntfSizing}
debugln(['TWinControl.DoSendBoundsToInterface WS has changed ClientRect in an unexpected way: ',
DbgSName(Self),' Bounds=',dbgs(BoundsRect),' ExpectedClientRect=',dbgs(OldClientRect),' New=',dbgs(NewClientRect)]);
{$ENDIF}
//DebugLn(['TWinControl.DoSendBoundsToInterface ',DbgSName(Self),' Bounds=',dbgs(BoundsRect),' OldClientRect=',dbgs(OldClientRect),' NewClientRect=',dbgs(NewClientRect)]);
AdjustSize;
end;
end;
procedure TWinControl.RealizeBounds;
procedure Check;
var
c: TWinControl;
begin
c:=Self;
while c<>nil do begin
DebugLn(['Check ',DbgSName(c),' ',c.HandleAllocated,
' wcfCreatingHandle=',wcfCreatingHandle in FWinControlFlags,
' wcfInitializing=',wcfInitializing in FWinControlFlags,
' wcfCreatingChildHandles=',wcfCreatingChildHandles in FWinControlFlags,
'']);
c:=c.Parent;
end;
RaiseGDBException('');
end;
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))
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 else begin
{$IFDEF CHECK_POSITION}
if CheckPosition(Self) then begin
DbgOut('[TWinControl.RealizeBounds] NOT REALIZING ',DbgSName(Self),
' OldRelBounds=',dbgs(FBoundsRealized),
' -> NewBounds=',dbgs(NewBounds),
', because ');
if not HandleAllocated then debugln('not HandleAllocated');
if (csLoading in ComponentState) then debugln('csLoading');
if (csDestroying in ComponentState) then debugln('csDestroying');
if (CompareRect(@NewBounds,@FBoundsRealized)) then debugln('bounds not changed');
end;
{$ENDIF}
if not HandleAllocated then Check;
end;
end;
procedure TWinControl.RealizeBoundsRecursive;
var
i: Integer;
OldRealizing: boolean;
AControl: TControl;
begin
if not HandleAllocated then exit;
OldRealizing:=wcfRealizingBounds in FWinControlFlags;
Include(FWinControlFlags,wcfRealizingBounds);
try
if FControls<>nil then begin
for i:=0 to FControls.Count-1 do begin
AControl:=TControl(FControls[i]);
if (AControl is TWinControl) then
TWinControl(AControl).RealizeBoundsRecursive;
end;
end;
RealizeBounds;
finally
if not OldRealizing then
Exclude(FWinControlFlags,wcfRealizingBounds);
end;
end;
{------------------------------------------------------------------------------
Method: TWinControl.CMShowingChanged
Params: Message : not used
Returns: nothing
Shows or hides a control
Called by UpdateShowing
------------------------------------------------------------------------------}
procedure TWinControl.CMShowingChanged(var Message: TLMessage);
begin
{$IFDEF VerboseShowing}
DebugLn(['TWinControl.CMShowingChanged ',DbgSName(Self),' HandleAllocated=',HandleAllocated,' ',dbgs(ComponentState)]);
{$ENDIF}
if HandleAllocated and ([csDestroying,csLoading]*ComponentState=[]) then
DoSendShowHideToInterface
else
Exclude(FWinControlFlags, wcfHandleVisible);
end;
{------------------------------------------------------------------------------
Method: TWinControl.ShowControl
Params: AControl: Control to show
Returns: nothing
Called by a child control (in TControl.Show), before setting Visible=true.
Asks to show the child control and recursively shows the parents.
------------------------------------------------------------------------------}
procedure TWinControl.ShowControl(AControl: TControl);
begin
if Parent <> nil then Parent.ShowControl(Self);
end;
{ TWinControlEnumerator }
function TWinControlEnumerator.GetCurrent: TControl;
begin
if (FIndex>=0) and (FIndex<FParent.ControlCount) then
Result:=FParent.Controls[FIndex]
else
Result:=nil;
end;
constructor TWinControlEnumerator.Create(Parent: TWinControl;
aLowToHigh: boolean);
begin
FParent:=Parent;
FLowToHigh:=aLowToHigh;
if FLowToHigh then
FIndex:=-1
else
FIndex:=FParent.ControlCount;
end;
function TWinControlEnumerator.GetEnumerator: TWinControlEnumerator;
begin
Result:=Self;
end;
function TWinControlEnumerator.MoveNext: Boolean;
begin
if FLowToHigh then
begin
inc(FIndex);
Result:=FIndex<FParent.ControlCount;
end
else begin
dec(FIndex);
Result:=FIndex>=0
end;
end;
{ $UNDEF CHECK_POSITION}
{$IFDEF ASSERT_IS_ON}
{$UNDEF ASSERT_IS_ON}
{$C-}
{$ENDIF}