Files
HeidiSQL/components/virtualtreeview/Source/VirtualTrees.Header.pas

5923 lines
205 KiB
ObjectPascal

unit VirtualTrees.Header;
interface
uses
System.Classes,
System.Types,
System.Generics.Collections,
WinApi.Windows,
WinApi.Messages,
Vcl.Graphics,
Vcl.Menus,
Vcl.ImgList,
Vcl.Controls,
Vcl.Themes,
Vcl.GraphUtil,
System.UITypes,
VirtualTrees.StyleHooks,
VirtualTrees.Utils,
VirtualTrees.Types,
VirtualTrees.DragImage;
{$MINENUMSIZE 1, make enumerations as small as possible}
const
DefaultColumnOptions = [coAllowClick, coDraggable, coEnabled, coParentColor, coParentBidiMode, coResizable,
coShowDropMark, coVisible, coAllowFocus, coEditable, coStyleColor];
type
TVTHeader = class;
TVirtualTreeColumn = class;
// This structure carries all important information about header painting and is used in the advanced header painting.
THeaderPaintInfo = record
TargetCanvas : TCanvas;
Column : TVirtualTreeColumn;
PaintRectangle : TRect;
TextRectangle : TRect;
IsHoverIndex,
IsDownIndex,
IsEnabled,
ShowHeaderGlyph,
ShowSortGlyph,
ShowRightBorder : Boolean;
DropMark : TVTDropMarkMode;
GlyphPos,
SortGlyphPos : TPoint;
SortGlyphSize : TSize;
procedure DrawSortArrow(pDirection : TSortDirection);
procedure DrawDropMark();
end;
TVirtualTreeColumns = class;
TVirtualTreeColumn = class(TCollectionItem)
private
const
cDefaultColumnSpacing = 3;
private
FText,
FHint : string;
FWidth : TDimension;
FPosition : TColumnPosition;
FMinWidth : TDimension;
FMaxWidth : TDimension;
FStyle : TVirtualTreeColumnStyle;
FImageIndex : TImageIndex;
FBiDiMode : TBiDiMode;
FLayout : TVTHeaderColumnLayout;
FMargin,
FSpacing : TDimension;
FOptions : TVTColumnOptions;
FEditOptions : TVTEditOptions;
FEditNextColumn : TDimension;
FTag : NativeInt;
FAlignment : TAlignment;
FCaptionAlignment : TAlignment; // Alignment of the caption.
FLastWidth : TDimension;
FColor : TColor;
FBonusPixel : Boolean;
FSpringRest : Single; // Accumulator for width adjustment when auto spring option is enabled.
FCaptionText : string;
FCheckBox : Boolean;
FCheckType : TCheckType;
FCheckState : TCheckState;
FImageRect : TRect;
FHasImage : Boolean;
FDefaultSortDirection : TSortDirection;
function GetCaptionAlignment : TAlignment;
function GetCaptionWidth : TDimension;
function GetLeft : TDimension;
function IsBiDiModeStored : Boolean;
function IsCaptionAlignmentStored : Boolean;
function IsColorStored : Boolean;
procedure SetAlignment(const Value : TAlignment);
procedure SetBiDiMode(Value : TBiDiMode);
procedure SetCaptionAlignment(const Value : TAlignment);
procedure SetCheckBox(Value : Boolean);
procedure SetCheckState(Value : TCheckState);
procedure SetCheckType(Value : TCheckType);
procedure SetColor(const Value : TColor);
procedure SetImageIndex(Value : TImageIndex);
procedure SetLayout(Value : TVTHeaderColumnLayout);
procedure SetMargin(Value : TDimension);
procedure SetMaxWidth(Value : TDimension);
procedure SetMinWidth(Value : TDimension);
procedure SetOptions(Value : TVTColumnOptions);
procedure SetPosition(Value : TColumnPosition);
procedure SetSpacing(Value : TDimension);
procedure SetStyle(Value : TVirtualTreeColumnStyle);
protected
FLeft : TDimension;
procedure ChangeScale(M, D : TDimension; isDpiChange : Boolean); virtual;
procedure ComputeHeaderLayout(var PaintInfo : THeaderPaintInfo; DrawFormat : Cardinal; CalculateTextRect : Boolean = False);
procedure DefineProperties(Filer : TFiler); override;
procedure GetAbsoluteBounds(var Left, Right : TDimension);
function GetDisplayName : string; override;
function GetText : string; virtual; // [IPK]
procedure SetText(const Value : string); virtual; // [IPK] private to protected & virtual
function GetOwner : TVirtualTreeColumns; reintroduce;
procedure InternalSetWidth(const Value : TDimension); //bypass side effects in SetWidth
procedure ReadHint(Reader : TReader);
procedure ReadText(Reader : TReader);
procedure SetCollection(Value : TCollection); override;
procedure SetWidth(Value : TDimension);
public
constructor Create(Collection : TCollection); override;
destructor Destroy; override;
procedure Assign(Source : TPersistent); override;
function Equals(OtherColumnObj : TObject) : Boolean; override;
function GetRect : TRect; virtual;
property HasImage : Boolean read FHasImage;
property ImageRect : TRect read FImageRect;
procedure LoadFromStream(const Stream : TStream; Version : Integer);
procedure ParentBiDiModeChanged;
procedure ParentColorChanged;
procedure RestoreLastWidth;
function GetEffectiveColor() : TColor;
procedure SaveToStream(const Stream : TStream);
function UseRightToLeftReading : Boolean;
property BonusPixel : Boolean read FBonusPixel write FBonusPixel;
property CaptionText : string read FCaptionText;
property LastWidth : TDimension read FLastWidth;
property Left : TDimension read GetLeft;
property Owner : TVirtualTreeColumns read GetOwner;
property SpringRest : Single read FSpringRest write FSpringRest;
published
property Alignment : TAlignment read FAlignment write SetAlignment default taLeftJustify;
property BiDiMode : TBiDiMode read FBiDiMode write SetBiDiMode stored IsBiDiModeStored;
property CaptionAlignment : TAlignment read GetCaptionAlignment write SetCaptionAlignment
stored IsCaptionAlignmentStored default taLeftJustify;
property CaptionWidth : TDimension read GetCaptionWidth;
property CheckType : TCheckType read FCheckType write SetCheckType default ctCheckBox;
property CheckState : TCheckState read FCheckState write SetCheckState default csUncheckedNormal;
property CheckBox : Boolean read FCheckBox write SetCheckBox default False;
property Color : TColor read FColor write SetColor stored IsColorStored;
property DefaultSortDirection : TSortDirection read FDefaultSortDirection write FDefaultSortDirection default sdAscending;
property Hint : string read FHint write FHint;
property ImageIndex : TImageIndex read FImageIndex write SetImageIndex default - 1;
property Layout : TVTHeaderColumnLayout read FLayout write SetLayout default blGlyphLeft;
property Margin : TDimension read FMargin write SetMargin default 4;
property MaxWidth : TDimension read FMaxWidth write SetMaxWidth default 10000;
property MinWidth : TDimension read FMinWidth write SetMinWidth default 10;
property Options : TVTColumnOptions read FOptions write SetOptions default DefaultColumnOptions;
property EditOptions : TVTEditOptions read FEditOptions write FEditOptions default toDefaultEdit;
property EditNextColumn : TDimension read FEditNextColumn write FEditNextColumn default - 1;
property Position : TColumnPosition read FPosition write SetPosition;
property Spacing : TDimension read FSpacing write SetSpacing default cDefaultColumnSpacing;
property Style : TVirtualTreeColumnStyle read FStyle write SetStyle default vsText;
property Tag : NativeInt read FTag write FTag default 0;
property Text : string read GetText write SetText;
property Width : TDimension read FWidth write SetWidth default 50;
end;
TVirtualTreeColumnClass = class of TVirtualTreeColumn;
TColumnsArray = array of TVirtualTreeColumn;
TCardinalArray = array of Cardinal;
TIndexArray = array of TColumnIndex;
TVirtualTreeColumns = class(TCollection)
private
FHeader : TVTHeader;
FHeaderBitmap : TBitmap; // backbuffer for drawing
FHoverIndex, // currently "hot" column
FDownIndex, // Column on which a mouse button is held down.
FTrackIndex : TColumnIndex; // Index of column which is currently being resized.
FClickIndex : TColumnIndex; // Index of the last clicked column.
FCheckBoxHit : Boolean; // True if the last click was on a header checkbox.
FPositionToIndex : TIndexArray;
FDefaultWidth : TDimension; // the width columns are created with
FNeedPositionsFix : Boolean; // True if FixPositions must still be called after DFM loading or Bidi mode change.
FClearing : Boolean; // True if columns are being deleted entirely.
FColumnPopupMenu : TPopupMenu; // Member for storing the TVTHeaderPopupMenu
function GetCount : TDimension;
function GetItem(Index : TColumnIndex) : TVirtualTreeColumn;
function GetNewIndex(P : TPoint; var OldIndex : TColumnIndex) : Boolean;
procedure SetDefaultWidth(Value : TDimension);
procedure SetItem(Index : TColumnIndex; Value : TVirtualTreeColumn);
function GetTreeView: TCustomControl;
protected
// drag support
FDragIndex : TColumnIndex; // index of column currently being dragged
FDropTarget : TColumnIndex; // current target column (index) while dragging
FDropBefore : Boolean; // True if drop position is in the left half of a column, False for the right
// side to drop the dragged column to
procedure AdjustAutoSize(CurrentIndex : TColumnIndex; Force : Boolean = False);
function AdjustDownColumn(P : TPoint) : TColumnIndex;
function AdjustHoverColumn(P : TPoint) : Boolean;
procedure AdjustPosition(Column : TVirtualTreeColumn; Position : Cardinal);
function CanSplitterResize(P : TPoint; Column : TColumnIndex) : Boolean;
procedure DoCanSplitterResize(P : TPoint; Column : TColumnIndex; var Allowed : Boolean); virtual;
procedure DrawButtonText(DC : HDC; Caption : string; Bounds : TRect; Enabled, Hot : Boolean; DrawFormat : Cardinal;
WrapCaption : Boolean);
procedure FixPositions;
function GetColumnAndBounds(P : TPoint; var ColumnLeft, ColumnRight : TDimension; Relative : Boolean = True) : Integer;
function GetOwner : TPersistent; override;
function HandleClick(P : TPoint; Button : TMouseButton; Force, DblClick : Boolean) : Boolean; virtual;
procedure HeaderPopupMenuAddHeaderPopupItem(const Sender : TObject; const Column : TColumnIndex; var Cmd : TAddPopupItemType);
procedure IndexChanged(OldIndex, NewIndex : Integer);
procedure InitializePositionArray;
procedure Notify(Item : TCollectionItem; Action : System.Classes.TCollectionNotification); override;
procedure ReorderColumns(RTL : Boolean);
procedure SetHoverIndex(Index : TColumnIndex);
procedure Update(Item : TCollectionItem); override;
procedure UpdatePositions(Force : Boolean = False);
property HeaderBitmap : TBitmap read FHeaderBitmap;
property PositionToIndex : TIndexArray read FPositionToIndex;
property HoverIndex : TColumnIndex read FHoverIndex write FHoverIndex;
property DownIndex : TColumnIndex read FDownIndex write FDownIndex;
property CheckBoxHit : Boolean read FCheckBoxHit write FCheckBoxHit;
// Mitigator function to use the correct style service for this context (either the style assigned to the control for Delphi > 10.4 or the application style)
function StyleServices(AControl : TControl = nil) : TCustomStyleServices;
public
constructor Create(AOwner : TVTHeader); virtual;
destructor Destroy; override;
function Add : TVirtualTreeColumn; virtual;
procedure AnimatedResize(Column : TColumnIndex; NewWidth : TDimension);
procedure Assign(Source : TPersistent); override;
procedure Clear; virtual;
function ColumnFromPosition(P : TPoint; Relative : Boolean = True) : TColumnIndex; overload; virtual;
function ColumnFromPosition(PositionIndex : TColumnPosition) : TColumnIndex; overload; virtual;
function Equals(OtherColumnsObj : TObject) : Boolean; override;
procedure GetColumnBounds(Column : TColumnIndex; var Left, Right : TDimension);
function GetFirstVisibleColumn(ConsiderAllowFocus : Boolean = False) : TColumnIndex;
function GetLastVisibleColumn(ConsiderAllowFocus : Boolean = False) : TColumnIndex;
function GetFirstColumn : TColumnIndex;
function GetNextColumn(Column : TColumnIndex) : TColumnIndex;
function GetNextVisibleColumn(Column : TColumnIndex; ConsiderAllowFocus : Boolean = False) : TColumnIndex;
function GetPreviousColumn(Column : TColumnIndex) : TColumnIndex;
function GetPreviousVisibleColumn(Column : TColumnIndex; ConsiderAllowFocus : Boolean = False) : TColumnIndex;
function GetScrollWidth : TDimension;
function GetVisibleColumns : TColumnsArray;
function GetVisibleFixedWidth : TDimension;
function IsValidColumn(Column : TColumnIndex) : Boolean;
procedure LoadFromStream(const Stream : TStream; Version : Integer);
procedure PaintHeader(DC : HDC; R : TRect; HOffset : TDimension); overload; virtual;
procedure PaintHeader(TargetCanvas : TCanvas; R : TRect; const Target : TPoint;
RTLOffset : TDimension = 0); overload; virtual;
procedure SaveToStream(const Stream : TStream);
procedure EndUpdate(); override;
function TotalWidth : TDimension;
property Count : Integer read GetCount;
property ClickIndex : TColumnIndex read FClickIndex write FClickIndex;
property DefaultWidth : TDimension read FDefaultWidth write SetDefaultWidth;
property DragIndex : TColumnIndex read FDragIndex write FDragIndex;
property DropBefore : Boolean read FDropBefore write FDropBefore;
property DropTarget : TColumnIndex read FDropTarget write FDropTarget;
property Items[Index : TColumnIndex] : TVirtualTreeColumn read GetItem write SetItem; default;
property Header: TVTHeader read FHeader;
property TrackIndex : TColumnIndex read FTrackIndex write FTrackIndex;
property TreeView : TCustomControl read GetTreeView;
end;
TVirtualTreeColumnsClass = class of TVirtualTreeColumns;
TVTConstraintPercent = 0 .. 100;
TVTFixedAreaConstraints = class(TPersistent)
private
FHeader : TVTHeader;
FMaxHeightPercent, FMaxWidthPercent, FMinHeightPercent, FMinWidthPercent : TVTConstraintPercent;
FOnChange : TNotifyEvent;
procedure SetConstraints(Index : Integer; Value : TVTConstraintPercent);
protected
procedure Change;
property Header : TVTHeader read FHeader;
public
constructor Create(AOwner : TVTHeader);
procedure Assign(Source : TPersistent); override;
property OnChange : TNotifyEvent read FOnChange write FOnChange;
published
property MaxHeightPercent : TVTConstraintPercent index 0 read FMaxHeightPercent write SetConstraints default 0;
property MaxWidthPercent : TVTConstraintPercent index 1 read FMaxWidthPercent write SetConstraints default 95;
property MinHeightPercent : TVTConstraintPercent index 2 read FMinHeightPercent write SetConstraints default 0;
property MinWidthPercent : TVTConstraintPercent index 3 read FMinWidthPercent write SetConstraints default 0;
end;
TVTHeaderStyle = (hsThickButtons, //TButton look and feel
hsFlatButtons, //flatter look than hsThickButton, like an always raised flat TToolButton
hsPlates //flat TToolButton look and feel (raise on hover etc.)
);
TVTHeaderOption = (hoAutoResize, //Adjust a column so that the header never exceeds the client width of the owner control.
hoColumnResize, //Resizing columns with the mouse is allowed.
hoDblClickResize, //Allows a column to resize itself to its largest entry.
hoDrag, //Dragging columns is allowed.
hoHotTrack, //Header captions are highlighted when mouse is over a particular column.
hoOwnerDraw, //Header items with the owner draw style can be drawn by the application via event.
hoRestrictDrag, //Header can only be dragged horizontally.
hoShowHint, //Show application defined header hint.
hoShowImages, //Show header images.
hoShowSortGlyphs, //Allow visible sort glyphs.
hoVisible, //Header is visible.
hoAutoSpring, //Distribute size changes of the header to all columns, which are sizable and have the coAutoSpring option enabled.
hoFullRepaintOnResize, //Fully invalidate the header (instead of subsequent columns only) when a column is resized.
hoDisableAnimatedResize, //Disable animated resize for all columns.
hoHeightResize, //Allow resizing header height via mouse.
hoHeightDblClickResize, //Allow the header to resize itself to its default height.
hoHeaderClickAutoSort, //Clicks on the header will make the clicked column the SortColumn or toggle sort direction if it already was the sort column
hoAutoColumnPopupMenu, //Show a context menu for activating and deactivating columns on right click
hoAutoResizeInclCaption //Includes the header caption for the auto resizing
);
TVTHeaderOptions = set of TVTHeaderOption;
THeaderState = (hsAutoSizing, //auto size chain is in progess, do not trigger again on WM_SIZE
hsDragging, //header dragging is in progress (only if enabled)
hsDragPending, //left button is down, user might want to start dragging a column
hsLoading, //The header currently loads from stream, so updates are not necessary.
hsColumnWidthTracking, //column resizing is in progress
hsColumnWidthTrackPending, //left button is down, user might want to start resize a column
hsHeightTracking, //height resizing is in progress
hsHeightTrackPending, //left button is down, user might want to start changing height
hsResizing, //multi column resizing in progress
hsScaling, //the header is scaled after a change of FixedAreaConstraints or client size
hsNeedScaling //the header needs to be scaled
);
THeaderStates = set of THeaderState;
TVTHeader = class(TPersistent)
private
FOwner : TCustomControl;
FColumns : TVirtualTreeColumns;
FHeight : TDimension;
FFont : TFont;
FParentFont : Boolean;
FOptions : TVTHeaderOptions;
FStyle : TVTHeaderStyle; //button style
FBackgroundColor : TColor;
FAutoSizeIndex : TColumnIndex;
FPopupMenu : TPopupMenu;
FMainColumn : TColumnIndex; //the column which holds the tree
FMaxHeight : TDimension;
FMinHeight : TDimension;
FDefaultHeight : TDimension;
FFixedAreaConstraints : TVTFixedAreaConstraints; //Percentages for the fixed area (header, fixed columns).
FImages : TCustomImageList;
FImageChangeLink : TChangeLink; //connections to the image list to get notified about changes
fSplitterHitTolerance : TDimension; //For property SplitterHitTolerance
FSortColumn : TColumnIndex;
FSortDirection : TSortDirection;
FDragImage : TVTDragImage; //drag image management during header drag
FLastWidth : TDimension; //Used to adjust spring columns. This is the width of all visible columns, not the header rectangle.
FRestoreSelectionColumnIndex : Integer; //The column that is used to implement the coRestoreSelection option
function GetMainColumn : TColumnIndex;
function GetUseColumns : Boolean;
function IsFontStored : Boolean;
procedure SetAutoSizeIndex(Value : TColumnIndex);
procedure SetBackground(Value : TColor);
procedure SetColumns(Value : TVirtualTreeColumns);
procedure SetDefaultHeight(Value : TDimension);
procedure SetFont(const Value : TFont);
procedure SetHeight(Value : TDimension);
procedure SetImages(const Value : TCustomImageList);
procedure SetMainColumn(Value : TColumnIndex);
procedure SetMaxHeight(Value : TDimension);
procedure SetMinHeight(Value : TDimension);
procedure SetOptions(Value : TVTHeaderOptions);
procedure SetParentFont(Value : Boolean);
procedure SetSortColumn(Value : TColumnIndex);
procedure SetSortDirection(const Value : TSortDirection);
procedure SetStyle(Value : TVTHeaderStyle);
function GetRestoreSelectionColumnIndex : Integer;
protected
FStates : THeaderStates; //Used to keep track of internal states the header can enter.
FDragStart : TPoint; //initial mouse drag position
FTrackStart : TPoint; //client coordinates of the tracking start point
FTrackPoint : TPoint; //Client coordinate where the tracking started.
FDoingAutoFitColumns : Boolean; //Flag to avoid using the stored width for Main column
procedure FontChanged(Sender : TObject); virtual;
procedure AutoScale(isDpiChange: Boolean); virtual;
function CanSplitterResize(P : TPoint) : Boolean;
function CanWriteColumns : Boolean; virtual;
procedure ChangeScale(M, D : TDimension; isDpiChange : Boolean); virtual;
function DetermineSplitterIndex(P : TPoint) : Boolean; virtual;
procedure DoAfterAutoFitColumn(Column : TColumnIndex); virtual;
procedure DoAfterColumnWidthTracking(Column : TColumnIndex); virtual;
procedure DoAfterHeightTracking; virtual;
function DoBeforeAutoFitColumn(Column : TColumnIndex; SmartAutoFitType : TSmartAutoFitType) : Boolean; virtual;
procedure DoBeforeColumnWidthTracking(Column : TColumnIndex; Shift : TShiftState); virtual;
procedure DoBeforeHeightTracking(Shift : TShiftState); virtual;
procedure DoCanSplitterResize(P : TPoint; var Allowed : Boolean); virtual;
function DoColumnWidthDblClickResize(Column : TColumnIndex; P : TPoint; Shift : TShiftState) : Boolean; virtual;
function DoColumnWidthTracking(Column : TColumnIndex; Shift : TShiftState; var TrackPoint : TPoint; P : TPoint) : Boolean; virtual;
function DoGetPopupMenu(Column : TColumnIndex; Position : TPoint) : TPopupMenu; virtual;
function DoHeightTracking(var P : TPoint; Shift : TShiftState) : Boolean; virtual;
function DoHeightDblClickResize(var P : TPoint; Shift : TShiftState) : Boolean; virtual;
procedure DoSetSortColumn(Value : TColumnIndex; pSortDirection : TSortDirection); virtual;
procedure DragTo(P : TPoint); virtual;
procedure FixedAreaConstraintsChanged(Sender : TObject);
function GetColumnsClass : TVirtualTreeColumnsClass; virtual;
function GetOwner : TPersistent; override;
function GetShiftState : TShiftState;
function HandleHeaderMouseMove(var Message : TWMMouseMove) : Boolean;
function HandleMessage(var Message : TMessage) : Boolean; virtual;
procedure ImageListChange(Sender : TObject);
procedure PrepareDrag(P, Start : TPoint);
procedure ReadColumns(Reader : TReader);
procedure RecalculateHeader; virtual;
procedure RescaleHeader;
procedure UpdateMainColumn;
procedure UpdateSpringColumns;
procedure WriteColumns(Writer : TWriter);
procedure InternalSetMainColumn(const Index : TColumnIndex);
procedure InternalSetAutoSizeIndex(const Index : TColumnIndex);
procedure InternalSetSortColumn(const Index : TColumnIndex);
public
constructor Create(AOwner : TCustomControl); virtual;
destructor Destroy; override;
function AllowFocus(ColumnIndex : TColumnIndex) : Boolean;
procedure Assign(Source : TPersistent); override;
procedure AutoFitColumns(); overload;
procedure AutoFitColumns(Animated : Boolean; SmartAutoFitType : TSmartAutoFitType = smaUseColumnOption; RangeStartCol : Integer = NoColumn; RangeEndCol : Integer = NoColumn); overload; virtual;
function InHeader(P : TPoint) : Boolean; virtual;
function InHeaderSplitterArea(P : TPoint) : Boolean; virtual;
procedure Invalidate(Column : TVirtualTreeColumn; ExpandToBorder : Boolean = False; UpdateNowFlag : Boolean = False);
procedure LoadFromStream(const Stream : TStream); virtual;
function ResizeColumns(ChangeBy : TDimension; RangeStartCol : TColumnIndex; RangeEndCol : TColumnIndex; Options : TVTColumnOptions = [coVisible]) : TDimension;
procedure RestoreColumns;
procedure SaveToStream(const Stream : TStream); virtual;
procedure StyleChanged(); virtual;
property DragImage : TVTDragImage read FDragImage;
property RestoreSelectionColumnIndex : Integer read GetRestoreSelectionColumnIndex write FRestoreSelectionColumnIndex default NoColumn;
property States : THeaderStates read FStates;
property Treeview : TCustomControl read FOwner;
property UseColumns : Boolean read GetUseColumns;
property doingAutoFitColumns : Boolean read FDoingAutoFitColumns;
published
property AutoSizeIndex : TColumnIndex read FAutoSizeIndex write SetAutoSizeIndex;
property Background : TColor read FBackgroundColor write SetBackground default clBtnFace;
property Columns : TVirtualTreeColumns read FColumns write SetColumns stored False; //Stored by the owner tree to support VFI.
property DefaultHeight : Integer read FDefaultHeight write SetDefaultHeight default 19;
property Font : TFont read FFont write SetFont stored IsFontStored;
property FixedAreaConstraints : TVTFixedAreaConstraints read FFixedAreaConstraints write FFixedAreaConstraints;
property Height : Integer read FHeight write SetHeight default 19;
property Images : TCustomImageList read FImages write SetImages;
property MainColumn : TColumnIndex read GetMainColumn write SetMainColumn default 0;
property MaxHeight : Integer read FMaxHeight write SetMaxHeight default 10000;
property MinHeight : Integer read FMinHeight write SetMinHeight default 10;
property Options : TVTHeaderOptions read FOptions write SetOptions default [hoColumnResize, hoDrag, hoShowSortGlyphs];
property ParentFont : Boolean read FParentFont write SetParentFont default True;
property PopupMenu : TPopupMenu read FPopupMenu write FPopupMenu;
property SortColumn : TColumnIndex read FSortColumn write SetSortColumn default NoColumn;
property SortDirection : TSortDirection read FSortDirection write SetSortDirection default sdAscending;
property SplitterHitTolerance : Integer read fSplitterHitTolerance write fSplitterHitTolerance default 8;
//The area in pixels around a spliter which is sensitive for resizing
property Style : TVTHeaderStyle read FStyle write SetStyle default hsThickButtons;
end;
TVTHeaderClass = class of TVTHeader;
implementation
uses
WinApi.ShlObj,
WinApi.UxTheme,
System.Math,
System.SysUtils,
Vcl.Forms,
VirtualTrees,
VirtualTrees.HeaderPopup;
type
TVirtualTreeColumnsCracker = class(TVirtualTreeColumns);
TVirtualTreeColumnCracker = class(TVirtualTreeColumn);
TBaseVirtualTreeCracker = class(TBaseVirtualTree);
TVTHeaderHelper = class helper for TVTHeader
public
function Tree : TBaseVirtualTreeCracker;
end;
TVirtualTreeColumnHelper = class helper for TVirtualTreeColumn
function TreeViewControl : TBaseVirtualTreeCracker;
function Header : TVTHeader;
end;
TVirtualTreeColumnsHelper = class helper for TVirtualTreeColumns
function TreeViewControl : TBaseVirtualTreeCracker;
end;
//----------------- TVTFixedAreaConstraints ----------------------------------------------------------------------------
constructor TVTFixedAreaConstraints.Create(AOwner : TVTHeader);
begin
inherited Create;
FMaxWidthPercent := 95;
FHeader := AOwner;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTFixedAreaConstraints.SetConstraints(Index : Integer; Value : TVTConstraintPercent);
begin
case Index of
0 :
if Value <> FMaxHeightPercent then
begin
FMaxHeightPercent := Value;
if (Value > 0) and (Value < FMinHeightPercent) then
FMinHeightPercent := Value;
Change;
end;
1 :
if Value <> FMaxWidthPercent then
begin
FMaxWidthPercent := Value;
if (Value > 0) and (Value < FMinWidthPercent) then
FMinWidthPercent := Value;
Change;
end;
2 :
if Value <> FMinHeightPercent then
begin
FMinHeightPercent := Value;
if (FMaxHeightPercent > 0) and (Value > FMaxHeightPercent) then
FMaxHeightPercent := Value;
Change;
end;
3 :
if Value <> FMinWidthPercent then
begin
FMinWidthPercent := Value;
if (FMaxWidthPercent > 0) and (Value > FMaxWidthPercent) then
FMaxWidthPercent := Value;
Change;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTFixedAreaConstraints.Change;
begin
if Assigned(FOnChange) then
FOnChange(Self);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTFixedAreaConstraints.Assign(Source : TPersistent);
begin
if Source is TVTFixedAreaConstraints then
begin
FMaxHeightPercent := TVTFixedAreaConstraints(Source).FMaxHeightPercent;
FMaxWidthPercent := TVTFixedAreaConstraints(Source).FMaxWidthPercent;
FMinHeightPercent := TVTFixedAreaConstraints(Source).FMinHeightPercent;
FMinWidthPercent := TVTFixedAreaConstraints(Source).FMinWidthPercent;
Change;
end
else
inherited;
end;
//----------------- TVTHeader -----------------------------------------------------------------------------------------
constructor TVTHeader.Create(AOwner : TCustomControl);
begin
inherited Create;
FOwner := AOwner;
FColumns := GetColumnsClass.Create(Self);
FHeight := 19;
FDefaultHeight := FHeight;
FMinHeight := 10;
FMaxHeight := 10000;
FFont := TFont.Create;
FFont.OnChange := FontChanged;
FParentFont := True;
FBackgroundColor := clBtnFace;
FOptions := [hoColumnResize, hoDrag, hoShowSortGlyphs];
FImageChangeLink := TChangeLink.Create;
FImageChangeLink.OnChange := ImageListChange;
FSortColumn := NoColumn;
FSortDirection := sdAscending;
FMainColumn := NoColumn;
FDragImage := TVTDragImage.Create(AOwner);
with FDragImage do
begin
Fade := False;
PreBlendBias := - 50;
Transparency := 140;
end;
fSplitterHitTolerance := 8;
FFixedAreaConstraints := TVTFixedAreaConstraints.Create(Self);
FFixedAreaConstraints.OnChange := FixedAreaConstraintsChanged;
FDoingAutoFitColumns := False;
end;
//----------------------------------------------------------------------------------------------------------------------
destructor TVTHeader.Destroy;
begin
FDragImage.Free;
FFixedAreaConstraints.Free;
FImageChangeLink.Free;
FFont.Free;
FColumns.Clear; //TCollection's Clear method is not virtual, so we have to call our own Clear method manually.
FColumns.Free;
inherited;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.FontChanged(Sender : TObject);
begin
inherited;
{$IF CompilerVersion < 31}
AutoScale(false);
{$IFEND}
end;
procedure TVTHeader.AutoScale(isDpiChange: Boolean);
var
I : Integer;
lMaxHeight : Integer;
begin
if (toAutoChangeScale in TBaseVirtualTreeCracker(Tree).TreeOptions.AutoOptions) and not isDpiChange then
begin
//Ensure a minimum header size based on the font, so that all text is visible.
//First find the largest Columns[].Spacing
lMaxHeight := 0;
for I := 0 to Self.Columns.Count - 1 do
lMaxHeight := Max(lMaxHeight, Columns[I].Spacing);
//Calculate the required height based on the font, this is important as the user might just have increased the size of the system icon font.
with TBitmap.Create do
try
Canvas.Font.Assign(FFont);
lMaxHeight := lMaxHeight { top spacing } + (lMaxHeight div 2) { minimum bottom spacing } + Canvas.TextHeight('Q');
finally
Free;
end;
//Get the maximum of the scaled original value and the minimum needed header height.
lMaxHeight := Max(lMaxHeight, FHeight);
//Set the calculated size
Self.SetHeight(lMaxHeight);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.GetMainColumn : TColumnIndex;
begin
if FColumns.Count > 0 then
Result := FMainColumn
else
Result := NoColumn;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.GetUseColumns : Boolean;
begin
Result := FColumns.Count > 0;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.IsFontStored : Boolean;
begin
Result := not ParentFont;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.SetAutoSizeIndex(Value : TColumnIndex);
begin
if FAutoSizeIndex <> Value then
begin
FAutoSizeIndex := Value;
if hoAutoResize in FOptions then
TVirtualTreeColumnsCracker(Columns).AdjustAutoSize(InvalidColumn);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.SetBackground(Value : TColor);
begin
if FBackgroundColor <> Value then
begin
FBackgroundColor := Value;
Invalidate(nil);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.SetColumns(Value : TVirtualTreeColumns);
begin
FColumns.Assign(Value);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.SetDefaultHeight(Value : Integer);
begin
if Value < FMinHeight then
Value := FMinHeight;
if Value > FMaxHeight then
Value := FMaxHeight;
if FHeight = FDefaultHeight then
SetHeight(Value);
FDefaultHeight := Value;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.SetFont(const Value : TFont);
begin
FFont.Assign(Value);
FParentFont := False;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.SetHeight(Value : Integer);
var
RelativeMaxHeight, RelativeMinHeight, EffectiveMaxHeight, EffectiveMinHeight : Integer;
begin
if not Tree.HandleAllocated then
begin
FHeight := Value;
Include(FStates, hsNeedScaling);
end
else
begin
with FFixedAreaConstraints do
begin
RelativeMaxHeight := ((Tree.ClientHeight + FHeight) * FMaxHeightPercent) div 100;
RelativeMinHeight := ((Tree.ClientHeight + FHeight) * FMinHeightPercent) div 100;
EffectiveMinHeight := IfThen(FMaxHeightPercent > 0, Min(RelativeMaxHeight, FMinHeight), FMinHeight);
EffectiveMaxHeight := IfThen(FMinHeightPercent > 0, Max(RelativeMinHeight, FMaxHeight), FMaxHeight);
Value := Min(Max(Value, EffectiveMinHeight), EffectiveMaxHeight);
if FMinHeightPercent > 0 then
Value := Max(RelativeMinHeight, Value);
if FMaxHeightPercent > 0 then
Value := Min(RelativeMaxHeight, Value);
end;
if FHeight <> Value then
begin
FHeight := Value;
if not (csLoading in Tree.ComponentState) and not (hsScaling in FStates) then
RecalculateHeader;
Tree.Invalidate;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.SetImages(const Value : TCustomImageList);
begin
if FImages <> Value then
begin
if Assigned(FImages) then
begin
FImages.UnRegisterChanges(FImageChangeLink);
FImages.RemoveFreeNotification(FOwner);
end;
FImages := Value;
if Assigned(FImages) then
begin
FImages.RegisterChanges(FImageChangeLink);
FImages.FreeNotification(FOwner);
end;
if not (csLoading in Tree.ComponentState) then
Invalidate(nil);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.SetMainColumn(Value : TColumnIndex);
begin
if (csLoading in Tree.ComponentState) or (csDestroying in Tree.ComponentState) then
FMainColumn := Value
else
begin
if Value < 0 then
Value := 0;
if Value > FColumns.Count - 1 then
Value := FColumns.Count - 1;
if Value <> FMainColumn then
begin
FMainColumn := Value;
Tree.MainColumnChanged;
if not (toExtendedFocus in Tree.TreeOptions.SelectionOptions) then
Tree.FocusedColumn := FMainColumn;
Tree.Invalidate;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.SetMaxHeight(Value : Integer);
begin
if Value < FMinHeight then
Value := FMinHeight;
FMaxHeight := Value;
SetHeight(FHeight);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.SetMinHeight(Value : Integer);
begin
if Value < 0 then
Value := 0;
if Value > FMaxHeight then
Value := FMaxHeight;
FMinHeight := Value;
SetHeight(FHeight);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.SetOptions(Value : TVTHeaderOptions);
var
ToBeSet, ToBeCleared : TVTHeaderOptions;
begin
ToBeSet := Value - FOptions;
ToBeCleared := FOptions - Value;
FOptions := Value;
if (hoAutoResize in (ToBeSet + ToBeCleared)) and (FColumns.Count > 0) then
begin
TVirtualTreeColumnsCracker(FColumns).AdjustAutoSize(InvalidColumn);
if Tree.HandleAllocated then
begin
Tree.UpdateHorizontalScrollBar(False);
if hoAutoResize in ToBeSet then
Tree.Invalidate;
end;
end;
if not (csLoading in Tree.ComponentState) and Tree.HandleAllocated then
begin
if hoVisible in (ToBeSet + ToBeCleared) then
RecalculateHeader;
Invalidate(nil);
Tree.Invalidate;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.SetParentFont(Value : Boolean);
begin
if FParentFont <> Value then
begin
FParentFont := Value;
if FParentFont then
FFont.Assign(TBaseVirtualTree(FOwner).Font);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.SetSortColumn(Value : TColumnIndex);
begin
if csLoading in Tree.ComponentState then
FSortColumn := Value
else
DoSetSortColumn(Value, FSortDirection);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.SetSortDirection(const Value : TSortDirection);
begin
if Value <> FSortDirection then
begin
FSortDirection := Value;
Invalidate(nil);
if ((toAutoSort in Tree.TreeOptions.AutoOptions) or (hoHeaderClickAutoSort in Options)) and (Tree.UpdateCount = 0) then
Tree.SortTree(FSortColumn, FSortDirection, True);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.CanSplitterResize(P : TPoint) : Boolean;
begin
Result := hoHeightResize in FOptions;
DoCanSplitterResize(P, Result);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.SetStyle(Value : TVTHeaderStyle);
begin
if FStyle <> Value then
begin
FStyle := Value;
if not (csLoading in Tree.ComponentState) then
Invalidate(nil);
end;
end;
procedure TVTHeader.StyleChanged();
begin
{$IF CompilerVersion < 31}
AutoScale(False); //Elements may have changed in size
{$IFEND}
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.CanWriteColumns : Boolean;
//descendants may override this to optionally prevent column writing (e.g. if they are build dynamically).
begin
Result := True;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.ChangeScale(M, D : Integer; isDpiChange : Boolean);
var
I : Integer;
begin
//This method is only executed if toAutoChangeScale is set
FMinHeight := MulDiv(FMinHeight, M, D);
FMaxHeight := MulDiv(FMaxHeight, M, D);
Self.Height := MulDiv(FHeight, M, D);
if not ParentFont then
Font.Height := MulDiv(Font.Height, M, D);
//Scale the columns widths too
for I := 0 to FColumns.Count - 1 do
TVirtualTreeColumnCracker(Self.FColumns[I]).ChangeScale(M, D, isDpiChange);
if not isDpiChange then
AutoScale(isDpiChange);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.DetermineSplitterIndex(P : TPoint) : Boolean;
//Tries to find the index of that column whose right border corresponds to P.
//Result is True if column border was hit (with -3..+5 pixels tolerance).
//For continuous resizing the current track index and the column's left/right border are set.
//Note: The hit test is checking from right to left (or left to right in RTL mode) to make enlarging of zero-sized
//columns possible.
var
VisibleFixedWidth : Integer;
SplitPoint : Integer;
//--------------- local function --------------------------------------------
function IsNearBy(IsFixedCol : Boolean; LeftTolerance, RightTolerance : Integer) : Boolean;
begin
if IsFixedCol then
Result := (P.X < SplitPoint + Tree.EffectiveOffsetX + RightTolerance) and (P.X > SplitPoint + Tree.EffectiveOffsetX - LeftTolerance)
else
Result := (P.X > VisibleFixedWidth) and (P.X < SplitPoint + RightTolerance) and (P.X > SplitPoint - LeftTolerance);
end;
//--------------- end local function ----------------------------------------
var
I : Integer;
LeftTolerance : Integer; //The area left of the column divider which allows column resizing
begin
Result := False;
if FColumns.Count > 0 then
begin
FColumns.TrackIndex := NoColumn;
VisibleFixedWidth := FColumns.GetVisibleFixedWidth;
LeftTolerance := Round(SplitterHitTolerance * 0.6);
if Tree.UseRightToLeftAlignment then
begin
SplitPoint := - Tree.EffectiveOffsetX;
if FColumns.TotalWidth < Tree.ClientWidth then
Inc(SplitPoint, Tree.ClientWidth - FColumns.TotalWidth);
for I := 0 to FColumns.Count - 1 do
with TVirtualTreeColumnsCracker(FColumns), Items[PositionToIndex[I]] do
if coVisible in Options then
begin
if IsNearBy(coFixed in Options, LeftTolerance, SplitterHitTolerance - LeftTolerance) then
begin
if CanSplitterResize(P, PositionToIndex[I]) then
begin
Result := True;
TrackIndex := PositionToIndex[I];
//Keep the right border of this column. This and the current mouse position
//directly determine the current column width.
FTrackPoint.X := SplitPoint + IfThen(coFixed in Options, Tree.EffectiveOffsetX) + Width;
FTrackPoint.Y := P.Y;
Break;
end;
end;
Inc(SplitPoint, Width);
end;
end
else
begin
SplitPoint := - Tree.EffectiveOffsetX + FColumns.TotalWidth;
for I := FColumns.Count - 1 downto 0 do
with TVirtualTreeColumnsCracker(FColumns), Items[PositionToIndex[I]] do
if coVisible in Options then
begin
if IsNearBy(coFixed in Options, SplitterHitTolerance - LeftTolerance, LeftTolerance) then
begin
if CanSplitterResize(P, PositionToIndex[I]) then
begin
Result := True;
TrackIndex := PositionToIndex[I];
//Keep the left border of this column. This and the current mouse position
//directly determine the current column width.
FTrackPoint.X := SplitPoint + IfThen(coFixed in Options, Tree.EffectiveOffsetX) - Width;
FTrackPoint.Y := P.Y;
Break;
end;
end;
Dec(SplitPoint, Width);
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.DoAfterAutoFitColumn(Column : TColumnIndex);
begin
if Assigned(Tree.OnAfterAutoFitColumn) then
Tree.OnAfterAutoFitColumn(Self, Column);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.DoAfterColumnWidthTracking(Column : TColumnIndex);
//Tell the application that a column width tracking operation has been finished.
begin
if Assigned(Tree.OnAfterColumnWidthTracking) then
Tree.OnAfterColumnWidthTracking(Self, Column);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.DoAfterHeightTracking;
//Tell the application that a height tracking operation has been finished.
begin
if Assigned(Tree.OnAfterHeaderHeightTracking) then
Tree.OnAfterHeaderHeightTracking(Self);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.DoBeforeAutoFitColumn(Column : TColumnIndex; SmartAutoFitType : TSmartAutoFitType) : Boolean;
//Query the application if we may autofit a column.
begin
Result := True;
if Assigned(Tree.OnBeforeAutoFitColumn) then
Tree.OnBeforeAutoFitColumn(Self, Column, SmartAutoFitType, Result);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.DoBeforeColumnWidthTracking(Column : TColumnIndex; Shift : TShiftState);
//Tell the a application that a column width tracking operation may begin.
begin
if Assigned(Tree.OnBeforeColumnWidthTracking) then
Tree.OnBeforeColumnWidthTracking(Self, Column, Shift);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.DoBeforeHeightTracking(Shift : TShiftState);
//Tell the application that a height tracking operation may begin.
begin
if Assigned(Tree.OnBeforeHeaderHeightTracking) then
Tree.OnBeforeHeaderHeightTracking(Self, Shift);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.DoCanSplitterResize(P : TPoint; var Allowed : Boolean);
begin
if Assigned(Tree.OnCanSplitterResizeHeader) then
Tree.OnCanSplitterResizeHeader(Self, P, Allowed);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.DoColumnWidthDblClickResize(Column : TColumnIndex; P : TPoint; Shift : TShiftState) : Boolean;
//Queries the application whether a double click on the column splitter should resize the column.
begin
Result := True;
if Assigned(Tree.OnColumnWidthDblClickResize) then
Tree.OnColumnWidthDblClickResize(Self, Column, Shift, P, Result);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.DoColumnWidthTracking(Column : TColumnIndex; Shift : TShiftState; var TrackPoint : TPoint; P : TPoint) : Boolean;
begin
Result := True;
if Assigned(Tree.OnColumnWidthTracking) then
Tree.OnColumnWidthTracking(Self, Column, Shift, TrackPoint, P, Result);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.DoGetPopupMenu(Column : TColumnIndex; Position : TPoint) : TPopupMenu;
//Queries the application whether there is a column specific header popup menu.
var
AskParent : Boolean;
begin
Result := PopupMenu;
if Assigned(Tree.OnGetPopupMenu) then
Tree.OnGetPopupMenu(TBaseVirtualTree(FOwner), nil, Column, Position, AskParent, Result);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.DoHeightTracking(var P : TPoint; Shift : TShiftState) : Boolean;
begin
Result := True;
if Assigned(Tree.OnHeaderHeightTracking) then
Tree.OnHeaderHeightTracking(Self, P, Shift, Result);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.DoHeightDblClickResize(var P : TPoint; Shift : TShiftState) : Boolean;
begin
Result := True;
if Assigned(Tree.OnHeaderHeightDblClickResize) then
Tree.OnHeaderHeightDblClickResize(Self, P, Shift, Result);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.DoSetSortColumn(Value : TColumnIndex; pSortDirection : TSortDirection);
begin
if Value < NoColumn then
Value := NoColumn;
if Value > Columns.Count - 1 then
Value := Columns.Count - 1;
if FSortColumn <> Value then
begin
if FSortColumn > NoColumn then
Invalidate(Columns[FSortColumn]);
FSortColumn := Value;
FSortDirection := pSortDirection;
if FSortColumn > NoColumn then
Invalidate(Columns[FSortColumn]);
if ((toAutoSort in Tree.TreeOptions.AutoOptions) or (hoHeaderClickAutoSort in Options)) and (Tree.UpdateCount = 0) then
Tree.SortTree(FSortColumn, FSortDirection, True);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.DragTo(P : TPoint);
//Moves the drag image to a new position, which is determined from the passed point P and the previous
//mouse position.
var
I, NewTarget : Integer;
//optimized drag image move support
ClientP : TPoint;
Left, Right : Integer;
NeedRepaint : Boolean; //True if the screen needs an update (changed drop target or drop side)
begin
//Determine new drop target and which side of it is prefered.
ClientP := Tree.ScreenToClient(P);
//Make coordinates relative to (0, 0) of the non-client area.
Inc(ClientP.Y, FHeight);
NewTarget := FColumns.ColumnFromPosition(ClientP);
NeedRepaint := (NewTarget <> InvalidColumn) and (NewTarget <> FColumns.DropTarget);
if NewTarget >= 0 then
begin
FColumns.GetColumnBounds(NewTarget, Left, Right);
if (ClientP.X < ((Left + Right) div 2)) <> FColumns.DropBefore then
begin
NeedRepaint := True;
FColumns.DropBefore := not FColumns.DropBefore;
end;
end;
if NeedRepaint then
begin
//Invalidate columns which need a repaint.
if FColumns.DropTarget > NoColumn then
begin
I := FColumns.DropTarget;
FColumns.DropTarget := NoColumn;
Invalidate(FColumns.Items[I]);
end;
if (NewTarget > NoColumn) and (NewTarget <> FColumns.DropTarget) then
begin
Invalidate(FColumns.Items[NewTarget]);
FColumns.DropTarget := NewTarget;
end;
end;
//Fix for various problems mentioned in issue 248.
if NeedRepaint then
begin
UpdateWindow(FOwner.Handle);
//The new routine recaptures the backup image after the updatewindow
//Note: We could have called this unconditionally but when called
//over the tree, doesn't capture the background image. Since our
//problems are in painting of the header, we call it only when the
//drag image is over the header.
if
//determine the case when the drag image is or was on the header area
(InHeader(FOwner.ScreenToClient(FDragImage.LastPosition)) or InHeader(FOwner.ScreenToClient(FDragImage.ImagePosition))) then
begin
GDIFlush;
TBaseVirtualTreeCracker(FOwner).UpdateWindowAndDragImage(TBaseVirtualTree(FOwner), TBaseVirtualTreeCracker(FOwner).HeaderRect, True, True);
end;
//since we took care of UpdateWindow above, there is no need to do an
//update window again by sending NeedRepaint. So switch off the second parameter.
NeedRepaint := False;
end;
FDragImage.DragTo(P, NeedRepaint);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.FixedAreaConstraintsChanged(Sender : TObject);
//This method gets called when FFixedAreaConstraints is changed.
begin
if Tree.HandleAllocated then
RescaleHeader
else
Include(FStates, hsNeedScaling);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.GetColumnsClass : TVirtualTreeColumnsClass;
//Returns the class to be used for the actual column implementation. descendants may optionally override this and
//return their own class.
begin
Result := TVirtualTreeColumns;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.GetOwner : TPersistent;
begin
Result := FOwner;
end;
function TVTHeader.GetRestoreSelectionColumnIndex : Integer;
begin
if FRestoreSelectionColumnIndex >= 0 then
Result := FRestoreSelectionColumnIndex
else
Result := MainColumn;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.GetShiftState : TShiftState;
begin
Result := [];
if GetKeyState(VK_SHIFT) < 0 then
Include(Result, ssShift);
if GetKeyState(VK_CONTROL) < 0 then
Include(Result, ssCtrl);
if GetKeyState(VK_MENU) < 0 then
Include(Result, ssAlt);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.HandleHeaderMouseMove(var Message : TWMMouseMove) : Boolean;
var
P : TPoint;
NextColumn, I : TColumnIndex;
NewWidth : Integer;
begin
Result := False;
with Message do
begin
P := Point(XPos, YPos);
if hsColumnWidthTrackPending in FStates then
begin
Tree.StopTimer(HeaderTimer);
FStates := FStates - [hsColumnWidthTrackPending] + [hsColumnWidthTracking];
HandleHeaderMouseMove := True;
Result := 0;
end
else if hsHeightTrackPending in FStates then
begin
Tree.StopTimer(HeaderTimer);
FStates := FStates - [hsHeightTrackPending] + [hsHeightTracking];
HandleHeaderMouseMove := True;
Result := 0;
end
else if hsColumnWidthTracking in FStates then
begin
if DoColumnWidthTracking(FColumns.TrackIndex, GetShiftState, FTrackPoint, P) then
begin
if Tree.UseRightToLeftAlignment then
begin
NewWidth := FTrackPoint.X - XPos;
NextColumn := FColumns.GetPreviousVisibleColumn(FColumns.TrackIndex);
end
else
begin
NewWidth := XPos - FTrackPoint.X;
NextColumn := FColumns.GetNextVisibleColumn(FColumns.TrackIndex);
end;
//The autosized column cannot be resized using the mouse normally. Instead we resize the next
//visible column, so it look as we directly resize the autosized column.
if (hoAutoResize in FOptions) and (FColumns.TrackIndex = FAutoSizeIndex) and (NextColumn > NoColumn) and (coResizable in FColumns[NextColumn].Options) and
(FColumns[FColumns.TrackIndex].MinWidth < NewWidth) and (FColumns[FColumns.TrackIndex].MaxWidth > NewWidth) then
FColumns[NextColumn].Width := FColumns[NextColumn].Width - NewWidth + FColumns[FColumns.TrackIndex].Width
else
FColumns[FColumns.TrackIndex].Width := NewWidth; //1 EListError seen here (List index out of bounds (-1)) since 10/2013
end;
HandleHeaderMouseMove := True;
Result := 0;
end
else if hsHeightTracking in FStates then
begin
if DoHeightTracking(P, GetShiftState) then
SetHeight(Integer(FHeight) + P.Y);
HandleHeaderMouseMove := True;
Result := 0;
end
else
begin
if hsDragPending in FStates then
begin
P := Tree.ClientToScreen(P);
//start actual dragging if allowed
if (hoDrag in FOptions) and Tree.DoHeaderDragging(TVirtualTreeColumnsCracker(FColumns).DownIndex) then
begin
if ((Abs(FDragStart.X - P.X) > Mouse.DragThreshold) or (Abs(FDragStart.Y - P.Y) > Mouse.DragThreshold)) then
begin
Tree.StopTimer(HeaderTimer);
with TVirtualTreeColumnsCracker(FColumns) do
begin
I := DownIndex;
DownIndex := NoColumn;
HoverIndex := NoColumn;
if I > NoColumn then
Invalidate(FColumns[I]);
end;
PrepareDrag(P, FDragStart);
FStates := FStates - [hsDragPending] + [hsDragging];
HandleHeaderMouseMove := True;
Result := 0;
end;
end;
end
else if hsDragging in FStates then
begin
DragTo(Tree.ClientToScreen(Point(XPos, YPos)));
HandleHeaderMouseMove := True;
Result := 0;
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.HandleMessage(var Message : TMessage) : Boolean;
//The header gets here the opportunity to handle certain messages before they reach the tree. This is important
//because the tree needs to handle various non-client area messages for the header as well as some dragging/tracking
//events.
//By returning True the message will not be handled further, otherwise the message is then dispatched
//to the proper message handlers.
var
P : TPoint;
R : TRect;
I : TColumnIndex;
OldPosition : Integer;
HitIndex : TColumnIndex;
NewCursor : HCURSOR;
Button : TMouseButton;
IsInHeader, IsHSplitterHit, IsVSplitterHit : Boolean;
//--------------- local function --------------------------------------------
function HSplitterHit : Boolean;
begin
Result := (hoColumnResize in FOptions) and DetermineSplitterIndex(P);
if Result and not InHeader(P) then
begin
// Code commented due to issue #1067. What was the orginal inention of this code? It does not make much sense unless you allow column resize outside the header.
//NextCol := FColumns.GetNextVisibleColumn(FColumns.TrackIndex);
//if not (coFixed in FColumns[FColumns.TrackIndex].Options) or (NextCol <= NoColumn) or
// (coFixed in FColumns[NextCol].Options) or (P.Y > Integer(Treeview.FRangeY)) then
Result := False;
end;
end;
//--------------- end local function ----------------------------------------
begin
Result := False;
case Message.Msg of
WM_SIZE :
begin
if not (tsWindowCreating in TBaseVirtualTreeCracker(FOwner).TreeStates) then
if (hoAutoResize in FOptions) and not (hsAutoSizing in FStates) then
begin
TVirtualTreeColumnsCracker(FColumns).AdjustAutoSize(InvalidColumn);
Invalidate(nil);
end
else if not (hsScaling in FStates) then
begin
RescaleHeader;
Invalidate(nil);
end;
end;
CM_PARENTFONTCHANGED :
if FParentFont then
FFont.Assign(TBaseVirtualTreeCracker(FOwner).Font);
CM_BIDIMODECHANGED :
for I := 0 to FColumns.Count - 1 do
if coParentBiDiMode in FColumns[I].Options then
FColumns[I].ParentBiDiModeChanged;
WM_NCMBUTTONDOWN :
begin
with TWMNCMButtonDown(Message) do
P := Tree.ScreenToClient(Point(XCursor, YCursor));
if InHeader(P) then
TBaseVirtualTreeCracker(FOwner).DoHeaderMouseDown(mbMiddle, GetShiftState, P.X, P.Y + Integer(FHeight));
end;
WM_NCMBUTTONUP :
begin
with TWMNCMButtonUp(Message) do
P := FOwner.ScreenToClient(Point(XCursor, YCursor));
if InHeader(P) then
begin
with TVirtualTreeColumnsCracker(FColumns) do
begin
HandleClick(P, mbMiddle, True, False);
TBaseVirtualTreeCracker(FOwner).DoHeaderMouseUp(mbMiddle, GetShiftState, P.X, P.Y + Integer(Self.FHeight));
DownIndex := NoColumn;
CheckBoxHit := False;
end;
end;
end;
WM_LBUTTONDBLCLK, WM_NCLBUTTONDBLCLK, WM_NCMBUTTONDBLCLK, WM_NCRBUTTONDBLCLK :
begin
if Message.Msg <> WM_LBUTTONDBLCLK then
with TWMNCLButtonDblClk(Message) do
P := FOwner.ScreenToClient(Point(XCursor, YCursor))
else
with TWMLButtonDblClk(Message) do
P := Point(XPos, YPos);
if (hoHeightDblClickResize in FOptions) and InHeaderSplitterArea(P) and (FDefaultHeight > 0) then
begin
if DoHeightDblClickResize(P, GetShiftState) and (FDefaultHeight > 0) then
SetHeight(FMinHeight);
Result := True;
end
else if HSplitterHit and ((Message.Msg = WM_NCLBUTTONDBLCLK) or (Message.Msg = WM_LBUTTONDBLCLK)) and (hoDblClickResize in FOptions) and (FColumns.TrackIndex > NoColumn)
then
begin
//If the click was on a splitter then resize column to smallest width.
if DoColumnWidthDblClickResize(FColumns.TrackIndex, P, GetShiftState) then
AutoFitColumns(True, smaUseColumnOption, FColumns[FColumns.TrackIndex].Position, FColumns[FColumns.TrackIndex].Position);
Message.Result := 0;
Result := True;
end
else if InHeader(P) and (Message.Msg <> WM_LBUTTONDBLCLK) then
begin
case Message.Msg of
WM_NCMBUTTONDBLCLK :
Button := mbMiddle;
WM_NCRBUTTONDBLCLK :
Button := mbRight;
else
//WM_NCLBUTTONDBLCLK
Button := mbLeft;
end;
if Button = mbLeft then
TVirtualTreeColumnsCracker(FColumns).AdjustDownColumn(P);
TVirtualTreeColumnsCracker(FColumns).HandleClick(P, Button, True, True);
end;
end;
//The "hot" area of the headers horizontal splitter is partly within the client area of the the tree, so we need
//to handle WM_LBUTTONDOWN here, too.
WM_LBUTTONDOWN, WM_NCLBUTTONDOWN :
begin
Application.CancelHint;
if not (csDesigning in Tree.ComponentState) then
begin
with Tree do
begin
//make sure no auto scrolling is active...
StopTimer(ScrollTimer);
DoStateChange([], [tsScrollPending, tsScrolling]);
//... pending editing is cancelled (actual editing remains active)
StopTimer(EditTimer);
DoStateChange([], [tsEditPending]);
end;
end;
if Message.Msg = WM_LBUTTONDOWN then
//Coordinates are already client area based.
with TWMLButtonDown(Message) do
begin
P := Point(XPos, YPos);
//#909
FDragStart := Tree.ClientToScreen(P);
end
else
with TWMNCLButtonDown(Message) do
begin
//want the drag start point in screen coordinates
FDragStart := Point(XCursor, YCursor);
P := Tree.ScreenToClient(FDragStart);
end;
IsInHeader := InHeader(P);
//in design-time header columns are always resizable
if (csDesigning in Tree.ComponentState) then
IsVSplitterHit := InHeaderSplitterArea(P)
else
IsVSplitterHit := InHeaderSplitterArea(P) and CanSplitterResize(P);
IsHSplitterHit := HSplitterHit;
if IsVSplitterHit or IsHSplitterHit then
begin
FTrackStart := P;
TVirtualTreeColumnsCracker(FColumns).HoverIndex := NoColumn;
if IsVSplitterHit then
begin
if not (csDesigning in Tree.ComponentState) then
DoBeforeHeightTracking(GetShiftState);
Include(FStates, hsHeightTrackPending);
end
else
begin
if not (csDesigning in Tree.ComponentState) then
DoBeforeColumnWidthTracking(FColumns.TrackIndex, GetShiftState);
Include(FStates, hsColumnWidthTrackPending);
end;
SetCapture(Tree.Handle);
Result := True;
Message.Result := 0;
end
else if IsInHeader then
begin
HitIndex := TVirtualTreeColumnsCracker(FColumns).AdjustDownColumn(P);
//in design-time header columns are always draggable
if ((csDesigning in Tree.ComponentState) and (HitIndex > NoColumn)) or ((hoDrag in FOptions) and (HitIndex > NoColumn) and (coDraggable in FColumns[HitIndex].Options))
then
begin
//Show potential drag operation.
//Disabled columns do not start a drag operation because they can't be clicked.
Include(FStates, hsDragPending);
SetCapture(Tree.Handle);
Result := True;
Message.Result := 0;
end;
end;
//This is a good opportunity to notify the application.
if not (csDesigning in Tree.ComponentState) and IsInHeader then
TBaseVirtualTreeCracker(FOwner).DoHeaderMouseDown(mbLeft, GetShiftState, P.X, P.Y + Integer(FHeight));
end;
WM_NCRBUTTONDOWN :
begin
with TWMNCRButtonDown(Message) do
P := FOwner.ScreenToClient(Point(XCursor, YCursor));
if InHeader(P) then
TBaseVirtualTreeCracker(FOwner).DoHeaderMouseDown(mbRight, GetShiftState, P.X, P.Y + Integer(FHeight));
end;
WM_NCRBUTTONUP :
if not (csDesigning in FOwner.ComponentState) then
with TWMNCRButtonUp(Message) do
begin
Application.CancelHint;
P := FOwner.ScreenToClient(Point(XCursor, YCursor));
if InHeader(P) then
begin
HandleMessage := TVirtualTreeColumnsCracker(FColumns).HandleClick(P, mbRight, True, False);
TBaseVirtualTreeCracker(FOwner).DoHeaderMouseUp(mbRight, GetShiftState, P.X, P.Y + Integer(FHeight));
end;
end;
//When the tree window has an active mouse capture then we only get "client-area" messages.
WM_LBUTTONUP, WM_NCLBUTTONUP :
begin
Application.CancelHint;
if FStates <> [] then
begin
ReleaseCapture;
if hsDragging in FStates then
begin
//successfull dragging moves columns
with TWMLButtonUp(Message) do
P := Tree.ClientToScreen(Point(XPos, YPos));
GetWindowRect(Tree.Handle, R);
with FColumns do
begin
FDragImage.EndDrag;
//Problem fixed:
//Column Header does not paint correctly after a drop in certain conditions
// ** The conditions are, drag is across header, mouse is not moved after
//the drop and the graphics hardware is slow in certain operations (encountered
//on Windows 10).
//Fix for the problem on certain systems where the dropped column header
//does not appear in the new position if the mouse is not moved after
//the drop. The reason is that the restore backup image operation (BitBlt)
//in the above EndDrag is slower than the header repaint in the code below
//and overlaps the new changed header with the older image.
//This happens because BitBlt seems to operate in its own thread in the
//graphics hardware and finishes later than the following code.
//
//To solve this problem, we introduce a small delay here so that the
//changed header in the following code is correctly repainted after
//the delayed BitBlt above has finished operation to restore the old
//backup image.
sleep(50);
if (DropTarget > - 1) and (DropTarget <> DragIndex) and PtInRect(R, P) then
begin
OldPosition := FColumns[DragIndex].Position;
if FColumns.DropBefore then
begin
if FColumns[DragIndex].Position < FColumns[DropTarget].Position then
FColumns[DragIndex].Position := Max(0, FColumns[DropTarget].Position - 1)
else
FColumns[DragIndex].Position := FColumns[DropTarget].Position;
end
else
begin
if FColumns[DragIndex].Position < FColumns[DropTarget].Position then
FColumns[DragIndex].Position := FColumns[DropTarget].Position
else
FColumns[DragIndex].Position := FColumns[DropTarget].Position + 1;
end;
Tree.DoHeaderDragged(DragIndex, OldPosition);
end
else
Tree.DoHeaderDraggedOut(DragIndex, P);
DropTarget := NoColumn;
end;
Invalidate(nil);
end;
Result := True;
Message.Result := 0;
end;
case Message.Msg of
WM_LBUTTONUP :
with TWMLButtonUp(Message) do
begin
with TVirtualTreeColumnsCracker(FColumns) do
begin
if DownIndex > NoColumn then
HandleClick(Point(XPos, YPos), mbLeft, False, False);
end;
if FStates <> [] then
TBaseVirtualTreeCracker(FOwner).DoHeaderMouseUp(mbLeft, KeysToShiftState(Keys), XPos, YPos);
end;
WM_NCLBUTTONUP :
with TWMNCLButtonUp(Message) do
begin
P := FOwner.ScreenToClient(Point(XCursor, YCursor));
TVirtualTreeColumnsCracker(FColumns).HandleClick(P, mbLeft, False, False);
TBaseVirtualTreeCracker(FOwner).DoHeaderMouseUp(mbLeft, GetShiftState, P.X, P.Y + Integer(FHeight));
end;
end;
if FColumns.TrackIndex > NoColumn then
begin
if hsColumnWidthTracking in FStates then
DoAfterColumnWidthTracking(FColumns.TrackIndex);
Invalidate(Columns[FColumns.TrackIndex]);
FColumns.TrackIndex := NoColumn;
end;
with TVirtualTreeColumnsCracker(FColumns) do
begin
if DownIndex > NoColumn then
begin
Invalidate(FColumns[DownIndex]);
DownIndex := NoColumn;
end;
end;
if hsHeightTracking in FStates then
DoAfterHeightTracking;
FStates := FStates - [hsDragging, hsDragPending, hsColumnWidthTracking, hsColumnWidthTrackPending, hsHeightTracking, hsHeightTrackPending];
end; //WM_NCLBUTTONUP
//hovering, mouse leave detection
WM_NCMOUSEMOVE :
with TWMNCMouseMove(Message), TVirtualTreeColumnsCracker(FColumns) do
begin
P := Tree.ScreenToClient(Point(XCursor, YCursor));
Tree.DoHeaderMouseMove(GetShiftState, P.X, P.Y + Integer(FHeight));
if InHeader(P) and ((AdjustHoverColumn(P)) or ((DownIndex >= 0) and (HoverIndex <> DownIndex))) then
begin
//We need a mouse leave detection from here for the non client area.
//TODO: The best solution available would be the TrackMouseEvent API.
//With the drop of the support of Win95 totally and WinNT4 we should replace the timer.
Tree.StopTimer(HeaderTimer);
SetTimer(Tree.Handle, HeaderTimer, 50, nil);
//use Delphi's internal hint handling for header hints too
if hoShowHint in FOptions then
begin
//client coordinates!
XCursor := P.X;
YCursor := P.Y + Integer(FHeight);
Application.HintMouseMessage(FOwner, Message);
end;
end;
end;
WM_TIMER :
if TWMTimer(Message).TimerID = HeaderTimer then
begin
//determine current mouse position to check if it left the window
GetCursorPos(P);
P := Tree.ScreenToClient(P);
with TVirtualTreeColumnsCracker(FColumns) do
begin
if not InHeader(P) or ((DownIndex > NoColumn) and (HoverIndex <> DownIndex)) then
begin
Tree.StopTimer(HeaderTimer);
HoverIndex := NoColumn;
ClickIndex := NoColumn;
DownIndex := NoColumn;
CheckBoxHit := False;
Result := True;
Message.Result := 0;
Invalidate(nil);
end;
end;
end;
WM_MOUSEMOVE : //mouse capture and general message redirection
Result := HandleHeaderMouseMove(TWMMouseMove(Message));
WM_SETCURSOR :
//Feature: design-time header
if (FStates = []) then
begin
//Retrieve last cursor position (GetMessagePos does not work here, I don't know why).
GetCursorPos(P);
//Is the mouse in the header rectangle and near the splitters?
P := Tree.ScreenToClient(P);
IsHSplitterHit := HSplitterHit;
//in design-time header columns are always resizable
if (csDesigning in Tree.ComponentState) then
IsVSplitterHit := InHeaderSplitterArea(P)
else
IsVSplitterHit := InHeaderSplitterArea(P) and CanSplitterResize(P);
if IsVSplitterHit or IsHSplitterHit then
begin
NewCursor := Screen.Cursors[Tree.Cursor];
if IsVSplitterHit and ((hoHeightResize in FOptions) or (csDesigning in Tree.ComponentState)) then
NewCursor := Screen.Cursors[crVSplit]
else if IsHSplitterHit then
NewCursor := Screen.Cursors[crHSplit];
if not (csDesigning in Tree.ComponentState) then
Tree.DoGetHeaderCursor(NewCursor);
Result := NewCursor <> Screen.Cursors[crDefault];
if Result then
begin
WinApi.Windows.SetCursor(NewCursor);
Message.Result := 1;
end;
end;
end
else
begin
Message.Result := 1;
Result := True;
end;
WM_KEYDOWN, WM_KILLFOCUS :
if (Message.Msg = WM_KILLFOCUS) or (TWMKeyDown(Message).CharCode = VK_ESCAPE) then
begin
if hsDragging in FStates then
begin
ReleaseCapture;
FDragImage.EndDrag;
Exclude(FStates, hsDragging);
FColumns.DropTarget := NoColumn;
Invalidate(nil);
Result := True;
Message.Result := 0;
end
else
begin
if [hsColumnWidthTracking, hsHeightTracking] * FStates <> [] then
begin
ReleaseCapture;
if hsColumnWidthTracking in FStates then
DoAfterColumnWidthTracking(FColumns.TrackIndex);
if hsHeightTracking in FStates then
DoAfterHeightTracking;
Result := True;
Message.Result := 0;
end;
FStates := FStates - [hsColumnWidthTracking, hsColumnWidthTrackPending, hsHeightTracking, hsHeightTrackPending];
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.ImageListChange(Sender : TObject);
begin
if not (csDestroying in Tree.ComponentState) then
Invalidate(nil);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.PrepareDrag(P, Start : TPoint);
//Initializes dragging of the header, P is the current mouse postion and Start the initial mouse position.
var
Image : TBitmap;
ImagePos : TPoint;
DragColumn : TVirtualTreeColumn;
RTLOffset : Integer;
begin
//Determine initial position of drag image (screen coordinates).
FColumns.DropTarget := NoColumn;
Start := Tree.ScreenToClient(Start);
Inc(Start.Y, FHeight);
FColumns.DragIndex := FColumns.ColumnFromPosition(Start);
DragColumn := FColumns[FColumns.DragIndex];
Image := TBitmap.Create;
with Image do
try
PixelFormat := pf32Bit;
SetSize(DragColumn.Width, FHeight);
//Erase the entire image with the color key value, for the case not everything
//in the image is covered by the header image.
Canvas.Brush.Color := clBtnFace;
Canvas.FillRect(Rect(0, 0, Width, Height));
if Tree.UseRightToLeftAlignment then
RTLOffset := Tree.ComputeRTLOffset
else
RTLOffset := 0;
with DragColumn do
FColumns.PaintHeader(Canvas, Rect(Left, 0, Left + Width, Height), Point( - RTLOffset, 0), RTLOffset);
if Tree.UseRightToLeftAlignment then
ImagePos := Tree.ClientToScreen(Point(DragColumn.Left + Tree.ComputeRTLOffset(True), 0))
else
ImagePos := Tree.ClientToScreen(Point(DragColumn.Left, 0));
//Column rectangles are given in local window coordinates not client coordinates.
Dec(ImagePos.Y, FHeight);
if hoRestrictDrag in FOptions then
FDragImage.MoveRestriction := dmrHorizontalOnly
else
FDragImage.MoveRestriction := dmrNone;
FDragImage.PrepareDrag(Image, ImagePos, P, nil);
FDragImage.ShowDragImage;
finally
Image.Free;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.ReadColumns(Reader : TReader);
begin
Include(FStates, hsLoading);
Columns.Clear;
Reader.ReadValue;
Reader.ReadCollection(Columns);
Exclude(FStates, hsLoading);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.RecalculateHeader;
//Initiate a recalculation of the non-client area of the owner tree.
begin
if Tree.HandleAllocated then
begin
Tree.UpdateHeaderRect;
SetWindowPos(Tree.Handle, 0, 0, 0, 0, 0, SWP_FRAMECHANGED or SWP_NOMOVE or SWP_NOACTIVATE or SWP_NOOWNERZORDER or SWP_NOSENDCHANGING or SWP_NOSIZE or SWP_NOZORDER);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.RescaleHeader;
//Rescale the fixed elements (fixed columns, header itself) to FixedAreaConstraints.
var
FixedWidth, MaxFixedWidth, MinFixedWidth : Integer;
//--------------- local function --------------------------------------------
procedure ComputeConstraints;
var
I : TColumnIndex;
begin
with FColumns do
begin
I := GetFirstVisibleColumn;
while I > NoColumn do
begin
if (coFixed in FColumns[I].Options) and (FColumns[I].Width < FColumns[I].MinWidth) then
TVirtualTreeColumnCracker(FColumns[I]).InternalSetWidth(FColumns[I].MinWidth); //SetWidth has side effects and this bypasses them
I := GetNextVisibleColumn(I);
end;
FixedWidth := GetVisibleFixedWidth;
end;
with FFixedAreaConstraints do
begin
MinFixedWidth := (Tree.ClientWidth * FMinWidthPercent) div 100;
MaxFixedWidth := (Tree.ClientWidth * FMaxWidthPercent) div 100;
end;
end;
//----------- end local function --------------------------------------------
begin
if ([csLoading, csReading, csWriting, csDestroying] * Tree.ComponentState = []) and not (hsLoading in FStates) and Tree.HandleAllocated then
begin
Include(FStates, hsScaling);
SetHeight(FHeight);
RecalculateHeader;
with FFixedAreaConstraints do
if (FMaxWidthPercent > 0) or (FMinWidthPercent > 0) or (FMinHeightPercent > 0) or (FMaxHeightPercent > 0) then
begin
ComputeConstraints;
with FColumns do
if (FMaxWidthPercent > 0) and (FixedWidth > MaxFixedWidth) then
ResizeColumns(MaxFixedWidth - FixedWidth, 0, Count - 1, [coVisible, coFixed])
else if (FMinWidthPercent > 0) and (FixedWidth < MinFixedWidth) then
ResizeColumns(MinFixedWidth - FixedWidth, 0, Count - 1, [coVisible, coFixed]);
TVirtualTreeColumnsCracker(FColumns).UpdatePositions;
end;
Exclude(FStates, hsScaling);
Exclude(FStates, hsNeedScaling);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.UpdateMainColumn();
//Called once the load process of the owner tree is done.
begin
if FMainColumn < 0 then
MainColumn := 0;
if FMainColumn > FColumns.Count - 1 then
MainColumn := FColumns.Count - 1;
if (FMainColumn >= 0) and not (coVisible in Self.Columns[FMainColumn].Options) then
begin
//Issue #946: Choose new MainColumn if current one ist not visible
MainColumn := Self.Columns.GetFirstVisibleColumn();
end
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.UpdateSpringColumns;
var
I : TColumnIndex;
SpringCount : Integer;
Sign : Integer;
ChangeBy : Single;
Difference : Single;
NewAccumulator : Single;
begin
with Tree do
ChangeBy := HeaderRect.Right - HeaderRect.Left - FLastWidth;
if (hoAutoSpring in FOptions) and (FLastWidth <> 0) and (ChangeBy <> 0) then
begin
//Stay positive if downsizing the control.
if ChangeBy < 0 then
Sign := - 1
else
Sign := 1;
ChangeBy := Abs(ChangeBy);
//Count how many columns have spring enabled.
SpringCount := 0;
for I := 0 to FColumns.Count - 1 do
if [coVisible, coAutoSpring] * FColumns[I].Options = [coVisible, coAutoSpring] then
Inc(SpringCount);
if SpringCount > 0 then
begin
//Calculate the size to add/sub to each columns.
Difference := ChangeBy / SpringCount;
//Adjust the column's size accumulators and resize if the result is >= 1.
for I := 0 to FColumns.Count - 1 do
if [coVisible, coAutoSpring] * FColumns[I].Options = [coVisible, coAutoSpring] then
begin
//Sum up rest changes from previous runs and the amount from this one and store it in the
//column. If there is at least one pixel difference then do a resize and reset the accumulator.
NewAccumulator := FColumns[I].SpringRest + Difference;
//Set new width if at least one pixel size difference is reached.
if NewAccumulator >= 1 then
TVirtualTreeColumnCracker(FColumns[I]).SetWidth(FColumns[I].Width + (Trunc(NewAccumulator) * Sign));
FColumns[I].SpringRest := Frac(NewAccumulator);
//Keep track of the size count.
ChangeBy := ChangeBy - Difference;
//Exit loop if resize count drops below freezing point.
if ChangeBy < 0 then
Break;
end;
end;
end;
with Tree do
FLastWidth := HeaderRect.Right - HeaderRect.Left;
end;
//----------------------------------------------------------------------------------------------------------------------
type
//--- HACK WARNING!
//This type cast is a partial rewrite of the private section of TWriter. The purpose is to have access to
//the FPropPath member, which is otherwise not accessible. The reason why this access is needed is that
//with nested components this member contains unneeded property path information. These information prevent
//successful load of the stored properties later.
//In System.Classes.pas you can see that FPropPath is reset several times to '' to prevent this case for certain properies.
//Unfortunately, there is no clean way for us here to do the same.
{$HINTS off}
TWriterHack = class(TFiler)
private
FRootAncestor : TComponent;
FPropPath : string;
end;
{$HINTS on}
procedure TVTHeader.WriteColumns(Writer : TWriter);
//Write out the columns but take care for the case VT is a nested component.
var
LastPropPath : string;
begin
//Save last property path for restoration.
LastPropPath := TWriterHack(Writer).FPropPath;
try
//If VT is a nested component then this path contains the name of the parent component at this time
//(otherwise it is already empty). This path is then combined with the property name under which the tree
//is defined in the parent component. Unfortunately, the load code in System.Classes.pas does not consider this case
//is then unable to load this property.
TWriterHack(Writer).FPropPath := '';
Writer.WriteCollection(Columns);
finally
TWriterHack(Writer).FPropPath := LastPropPath;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.AllowFocus(ColumnIndex : TColumnIndex) : Boolean;
begin
Result := False;
if not FColumns.IsValidColumn(ColumnIndex) then
Exit; //Just in case.
Result := (coAllowFocus in FColumns[ColumnIndex].Options);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.Assign(Source : TPersistent);
begin
if Source is TVTHeader then
begin
AutoSizeIndex := TVTHeader(Source).AutoSizeIndex;
Background := TVTHeader(Source).Background;
Columns := TVTHeader(Source).Columns;
Font := TVTHeader(Source).Font;
FixedAreaConstraints.Assign(TVTHeader(Source).FixedAreaConstraints);
Height := TVTHeader(Source).Height;
Images := TVTHeader(Source).Images;
MainColumn := TVTHeader(Source).MainColumn;
Options := TVTHeader(Source).Options;
ParentFont := TVTHeader(Source).ParentFont;
PopupMenu := TVTHeader(Source).PopupMenu;
SortColumn := TVTHeader(Source).SortColumn;
SortDirection := TVTHeader(Source).SortDirection;
Style := TVTHeader(Source).Style;
RescaleHeader;
end
else
inherited;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.AutoFitColumns();
begin
AutoFitColumns(not Tree.IsUpdating);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.AutoFitColumns(Animated : Boolean; SmartAutoFitType : TSmartAutoFitType = smaUseColumnOption; RangeStartCol : Integer = NoColumn;
RangeEndCol : Integer = NoColumn);
//--------------- local functions -------------------------------------------
function GetUseSmartColumnWidth(ColumnIndex : TColumnIndex) : Boolean;
begin
case SmartAutoFitType of
smaAllColumns :
Result := True;
smaUseColumnOption :
Result := coSmartResize in FColumns.Items[ColumnIndex].Options;
else
Result := False;
end;
end;
//----------------------------------------------------------------------------
procedure DoAutoFitColumn(Column : TColumnIndex);
begin
with TVirtualTreeColumnsCracker(FColumns) do
if ([coResizable, coVisible] * Items[PositionToIndex[Column]].Options = [coResizable, coVisible]) and DoBeforeAutoFitColumn(PositionToIndex[Column], SmartAutoFitType) and
not Tree.OperationCanceled then
begin
if Animated then
AnimatedResize(PositionToIndex[Column], Tree.GetMaxColumnWidth(PositionToIndex[Column], GetUseSmartColumnWidth(PositionToIndex[Column])))
else
FColumns[PositionToIndex[Column]].Width := Tree.GetMaxColumnWidth(PositionToIndex[Column], GetUseSmartColumnWidth(PositionToIndex[Column]));
DoAfterAutoFitColumn(PositionToIndex[Column]);
end;
end;
//--------------- end local functions ----------------------------------------
var
I : Integer;
StartCol, EndCol : Integer;
begin
StartCol := Max(NoColumn + 1, RangeStartCol);
if RangeEndCol <= NoColumn then
EndCol := FColumns.Count - 1
else
EndCol := Min(RangeEndCol, FColumns.Count - 1);
if StartCol > EndCol then
Exit; //nothing to do
Tree.StartOperation(okAutoFitColumns);
FDoingAutoFitColumns := True;
try
if Assigned(Tree.OnBeforeAutoFitColumns) then
Tree.OnBeforeAutoFitColumns(Self, SmartAutoFitType);
for I := StartCol to EndCol do
DoAutoFitColumn(I);
if Assigned(Tree.OnAfterAutoFitColumns) then
Tree.OnAfterAutoFitColumns(Self);
finally
Tree.EndOperation(okAutoFitColumns);
Tree.Invalidate();
FDoingAutoFitColumns := False;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.InHeader(P : TPoint) : Boolean;
//Determines whether the given point (client coordinates!) is within the header rectangle (non-client coordinates).
var
R, RW : TRect;
begin
R := Tree.HeaderRect;
//Current position of the owner in screen coordinates.
GetWindowRect(Tree.Handle, RW);
//Convert to client coordinates.
MapWindowPoints(0, Tree.Handle, RW, 2);
//Consider the header within this rectangle.
OffsetRect(R, RW.Left, RW.Top);
Result := PtInRect(R, P);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.InHeaderSplitterArea(P : TPoint) : Boolean;
//Determines whether the given point (client coordinates!) hits the horizontal splitter area of the header.
var
R, RW : TRect;
begin
if (P.Y > 2) or (P.Y < - 2) or not (hoVisible in FOptions) then
Result := False
else
begin
R := Tree.HeaderRect;
Inc(R.Bottom, 2);
//Current position of the owner in screen coordinates.
GetWindowRect(Tree.Handle, RW);
//Convert to client coordinates.
MapWindowPoints(0, Tree.Handle, RW, 2);
//Consider the header within this rectangle.
OffsetRect(R, RW.Left, RW.Top);
Result := PtInRect(R, P);
end;
end;
procedure TVTHeader.InternalSetAutoSizeIndex(const Index : TColumnIndex);
begin
FAutoSizeIndex := index;
end;
procedure TVTHeader.InternalSetMainColumn(const Index : TColumnIndex);
begin
FMainColumn := index;
end;
procedure TVTHeader.InternalSetSortColumn(const Index : TColumnIndex);
begin
FSortColumn := index;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.Invalidate(Column : TVirtualTreeColumn; ExpandToBorder : Boolean = False; UpdateNowFlag : Boolean = False);
//Because the header is in the non-client area of the tree it needs some special handling in order to initiate its
//repainting.
//If ExpandToBorder is True then not only the given column but everything or (depending on hoFullRepaintOnResize) just
//everything to its right (or left, in RTL mode) will be invalidated (useful for resizing). This makes only sense when
//a column is given.
var
R, RW : TRect;
Flags : Cardinal;
begin
if (hoVisible in FOptions) and Tree.HandleAllocated then
with Tree do
begin
if Column = nil then
R := HeaderRect
else
begin
R := Column.GetRect;
if not (coFixed in Column.Options) then
OffsetRect(R, - EffectiveOffsetX, 0);
if UseRightToLeftAlignment then
OffsetRect(R, ComputeRTLOffset, 0);
if ExpandToBorder then
begin
if (hoFullRepaintOnResize in Header.Options) then
begin
R.Left := HeaderRect.Left;
R.Right := HeaderRect.Right;
end
else
begin
if UseRightToLeftAlignment then
R.Left := HeaderRect.Left
else
R.Right := HeaderRect.Right;
end;
end;
end;
R.Bottom := Tree.ClientHeight; //We want to repaint the entire column to bottom, not just the header
//Current position of the owner in screen coordinates.
GetWindowRect(Handle, RW);
//Consider the header within this rectangle.
OffsetRect(R, RW.Left, RW.Top);
//Expressed in client coordinates (because RedrawWindow wants them so, they will actually become negative).
MapWindowPoints(0, Handle, R, 2);
Flags := RDW_FRAME or RDW_INVALIDATE or RDW_VALIDATE or RDW_NOINTERNALPAINT or RDW_NOERASE or RDW_NOCHILDREN;
if UpdateNowFlag then
Flags := Flags or RDW_UPDATENOW;
RedrawWindow(Handle, @R, 0, Flags);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.LoadFromStream(const Stream : TStream);
//restore the state of the header from the given stream
var
Dummy, Version : Integer;
S : AnsiString;
OldOptions : TVTHeaderOptions;
begin
Include(FStates, hsLoading);
with Stream do
try
//Switch off all options which could influence loading the columns (they will be later set again).
OldOptions := FOptions;
FOptions := [];
//Determine whether the stream contains data without a version number.
ReadBuffer(Dummy, SizeOf(Dummy));
if Dummy > - 1 then
begin
//Seek back to undo the read operation if this is an old stream format.
Seek( - SizeOf(Dummy), soFromCurrent);
Version := - 1;
end
else //Read version number if this is a "versionized" format.
ReadBuffer(Version, SizeOf(Version));
Columns.LoadFromStream(Stream, Version);
ReadBuffer(Dummy, SizeOf(Dummy));
AutoSizeIndex := Dummy;
ReadBuffer(Dummy, SizeOf(Dummy));
Background := Dummy;
ReadBuffer(Dummy, SizeOf(Dummy));
Height := Dummy;
ReadBuffer(Dummy, SizeOf(Dummy));
FOptions := OldOptions;
Options := TVTHeaderOptions(Dummy);
//PopupMenu is neither saved nor restored
ReadBuffer(Dummy, SizeOf(Dummy));
Style := TVTHeaderStyle(Dummy);
//TFont has no own save routine so we do it manually
with Font do
begin
ReadBuffer(Dummy, SizeOf(Dummy));
Color := Dummy;
ReadBuffer(Dummy, SizeOf(Dummy));
Height := Dummy;
ReadBuffer(Dummy, SizeOf(Dummy));
SetLength(S, Dummy);
ReadBuffer(PAnsiChar(S)^, Dummy);
Name := UTF8ToString(S);
ReadBuffer(Dummy, SizeOf(Dummy));
Pitch := TFontPitch(Dummy);
ReadBuffer(Dummy, SizeOf(Dummy));
Style := TFontStyles(Byte(Dummy));
end;
//Read data introduced by stream version 1+.
if Version > 0 then
begin
ReadBuffer(Dummy, SizeOf(Dummy));
MainColumn := Dummy;
ReadBuffer(Dummy, SizeOf(Dummy));
SortColumn := Dummy;
ReadBuffer(Dummy, SizeOf(Dummy));
SortDirection := TSortDirection(Byte(Dummy));
end;
//Read data introduced by stream version 5+.
if Version > 4 then
begin
ReadBuffer(Dummy, SizeOf(Dummy));
ParentFont := Boolean(Dummy);
ReadBuffer(Dummy, SizeOf(Dummy));
FMaxHeight := Integer(Dummy);
ReadBuffer(Dummy, SizeOf(Dummy));
FMinHeight := Integer(Dummy);
ReadBuffer(Dummy, SizeOf(Dummy));
FDefaultHeight := Integer(Dummy);
with FFixedAreaConstraints do
begin
ReadBuffer(Dummy, SizeOf(Dummy));
FMaxHeightPercent := TVTConstraintPercent(Dummy);
ReadBuffer(Dummy, SizeOf(Dummy));
FMaxWidthPercent := TVTConstraintPercent(Dummy);
ReadBuffer(Dummy, SizeOf(Dummy));
FMinHeightPercent := TVTConstraintPercent(Dummy);
ReadBuffer(Dummy, SizeOf(Dummy));
FMinWidthPercent := TVTConstraintPercent(Dummy);
end;
end;
finally
Exclude(FStates, hsLoading);
RecalculateHeader();
Tree.DoColumnResize(NoColumn);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVTHeader.ResizeColumns(ChangeBy : Integer; RangeStartCol : TColumnIndex; RangeEndCol : TColumnIndex; Options : TVTColumnOptions = [coVisible]) : Integer;
//Distribute the given width change to a range of columns. A 'fair' way is used to distribute ChangeBy to the columns,
//while ensuring that everything that can be distributed will be distributed.
var
Start, I : TColumnIndex;
ColCount, ToGo, Sign, Rest, MaxDelta, Difference : Integer;
Constraints, Widths : array of Integer;
BonusPixel : Boolean;
//--------------- local functions -------------------------------------------
function IsResizable(Column : TColumnIndex) : Boolean;
begin
if BonusPixel then
Result := Widths[Column - RangeStartCol] < Constraints[Column - RangeStartCol]
else
Result := Widths[Column - RangeStartCol] > Constraints[Column - RangeStartCol];
end;
//---------------------------------------------------------------------------
procedure IncDelta(Column : TColumnIndex);
begin
if BonusPixel then
Inc(MaxDelta, FColumns[Column].MaxWidth - Widths[Column - RangeStartCol])
else
Inc(MaxDelta, Widths[Column - RangeStartCol] - Constraints[Column - RangeStartCol]);
end;
//---------------------------------------------------------------------------
function ChangeWidth(Column : TColumnIndex; Delta : Integer) : Integer;
begin
if Delta > 0 then
Delta := Min(Delta, Constraints[Column - RangeStartCol] - Widths[Column - RangeStartCol])
else
Delta := Max(Delta, Constraints[Column - RangeStartCol] - Widths[Column - RangeStartCol]);
Inc(Widths[Column - RangeStartCol], Delta);
Dec(ToGo, Abs(Delta));
Result := Abs(Delta);
end;
//---------------------------------------------------------------------------
function ReduceConstraints : Boolean;
var
MaxWidth, MaxReserveCol, Column : TColumnIndex;
begin
Result := True;
if not (hsScaling in FStates) or BonusPixel then
Exit;
MaxWidth := 0;
MaxReserveCol := NoColumn;
for Column := RangeStartCol to RangeEndCol do
if (Options * FColumns[Column].Options = Options) and (FColumns[Column].Width > MaxWidth) then
begin
MaxWidth := Widths[Column - RangeStartCol];
MaxReserveCol := Column;
end;
if (MaxReserveCol <= NoColumn) or (Constraints[MaxReserveCol - RangeStartCol] <= 10) then
Result := False
else
Dec(Constraints[MaxReserveCol - RangeStartCol], Constraints[MaxReserveCol - RangeStartCol] div 10);
end;
//----------- end local functions -------------------------------------------
begin
Result := 0;
if (ChangeBy <> 0) and (RangeEndCol >= 0) then // RangeEndCol == -1 means no columns, so nothing to do
begin
//Do some initialization here
BonusPixel := ChangeBy > 0;
Sign := IfThen(BonusPixel, 1, - 1);
Start := IfThen(BonusPixel, RangeStartCol, RangeEndCol);
ToGo := Abs(ChangeBy);
SetLength(Widths, RangeEndCol - RangeStartCol + 1);
SetLength(Constraints, RangeEndCol - RangeStartCol + 1);
for I := RangeStartCol to RangeEndCol do
begin
Widths[I - RangeStartCol] := FColumns[I].Width;
Constraints[I - RangeStartCol] := IfThen(BonusPixel, FColumns[I].MaxWidth, FColumns[I].MinWidth);
end;
repeat
repeat
MaxDelta := 0;
ColCount := 0;
for I := RangeStartCol to RangeEndCol do
if (Options * FColumns[I].Options = Options) and IsResizable(I) then
begin
Inc(ColCount);
IncDelta(I);
end;
if MaxDelta < Abs(ChangeBy) then
if not ReduceConstraints then
Break;
until (MaxDelta >= Abs(ChangeBy)) or not (hsScaling in FStates);
if ColCount = 0 then
Break;
ToGo := Min(ToGo, MaxDelta);
Difference := ToGo div ColCount;
Rest := ToGo mod ColCount;
if Difference > 0 then
for I := RangeStartCol to RangeEndCol do
if (Options * FColumns[I].Options = Options) and IsResizable(I) then
ChangeWidth(I, Difference * Sign);
//Now distribute Rest.
I := Start;
while Rest > 0 do
begin
if (Options * FColumns[I].Options = Options) and IsResizable(I) then
if FColumns[I].BonusPixel <> BonusPixel then
begin
Dec(Rest, ChangeWidth(I, Sign));
FColumns[I].BonusPixel := BonusPixel;
end;
Inc(I, Sign);
if (BonusPixel and (I > RangeEndCol)) or (not BonusPixel and (I < RangeStartCol)) then
begin
for I := RangeStartCol to RangeEndCol do
if Options * FColumns[I].Options = Options then
FColumns[I].BonusPixel := not FColumns[I].BonusPixel;
I := Start;
end;
end;
until ToGo <= 0;
//Now set the computed widths. We also compute the result here.
Include(FStates, hsResizing);
for I := RangeStartCol to RangeEndCol do
if (Options * FColumns[I].Options = Options) then
begin
Inc(Result, Widths[I - RangeStartCol] - FColumns[I].Width);
TVirtualTreeColumnCracker(FColumns[I]).SetWidth(Widths[I - RangeStartCol]);
end;
Exclude(FStates, hsResizing);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.RestoreColumns;
//Restores all columns to their width which they had before they have been auto fitted.
var
I : TColumnIndex;
begin
with TVirtualTreeColumnsCracker(FColumns) do
for I := Count - 1 downto 0 do
if [coResizable, coVisible] * Items[PositionToIndex[I]].Options = [coResizable, coVisible] then
Items[I].RestoreLastWidth;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVTHeader.SaveToStream(const Stream : TStream);
//Saves the complete state of the header into the provided stream.
var
Dummy : Integer;
Tmp : AnsiString;
begin
with Stream do
begin
//In previous version of VT was no header stream version defined.
//For feature enhancements it is necessary, however, to know which stream
//format we are trying to load.
//In order to distict from non-version streams an indicator is inserted.
Dummy := - 1;
WriteBuffer(Dummy, SizeOf(Dummy));
//Write current stream version number, nothing more is required at the time being.
Dummy := VTHeaderStreamVersion;
WriteBuffer(Dummy, SizeOf(Dummy));
//Save columns in case they depend on certain options (like auto size).
Columns.SaveToStream(Stream);
Dummy := FAutoSizeIndex;
WriteBuffer(Dummy, SizeOf(Dummy));
Dummy := FBackgroundColor;
WriteBuffer(Dummy, SizeOf(Dummy));
Dummy := FHeight;
WriteBuffer(Dummy, SizeOf(Dummy));
Dummy := Integer(FOptions);
WriteBuffer(Dummy, SizeOf(Dummy));
//PopupMenu is neither saved nor restored
Dummy := Ord(FStyle);
WriteBuffer(Dummy, SizeOf(Dummy));
//TFont has no own save routine so we do it manually
with Font do
begin
Dummy := Color;
WriteBuffer(Dummy, SizeOf(Dummy));
//Need only to write one: size or height, I decided to write height.
Dummy := Height;
WriteBuffer(Dummy, SizeOf(Dummy));
Tmp := UTF8Encode(Name);
Dummy := Length(Tmp);
WriteBuffer(Dummy, SizeOf(Dummy));
WriteBuffer(PAnsiChar(Tmp)^, Dummy);
Dummy := Ord(Pitch);
WriteBuffer(Dummy, SizeOf(Dummy));
Dummy := Byte(Style);
WriteBuffer(Dummy, SizeOf(Dummy));
end;
//Data introduced by stream version 1.
Dummy := FMainColumn;
WriteBuffer(Dummy, SizeOf(Dummy));
Dummy := FSortColumn;
WriteBuffer(Dummy, SizeOf(Dummy));
Dummy := Byte(FSortDirection);
WriteBuffer(Dummy, SizeOf(Dummy));
//Data introduced by stream version 5.
Dummy := Integer(ParentFont);
WriteBuffer(Dummy, SizeOf(Dummy));
Dummy := Integer(FMaxHeight);
WriteBuffer(Dummy, SizeOf(Dummy));
Dummy := Integer(FMinHeight);
WriteBuffer(Dummy, SizeOf(Dummy));
Dummy := Integer(FDefaultHeight);
WriteBuffer(Dummy, SizeOf(Dummy));
with FFixedAreaConstraints do
begin
Dummy := Integer(FMaxHeightPercent);
WriteBuffer(Dummy, SizeOf(Dummy));
Dummy := Integer(FMaxWidthPercent);
WriteBuffer(Dummy, SizeOf(Dummy));
Dummy := Integer(FMinHeightPercent);
WriteBuffer(Dummy, SizeOf(Dummy));
Dummy := Integer(FMinWidthPercent);
WriteBuffer(Dummy, SizeOf(Dummy));
end;
end;
end;
{ TVTHeaderHelper }
function TVTHeaderHelper.Tree : TBaseVirtualTreeCracker;
begin
Result := TBaseVirtualTreeCracker(Self.FOwner);
end;
//----------------- TVirtualTreeColumn ---------------------------------------------------------------------------------
constructor TVirtualTreeColumn.Create(Collection : TCollection);
begin
FMinWidth := 10;
FMaxWidth := 10000;
FImageIndex := - 1;
FMargin := 4;
FSpacing := cDefaultColumnSpacing;
FText := '';
FOptions := DefaultColumnOptions;
FAlignment := taLeftJustify;
FBiDiMode := bdLeftToRight;
FColor := clWindow;
FLayout := blGlyphLeft;
FBonusPixel := False;
FCaptionAlignment := taLeftJustify;
FCheckType := ctCheckBox;
FCheckState := csUncheckedNormal;
FCheckBox := False;
FHasImage := False;
FDefaultSortDirection := sdAscending;
FEditNextColumn := - 1;
inherited Create(Collection);
if Assigned(Owner) then
begin
FWidth := Owner.DefaultWidth;
FLastWidth := Owner.DefaultWidth;
FPosition := Owner.Count - 1;
end;
end;
procedure TVirtualTreeColumn.SetCollection(Value : TCollection);
begin
inherited;
// Read parent bidi mode and color values as default values.
ParentBiDiModeChanged;
ParentColorChanged;
end;
//----------------------------------------------------------------------------------------------------------------------
destructor TVirtualTreeColumn.Destroy;
var
I : Integer;
ai : TColumnIndex;
sc : TColumnIndex;
//--------------- local function ---------------------------------------------
procedure AdjustColumnIndex(var ColumnIndex : TColumnIndex);
begin
if Index = ColumnIndex then
ColumnIndex := NoColumn
else
if Index < ColumnIndex then
Dec(ColumnIndex);
end;
//--------------- end local function -----------------------------------------
begin
// Check if this column is somehow referenced by its collection parent or the header.
with Owner do
begin
// If the columns collection object is currently deleting all columns
// then we don't need to check the various cached indices individually.
if not FClearing then
begin
TreeViewControl.CancelEditNode;
IndexChanged(Index, - 1);
AdjustColumnIndex(FHoverIndex);
AdjustColumnIndex(FDownIndex);
AdjustColumnIndex(FTrackIndex);
AdjustColumnIndex(FClickIndex);
with Header do
begin
ai := AutoSizeIndex;
AdjustColumnIndex(ai);
InternalSetAutoSizeIndex(ai);
if Index = MainColumn then
begin
// If the current main column is about to be destroyed then we have to find a new main column.
InternalSetMainColumn(NoColumn); //SetColumn has side effects we want to avoid here.
for I := 0 to Count - 1 do
if I <> Index then
begin
InternalSetMainColumn(I);
Break;
end;
end;
sc := SortColumn;
AdjustColumnIndex(sc);
InternalSetSortColumn(sc);
end;
end;
end;
inherited;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumn.GetCaptionAlignment : TAlignment;
begin
if coUseCaptionAlignment in FOptions then
Result := FCaptionAlignment
else
Result := FAlignment;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumn.GetCaptionWidth : TDimension;
var
Theme : HTHEME;
AdvancedOwnerDraw : Boolean;
PaintInfo : THeaderPaintInfo;
RequestedElements : THeaderPaintElements;
TextSize : TSize;
HeaderGlyphSize : TPoint;
UseText : Boolean;
R : TRect;
begin
AdvancedOwnerDraw := (hoOwnerDraw in Header.Options) and Assigned(TreeViewControl.OnAdvancedHeaderDraw) and Assigned(TreeViewControl.OnHeaderDrawQueryElements) and
not (csDesigning in TreeViewControl.ComponentState);
PaintInfo.Column := Self;
PaintInfo.TargetCanvas := Owner.HeaderBitmap.Canvas;
with PaintInfo, Column do
begin
ShowHeaderGlyph := (hoShowImages in Header.Options) and ((Assigned(Header.Images) and (FImageIndex > - 1)) or FCheckBox);
ShowSortGlyph := ((Header.SortColumn > - 1) and (Self = Owner.Items[Header.SortColumn])) and (hoShowSortGlyphs in Header.Options);
// This path for text columns or advanced owner draw.
// See if the application wants to draw part of the header itself.
RequestedElements := [];
if AdvancedOwnerDraw then
begin
PaintInfo.Column := Self;
TreeViewControl.DoHeaderDrawQueryElements(PaintInfo, RequestedElements);
end;
end;
UseText := Length(FText) > 0;
// If nothing is to show then don't waste time with useless preparation.
if not (UseText or PaintInfo.ShowHeaderGlyph or PaintInfo.ShowSortGlyph) then
Exit(0);
// Calculate sizes of the involved items.
with Header do
begin
if PaintInfo.ShowHeaderGlyph then
if not FCheckBox then
begin
if Assigned(Images) then
HeaderGlyphSize := Point(Images.Width, Images.Height);
end
else
with Self.TreeViewControl do
begin
if Assigned(CheckImages) then
HeaderGlyphSize := Point(CheckImages.Width, CheckImages.Height);
end
else
HeaderGlyphSize := Point(0, 0);
if PaintInfo.ShowSortGlyph then
begin
if tsUseExplorerTheme in Self.TreeViewControl.TreeStates then
begin
R := Rect(0, 0, 100, 100);
Theme := OpenThemeData(Self.TreeViewControl.Handle, 'HEADER');
GetThemePartSize(Theme, PaintInfo.TargetCanvas.Handle, HP_HEADERSORTARROW, HSAS_SORTEDUP, @R, TS_TRUE, PaintInfo.SortGlyphSize);
CloseThemeData(Theme);
end
else
begin
PaintInfo.SortGlyphSize.cx := Self.TreeViewControl.ScaledPixels(16);
PaintInfo.SortGlyphSize.cy := Self.TreeViewControl.ScaledPixels(4);
end;
end
else
begin
PaintInfo.SortGlyphSize.cx := 0;
PaintInfo.SortGlyphSize.cy := 0;
end;
end;
if UseText then
begin
GetTextExtentPoint32W(PaintInfo.TargetCanvas.Handle, PWideChar(FText), Length(FText), TextSize);
Inc(TextSize.cx, 2);
end
else
begin
TextSize.cx := 0;
TextSize.cy := 0;
end;
// if CalculateTextRect then
Result := TextSize.cx;
if PaintInfo.ShowHeaderGlyph then
if Layout in [blGlyphLeft, blGlyphRight] then
Inc(Result, HeaderGlyphSize.X + FSpacing)
else // if Layout in [ blGlyphTop, blGlyphBottom] then
Result := Max(Result, HeaderGlyphSize.X);
if PaintInfo.ShowSortGlyph then
Inc(Result, PaintInfo.SortGlyphSize.cx + FSpacing + 2); // without this +2, there is a slight movement of the sort glyph when expanding the column
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumn.GetLeft : Integer;
begin
Result := FLeft;
if [coVisible, coFixed] * FOptions <> [coVisible, coFixed] then
Dec(Result, TreeViewControl.EffectiveOffsetX);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumn.IsBiDiModeStored : Boolean;
begin
Result := not (coParentBidiMode in FOptions);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumn.IsCaptionAlignmentStored : Boolean;
begin
Result := coUseCaptionAlignment in FOptions;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumn.IsColorStored : Boolean;
begin
Result := not (coParentColor in FOptions);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetAlignment(const Value : TAlignment);
begin
if FAlignment <> Value then
begin
FAlignment := Value;
Changed(False);
// Setting the alignment affects also the tree, hence invalidate it too.
TreeViewControl.Invalidate;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetBiDiMode(Value : TBiDiMode);
begin
if Value <> FBiDiMode then
begin
FBiDiMode := Value;
Exclude(FOptions, coParentBidiMode);
Changed(False);
// Setting the alignment affects also the tree, hence invalidate it too.
TreeViewControl.Invalidate;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetCaptionAlignment(const Value : TAlignment);
begin
if not (coUseCaptionAlignment in FOptions) or (FCaptionAlignment <> Value) then
begin
FCaptionAlignment := Value;
Include(FOptions, coUseCaptionAlignment);
// Setting the alignment affects also the tree, hence invalidate it too.
Header.Invalidate(Self);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetColor(const Value : TColor);
begin
if FColor <> Value then
begin
FColor := Value;
Exclude(FOptions, coParentColor);
Exclude(FOptions, coStyleColor); // Issue #919
Changed(False);
TreeViewControl.Invalidate;
end;
end;
function TVirtualTreeColumn.GetEffectiveColor() : TColor;
// Returns the color that should effectively be used as background color for this
// column considering all flags in the TVirtualTreeColumn.Options property
begin
if (coParentColor in Options) or ((coStyleColor in Options) and TreeViewControl.VclStyleEnabled) then
Result := TreeViewControl.Colors.BackGroundColor
else
Result := Self.Color;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetCheckBox(Value : Boolean);
begin
if Value <> FCheckBox then
begin
FCheckBox := Value;
if Value and (csDesigning in TreeViewControl.ComponentState) then
Header.Options := Header.Options + [hoShowImages];
Changed(False);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetCheckState(Value : TCheckState);
begin
if Value <> FCheckState then
begin
FCheckState := Value;
Changed(False);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetCheckType(Value : TCheckType);
begin
if Value <> FCheckType then
begin
FCheckType := Value;
Changed(False);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetImageIndex(Value : TImageIndex);
begin
if Value <> FImageIndex then
begin
FImageIndex := Value;
Changed(False);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetLayout(Value : TVTHeaderColumnLayout);
begin
if FLayout <> Value then
begin
FLayout := Value;
Changed(False);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetMargin(Value : Integer);
begin
// Compatibility setting for -1.
if Value < 0 then
Value := 4;
if FMargin <> Value then
begin
FMargin := Value;
Changed(False);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetMaxWidth(Value : Integer);
begin
if Value < FMinWidth then
Value := FMinWidth;
FMaxWidth := Value;
SetWidth(FWidth);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetMinWidth(Value : Integer);
begin
if Value < 0 then
Value := 0;
if Value > FMaxWidth then
Value := FMaxWidth;
FMinWidth := Value;
SetWidth(FWidth);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetOptions(Value : TVTColumnOptions);
var
ToBeSet,
ToBeCleared : TVTColumnOptions;
VisibleChanged,
lParentColorSet : Boolean;
begin
if FOptions <> Value then
begin
ToBeCleared := FOptions - Value;
ToBeSet := Value - FOptions;
FOptions := Value;
VisibleChanged := coVisible in (ToBeSet + ToBeCleared);
lParentColorSet := coParentColor in ToBeSet;
if coParentBidiMode in ToBeSet then
ParentBiDiModeChanged;
if lParentColorSet then
begin
Include(FOptions, coStyleColor); // Issue #919
ParentColorChanged();
end;
if coAutoSpring in ToBeSet then
FSpringRest := 0;
if coVisible in ToBeCleared then
Header.UpdateMainColumn(); // Fixes issue #946
if ((coFixed in ToBeSet) or (coFixed in ToBeCleared)) and (coVisible in FOptions) then
Header.RescaleHeader;
Changed(False);
// Need to repaint and adjust the owner tree too.
if not (csLoading in TreeViewControl.ComponentState) and (VisibleChanged or lParentColorSet) and (Owner.UpdateCount = 0) and TreeViewControl.HandleAllocated then
begin
TreeViewControl.Invalidate();
if VisibleChanged then
begin
TreeViewControl.DoColumnVisibilityChanged(Self.Index, coVisible in ToBeSet);
TreeViewControl.UpdateHorizontalScrollBar(False);
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetPosition(Value : TColumnPosition);
var
Temp : TColumnIndex;
begin
if (csLoading in TreeViewControl.ComponentState) or (Owner.UpdateCount > 0) then
// Only cache the position for final fixup when loading from DFM.
FPosition := Value
else
begin
if Value >= TColumnPosition(Collection.Count) then
Value := Collection.Count - 1;
if FPosition <> Value then
begin
with Owner do
begin
InitializePositionArray;
TreeViewControl.CancelEditNode;
AdjustPosition(Self, Value);
Self.Changed(False);
// Need to repaint.
with Self.Header do
begin
if (UpdateCount = 0) and TreeViewControl.HandleAllocated then
begin
Invalidate(Self);
TreeViewControl.Invalidate;
end;
end;
end;
// If the moved column is now within the fixed columns then we make it fixed as well. If it's not
// we clear the fixed state (in case that fixed column is moved outside fixed area).
if (coFixed in FOptions) and (FPosition > 0) then
Temp := Owner.ColumnFromPosition(FPosition - 1)
else
Temp := Owner.ColumnFromPosition(FPosition + 1);
if Temp <> NoColumn then
begin
if coFixed in Owner[Temp].Options then
Options := Options + [coFixed]
else
Options := Options - [coFixed];
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetSpacing(Value : Integer);
begin
if FSpacing <> Value then
begin
FSpacing := Value;
Changed(False);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetStyle(Value : TVirtualTreeColumnStyle);
begin
if FStyle <> Value then
begin
FStyle := Value;
Changed(False);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetText(const Value : string);
begin
if FText <> Value then
begin
FText := Value;
FCaptionText := '';
Changed(False);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SetWidth(Value : Integer);
var
EffectiveMaxWidth,
EffectiveMinWidth,
TotalFixedMaxWidth,
TotalFixedMinWidth : Integer;
I : TColumnIndex;
begin
if not (hsScaling in Header.States) then
if ([coVisible, coFixed] * FOptions = [coVisible, coFixed]) then
begin
with Header, FixedAreaConstraints, TreeView do
begin
TotalFixedMinWidth := 0;
TotalFixedMaxWidth := 0;
for I := 0 to Columns.Count - 1 do
if ([coVisible, coFixed] * Columns[I].Options = [coVisible, coFixed]) then
begin
Inc(TotalFixedMaxWidth, Columns[I].MaxWidth);
Inc(TotalFixedMinWidth, Columns[I].MinWidth);
end;
if HandleAllocated then // Prevent premature creation of window handle, see issue #1073
begin
// The percentage values have precedence over the pixel values.
If MaxWidthPercent > 0 then
TotalFixedMinWidth := Min((ClientWidth * MaxWidthPercent) div 100, TotalFixedMinWidth);
If MinWidthPercent > 0 then
TotalFixedMaxWidth := Max((ClientWidth * MinWidthPercent) div 100, TotalFixedMaxWidth);
EffectiveMaxWidth := Min(TotalFixedMaxWidth - (Columns.GetVisibleFixedWidth - Self.FWidth), FMaxWidth);
EffectiveMinWidth := Max(TotalFixedMinWidth - (Columns.GetVisibleFixedWidth - Self.FWidth), FMinWidth);
Value := Min(Max(Value, EffectiveMinWidth), EffectiveMaxWidth);
if MinWidthPercent > 0 then
Value := Max((ClientWidth * MinWidthPercent) div 100 - Columns.GetVisibleFixedWidth + Self.FWidth, Value);
if MaxWidthPercent > 0 then
Value := Min((ClientWidth * MaxWidthPercent) div 100 - Columns.GetVisibleFixedWidth + Self.FWidth, Value);
end;// if HandleAllocated
end;
end
else
Value := Min(Max(Value, FMinWidth), FMaxWidth);
if FWidth <> Value then
begin
FLastWidth := FWidth;
if not (hsResizing in Header.States) then
FBonusPixel := False;
if not (hoAutoResize in Header.Options) or (Index <> Header.AutoSizeIndex) then
begin
FWidth := Value;
Owner.UpdatePositions;
end;
if not (csLoading in TreeViewControl.ComponentState) and (Owner.UpdateCount = 0) then
begin
if hoAutoResize in Header.Options then
Owner.AdjustAutoSize(Index);
TreeViewControl.DoColumnResize(Index);
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.ChangeScale(M, D : TDimension; isDpiChange : Boolean);
begin
FMinWidth := MulDiv(FMinWidth, M, D);
FMaxWidth := MulDiv(FMaxWidth, M, D);
FSpacing := MulDiv(FSpacing, M, D);
Self.Width := MulDiv(Self.Width, M, D);
end;
procedure TVirtualTreeColumn.ComputeHeaderLayout(var PaintInfo : THeaderPaintInfo; DrawFormat : Cardinal; CalculateTextRect : Boolean = False);
// The layout of a column header is determined by a lot of factors. This method takes them all into account and
// determines all necessary positions and bounds:
// - for the header text
// - the header glyph
// - the sort glyph
var
TextSize : TSize;
TextPos,
ClientSize,
HeaderGlyphSize : TPoint;
CurrentAlignment : TAlignment;
MinLeft,
MaxRight,
TextSpacing : Integer;
UseText : Boolean;
R : TRect;
Theme : HTHEME;
begin
UseText := Length(FText) > 0;
// If nothing is to show then don't waste time with useless preparation.
if not (UseText or PaintInfo.ShowHeaderGlyph or PaintInfo.ShowSortGlyph) then
Exit;
CurrentAlignment := CaptionAlignment;
if FBiDiMode <> bdLeftToRight then
ChangeBiDiModeAlignment(CurrentAlignment);
// Calculate sizes of the involved items.
ClientSize := Point(PaintInfo.PaintRectangle.Right - PaintInfo.PaintRectangle.Left, PaintInfo.PaintRectangle.Bottom - PaintInfo.PaintRectangle.Top);
with Owner, Header do
begin
if PaintInfo.ShowHeaderGlyph then
if not FCheckBox then
HeaderGlyphSize := Point(Images.Width, Images.Height)
else
with Self.TreeViewControl do
begin
if Assigned(CheckImages) then
HeaderGlyphSize := Point(CheckImages.Width, CheckImages.Height);
end
else
HeaderGlyphSize := Point(0, 0);
if PaintInfo.ShowSortGlyph then
begin
if tsUseExplorerTheme in Self.TreeViewControl.TreeStates then
begin
R := Rect(0, 0, 100, 100);
Theme := OpenThemeData(TreeViewControl.Handle, 'HEADER');
GetThemePartSize(Theme, PaintInfo.TargetCanvas.Handle, HP_HEADERSORTARROW, HSAS_SORTEDUP, @R, TS_TRUE, PaintInfo.SortGlyphSize);
CloseThemeData(Theme);
end
else
begin
PaintInfo.SortGlyphSize.cx := Self.TreeViewControl.ScaledPixels(16);
PaintInfo.SortGlyphSize.cy := Self.TreeViewControl.ScaledPixels(4);
end;
// In any case, the sort glyph is vertically centered.
PaintInfo.SortGlyphPos.Y := (ClientSize.Y - PaintInfo.SortGlyphSize.cy) div 2;
end
else
begin
PaintInfo.SortGlyphSize.cx := 0;
PaintInfo.SortGlyphSize.cy := 0;
end;
end;
if UseText then
begin
if not (coWrapCaption in FOptions) then
begin
FCaptionText := FText;
GetTextExtentPoint32W(PaintInfo.TargetCanvas.Handle, PWideChar(FText), Length(FText), TextSize);
Inc(TextSize.cx, 2);
PaintInfo.TextRectangle := Rect(0, 0, TextSize.cx, TextSize.cy);
end
else
begin
R := PaintInfo.PaintRectangle;
if FCaptionText = '' then
FCaptionText := WrapString(PaintInfo.TargetCanvas.Handle, FText, R, DT_RTLREADING and DrawFormat <> 0, DrawFormat);
GetStringDrawRect(PaintInfo.TargetCanvas.Handle, FCaptionText, R, DrawFormat);
TextSize.cx := PaintInfo.PaintRectangle.Right - PaintInfo.PaintRectangle.Left;
TextSize.cy := R.Bottom - R.Top;
PaintInfo.TextRectangle := Rect(0, 0, TextSize.cx, TextSize.cy);
end;
TextSpacing := FSpacing;
end
else
begin
TextSpacing := 0;
TextSize.cx := 0;
TextSize.cy := 0;
end;
// Check first for the special case where nothing is shown except the sort glyph.
if PaintInfo.ShowSortGlyph and not (UseText or PaintInfo.ShowHeaderGlyph) then
begin
// Center the sort glyph in the available area if nothing else is there.
PaintInfo.SortGlyphPos := Point((ClientSize.X - PaintInfo.SortGlyphSize.cx) div 2, (ClientSize.Y - PaintInfo.SortGlyphSize.cy) div 2);
end
else
begin
// Determine extents of text and glyph and calculate positions which are clear from the layout.
if (Layout in [blGlyphLeft, blGlyphRight]) or not PaintInfo.ShowHeaderGlyph then
begin
PaintInfo.GlyphPos.Y := (ClientSize.Y - HeaderGlyphSize.Y) div 2;
// If the text is taller than the given height, perform no vertical centration as this
// would make the text even less readable.
//Using Max() fixes badly positioned text if Extra Large fonts have been activated in the Windows display options
TextPos.Y := Max( - 5, (ClientSize.Y - TextSize.cy) div 2);
end
else
begin
if Layout = blGlyphTop then
begin
PaintInfo.GlyphPos.Y := (ClientSize.Y - HeaderGlyphSize.Y - TextSize.cy - TextSpacing) div 2;
TextPos.Y := PaintInfo.GlyphPos.Y + HeaderGlyphSize.Y + TextSpacing;
end
else
begin
TextPos.Y := (ClientSize.Y - HeaderGlyphSize.Y - TextSize.cy - TextSpacing) div 2;
PaintInfo.GlyphPos.Y := TextPos.Y + TextSize.cy + TextSpacing;
end;
end;
// Each alignment needs special consideration.
case CurrentAlignment of
taLeftJustify :
begin
MinLeft := FMargin;
if PaintInfo.ShowSortGlyph and (FBiDiMode <> bdLeftToRight) then
begin
// In RTL context is the sort glyph placed on the left hand side.
PaintInfo.SortGlyphPos.X := MinLeft;
Inc(MinLeft, PaintInfo.SortGlyphSize.cx + FSpacing);
end;
if Layout in [blGlyphTop, blGlyphBottom] then
begin
// Header glyph is above or below text, so both must be considered when calculating
// the left positition of the sort glyph (if it is on the right hand side).
TextPos.X := MinLeft;
if PaintInfo.ShowHeaderGlyph then
begin
PaintInfo.GlyphPos.X := (ClientSize.X - HeaderGlyphSize.X) div 2;
if PaintInfo.GlyphPos.X < MinLeft then
PaintInfo.GlyphPos.X := MinLeft;
MinLeft := Max(TextPos.X + TextSize.cx + TextSpacing, PaintInfo.GlyphPos.X + HeaderGlyphSize.X + FSpacing);
end
else
MinLeft := TextPos.X + TextSize.cx + TextSpacing;
end
else
begin
// Everything is lined up. TextSpacing might be 0 if there is no text.
// This simplifies the calculation because no extra tests are necessary.
if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphLeft) then
begin
PaintInfo.GlyphPos.X := MinLeft;
Inc(MinLeft, HeaderGlyphSize.X + FSpacing);
end;
TextPos.X := MinLeft;
Inc(MinLeft, TextSize.cx + TextSpacing);
if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphRight) then
begin
PaintInfo.GlyphPos.X := MinLeft;
Inc(MinLeft, HeaderGlyphSize.X + FSpacing);
end;
end;
if PaintInfo.ShowSortGlyph and (FBiDiMode = bdLeftToRight) then
PaintInfo.SortGlyphPos.X := MinLeft;
end;
taCenter :
begin
if Layout in [blGlyphTop, blGlyphBottom] then
begin
PaintInfo.GlyphPos.X := (ClientSize.X - HeaderGlyphSize.X) div 2;
TextPos.X := (ClientSize.X - TextSize.cx) div 2;
if PaintInfo.ShowSortGlyph then
Dec(TextPos.X, PaintInfo.SortGlyphSize.cx div 2);
end
else
begin
MinLeft := (ClientSize.X - HeaderGlyphSize.X - TextSpacing - TextSize.cx) div 2;
if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphLeft) then
begin
PaintInfo.GlyphPos.X := MinLeft;
Inc(MinLeft, HeaderGlyphSize.X + TextSpacing);
end;
TextPos.X := MinLeft;
Inc(MinLeft, TextSize.cx + TextSpacing);
if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphRight) then
PaintInfo.GlyphPos.X := MinLeft;
end;
if PaintInfo.ShowHeaderGlyph then
begin
MinLeft := Min(PaintInfo.GlyphPos.X, TextPos.X);
MaxRight := Max(PaintInfo.GlyphPos.X + HeaderGlyphSize.X, TextPos.X + TextSize.cx);
end
else
begin
MinLeft := TextPos.X;
MaxRight := TextPos.X + TextSize.cx;
end;
// Place the sort glyph directly to the left or right of the larger item.
if PaintInfo.ShowSortGlyph then
if FBiDiMode = bdLeftToRight then
begin
// Sort glyph on the right hand side.
PaintInfo.SortGlyphPos.X := MaxRight + FSpacing;
end
else
begin
// Sort glyph on the left hand side.
PaintInfo.SortGlyphPos.X := MinLeft - FSpacing - PaintInfo.SortGlyphSize.cx;
end;
end;
else
// taRightJustify
MaxRight := ClientSize.X - FMargin;
if PaintInfo.ShowSortGlyph and (FBiDiMode = bdLeftToRight) then
begin
// In LTR context is the sort glyph placed on the right hand side.
Dec(MaxRight, PaintInfo.SortGlyphSize.cx);
PaintInfo.SortGlyphPos.X := MaxRight;
Dec(MaxRight, FSpacing);
end;
if Layout in [blGlyphTop, blGlyphBottom] then
begin
TextPos.X := MaxRight - TextSize.cx;
if PaintInfo.ShowHeaderGlyph then
begin
PaintInfo.GlyphPos.X := (ClientSize.X - HeaderGlyphSize.X) div 2;
if PaintInfo.GlyphPos.X + HeaderGlyphSize.X + FSpacing > MaxRight then
PaintInfo.GlyphPos.X := MaxRight - HeaderGlyphSize.X - FSpacing;
MaxRight := Min(TextPos.X - TextSpacing, PaintInfo.GlyphPos.X - FSpacing);
end
else
MaxRight := TextPos.X - TextSpacing;
end
else
begin
// Everything is lined up. TextSpacing might be 0 if there is no text.
// This simplifies the calculation because no extra tests are necessary.
if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphRight) then
begin
PaintInfo.GlyphPos.X := MaxRight - HeaderGlyphSize.X;
MaxRight := PaintInfo.GlyphPos.X - FSpacing;
end;
TextPos.X := MaxRight - TextSize.cx;
MaxRight := TextPos.X - TextSpacing;
if PaintInfo.ShowHeaderGlyph and (Layout = blGlyphLeft) then
begin
PaintInfo.GlyphPos.X := MaxRight - HeaderGlyphSize.X;
MaxRight := PaintInfo.GlyphPos.X - FSpacing;
end;
end;
if PaintInfo.ShowSortGlyph and (FBiDiMode <> bdLeftToRight) then
PaintInfo.SortGlyphPos.X := MaxRight - PaintInfo.SortGlyphSize.cx;
end;
end;
// Once the position of each element is determined there remains only one but important step.
// The horizontal positions of every element must be adjusted so that it always fits into the
// given header area. This is accomplished by shorten the text appropriately.
// These are the maximum bounds. Nothing goes beyond them.
MinLeft := FMargin;
MaxRight := ClientSize.X - FMargin;
if PaintInfo.ShowSortGlyph then
begin
if FBiDiMode = bdLeftToRight then
begin
// Sort glyph on the right hand side.
if PaintInfo.SortGlyphPos.X + PaintInfo.SortGlyphSize.cx > MaxRight then
PaintInfo.SortGlyphPos.X := MaxRight - PaintInfo.SortGlyphSize.cx;
MaxRight := PaintInfo.SortGlyphPos.X - FSpacing;
end;
// Consider also the left side of the sort glyph regardless of the bidi mode.
if PaintInfo.SortGlyphPos.X < MinLeft then
PaintInfo.SortGlyphPos.X := MinLeft;
// Left border needs only adjustment if the sort glyph marks the left border.
if FBiDiMode <> bdLeftToRight then
MinLeft := PaintInfo.SortGlyphPos.X + PaintInfo.SortGlyphSize.cx + FSpacing;
// Finally transform sort glyph to its actual position.
Inc(PaintInfo.SortGlyphPos.X, PaintInfo.PaintRectangle.Left);
Inc(PaintInfo.SortGlyphPos.Y, PaintInfo.PaintRectangle.Top);
end;
if PaintInfo.ShowHeaderGlyph then
begin
if PaintInfo.GlyphPos.X + HeaderGlyphSize.X > MaxRight then
PaintInfo.GlyphPos.X := MaxRight - HeaderGlyphSize.X;
if Layout = blGlyphRight then
MaxRight := PaintInfo.GlyphPos.X - FSpacing;
if PaintInfo.GlyphPos.X < MinLeft then
PaintInfo.GlyphPos.X := MinLeft;
if Layout = blGlyphLeft then
MinLeft := PaintInfo.GlyphPos.X + HeaderGlyphSize.X + FSpacing;
if FCheckBox and (Header.MainColumn = Self.Index) then
Dec(PaintInfo.GlyphPos.X, 2)
else
if Header.MainColumn <> Self.Index then
Dec(PaintInfo.GlyphPos.X, 2);
// Finally transform header glyph to its actual position.
Inc(PaintInfo.GlyphPos.X, PaintInfo.PaintRectangle.Left);
Inc(PaintInfo.GlyphPos.Y, PaintInfo.PaintRectangle.Top);
end;
if UseText then
begin
if TextPos.X < MinLeft then
TextPos.X := MinLeft;
OffsetRect(PaintInfo.TextRectangle, TextPos.X, TextPos.Y);
if PaintInfo.TextRectangle.Right > MaxRight then
PaintInfo.TextRectangle.Right := MaxRight;
OffsetRect(PaintInfo.TextRectangle, PaintInfo.PaintRectangle.Left, PaintInfo.PaintRectangle.Top);
if coWrapCaption in FOptions then
begin
// Wrap the column caption if necessary.
R := PaintInfo.TextRectangle;
FCaptionText := WrapString(PaintInfo.TargetCanvas.Handle, FText, R, DT_RTLREADING and DrawFormat <> 0, DrawFormat);
GetStringDrawRect(PaintInfo.TargetCanvas.Handle, FCaptionText, R, DrawFormat);
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.DefineProperties(Filer : TFiler);
begin
inherited;
// These properites are remains from non-Unicode Delphi versions, readers remain for backward compatibility.
Filer.DefineProperty('WideText', ReadText, nil, False);
Filer.DefineProperty('WideHint', ReadHint, nil, False);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.GetAbsoluteBounds(var Left, Right : Integer);
// Returns the column's left and right bounds in header coordinates, that is, independant of the scrolling position.
begin
Left := FLeft;
Right := FLeft + FWidth;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumn.GetDisplayName : string;
// Returns the column text if it only contains ANSI characters, otherwise the column id is returned because the IDE
// still cannot handle Unicode strings.
var
I : Integer;
begin
// Check if the text of the column contains characters > 255
I := 1;
while I <= Length(FText) do
begin
if Ord(FText[I]) > 255 then
Break;
Inc(I);
end;
if I > Length(FText) then
Result := FText // implicit conversion
else
Result := Format('Column %d', [Index]);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumn.GetOwner : TVirtualTreeColumns;
begin
Result := Collection as TVirtualTreeColumns;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.InternalSetWidth(const Value : TDimension);
begin
FWidth := Value;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.ReadText(Reader : TReader);
begin
case Reader.NextValue of
vaLString, vaString :
SetText(Reader.ReadString);
else
SetText(Reader.ReadString);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.ReadHint(Reader : TReader);
begin
case Reader.NextValue of
vaLString, vaString :
FHint := Reader.ReadString;
else
FHint := Reader.ReadString;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.Assign(Source : TPersistent);
var
OldOptions : TVTColumnOptions;
begin
if Source is TVirtualTreeColumn then
begin
OldOptions := FOptions;
FOptions := [];
BiDiMode := TVirtualTreeColumn(Source).BiDiMode;
ImageIndex := TVirtualTreeColumn(Source).ImageIndex;
Layout := TVirtualTreeColumn(Source).Layout;
Margin := TVirtualTreeColumn(Source).Margin;
MaxWidth := TVirtualTreeColumn(Source).MaxWidth;
MinWidth := TVirtualTreeColumn(Source).MinWidth;
Position := TVirtualTreeColumn(Source).Position;
Spacing := TVirtualTreeColumn(Source).Spacing;
Style := TVirtualTreeColumn(Source).Style;
Text := TVirtualTreeColumn(Source).Text;
Hint := TVirtualTreeColumn(Source).Hint;
Width := TVirtualTreeColumn(Source).Width;
Alignment := TVirtualTreeColumn(Source).Alignment;
CaptionAlignment := TVirtualTreeColumn(Source).CaptionAlignment;
Color := TVirtualTreeColumn(Source).Color;
Tag := TVirtualTreeColumn(Source).Tag;
EditOptions := TVirtualTreeColumn(Source).EditOptions;
EditNextColumn := TVirtualTreeColumn(Source).EditNextColumn;
// Order is important. Assign options last.
FOptions := OldOptions;
Options := TVirtualTreeColumn(Source).Options;
Changed(False);
end
else
inherited Assign(Source);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumn.Equals(OtherColumnObj : TObject) : Boolean;
var
OtherColumn : TVirtualTreeColumn;
begin
if OtherColumnObj is TVirtualTreeColumn then
begin
OtherColumn := TVirtualTreeColumn(OtherColumnObj);
Result := (BiDiMode = OtherColumn.BiDiMode) and
(ImageIndex = OtherColumn.ImageIndex) and
(Layout = OtherColumn.Layout) and
(Margin = OtherColumn.Margin) and
(MaxWidth = OtherColumn.MaxWidth) and
(MinWidth = OtherColumn.MinWidth) and
(Position = OtherColumn.Position) and
(Spacing = OtherColumn.Spacing) and
(Style = OtherColumn.Style) and
(Text = OtherColumn.Text) and
(Hint = OtherColumn.Hint) and
(Width = OtherColumn.Width) and
(Alignment = OtherColumn.Alignment) and
(CaptionAlignment = OtherColumn.CaptionAlignment) and
(Color = OtherColumn.Color) and
(Tag = OtherColumn.Tag) and
(Options = OtherColumn.Options);
end
else
Result := False;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumn.GetRect : TRect;
// Returns the rectangle this column occupies in the header (relative to (0, 0) of the non-client area).
begin
with TVirtualTreeColumns(GetOwner).FHeader do
Result := TreeViewControl.HeaderRect;
Inc(Result.Left, FLeft);
Result.Right := Result.Left + FWidth;
end;
//----------------------------------------------------------------------------------------------------------------------
// [IPK]
function TVirtualTreeColumn.GetText : string;
begin
Result := FText;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.LoadFromStream(const Stream : TStream; Version : Integer);
var
Dummy : Integer;
S : string;
begin
with Stream do
begin
ReadBuffer(Dummy, SizeOf(Dummy));
SetLength(S, Dummy);
ReadBuffer(PWideChar(S)^, 2 * Dummy);
Text := S;
ReadBuffer(Dummy, SizeOf(Dummy));
SetLength(FHint, Dummy);
ReadBuffer(PWideChar(FHint)^, 2 * Dummy);
ReadBuffer(Dummy, SizeOf(Dummy));
Width := Dummy;
ReadBuffer(Dummy, SizeOf(Dummy));
MinWidth := Dummy;
ReadBuffer(Dummy, SizeOf(Dummy));
MaxWidth := Dummy;
ReadBuffer(Dummy, SizeOf(Dummy));
Style := TVirtualTreeColumnStyle(Dummy);
ReadBuffer(Dummy, SizeOf(Dummy));
ImageIndex := Dummy;
ReadBuffer(Dummy, SizeOf(Dummy));
Layout := TVTHeaderColumnLayout(Dummy);
ReadBuffer(Dummy, SizeOf(Dummy));
Margin := Dummy;
ReadBuffer(Dummy, SizeOf(Dummy));
Spacing := Dummy;
ReadBuffer(Dummy, SizeOf(Dummy));
BiDiMode := TBiDiMode(Dummy);
ReadBuffer(Dummy, SizeOf(Dummy));
if Version >= 3 then
Options := TVTColumnOptions(Dummy);
if Version > 0 then
begin
// Parts which have been introduced/changed with header stream version 1+.
ReadBuffer(Dummy, SizeOf(Dummy));
Tag := Dummy;
ReadBuffer(Dummy, SizeOf(Dummy));
Alignment := TAlignment(Dummy);
if Version > 1 then
begin
ReadBuffer(Dummy, SizeOf(Dummy));
Color := TColor(Dummy);
end;
if Version > 5 then
begin
if coUseCaptionAlignment in FOptions then
begin
ReadBuffer(Dummy, SizeOf(Dummy));
CaptionAlignment := TAlignment(Dummy);
end;
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.ParentBiDiModeChanged;
var
Columns : TVirtualTreeColumns;
begin
if coParentBidiMode in FOptions then
begin
Columns := GetOwner as TVirtualTreeColumns;
if Assigned(Columns) and (FBiDiMode <> TreeViewControl.BiDiMode) then
begin
FBiDiMode := TreeViewControl.BiDiMode;
Changed(False);
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.ParentColorChanged;
var
Columns : TVirtualTreeColumns;
begin
if coParentColor in FOptions then
begin
Columns := GetOwner as TVirtualTreeColumns;
if Assigned(Columns) and (FColor <> TreeViewControl.Color) then
begin
FColor := TreeViewControl.Color;
Changed(False);
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.RestoreLastWidth;
begin
TVirtualTreeColumns(GetOwner).AnimatedResize(Index, FLastWidth);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumn.SaveToStream(const Stream : TStream);
var
Dummy : Integer;
begin
with Stream do
begin
Dummy := Length(FText);
WriteBuffer(Dummy, SizeOf(Dummy));
WriteBuffer(PWideChar(FText)^, 2 * Dummy);
Dummy := Length(FHint);
WriteBuffer(Dummy, SizeOf(Dummy));
WriteBuffer(PWideChar(FHint)^, 2 * Dummy);
WriteBuffer(FWidth, SizeOf(FWidth));
WriteBuffer(FMinWidth, SizeOf(FMinWidth));
WriteBuffer(FMaxWidth, SizeOf(FMaxWidth));
Dummy := Ord(FStyle);
WriteBuffer(Dummy, SizeOf(Dummy));
Dummy := FImageIndex;
WriteBuffer(Dummy, SizeOf(Dummy));
Dummy := Ord(FLayout);
WriteBuffer(Dummy, SizeOf(Dummy));
WriteBuffer(FMargin, SizeOf(FMargin));
WriteBuffer(FSpacing, SizeOf(FSpacing));
Dummy := Ord(FBiDiMode);
WriteBuffer(Dummy, SizeOf(Dummy));
Dummy := Integer(FOptions);
WriteBuffer(Dummy, SizeOf(Dummy));
// parts introduced with stream version 1
WriteBuffer(FTag, SizeOf(Dummy));
Dummy := Cardinal(FAlignment);
WriteBuffer(Dummy, SizeOf(Dummy));
// parts introduced with stream version 2
Dummy := Integer(FColor);
WriteBuffer(Dummy, SizeOf(Dummy));
// parts introduced with stream version 6
if coUseCaptionAlignment in FOptions then
begin
Dummy := Cardinal(FCaptionAlignment);
WriteBuffer(Dummy, SizeOf(Dummy));
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumn.UseRightToLeftReading : Boolean;
begin
Result := FBiDiMode <> bdLeftToRight;
end;
//----------------- TVirtualTreeColumns --------------------------------------------------------------------------------
constructor TVirtualTreeColumns.Create(AOwner : TVTHeader);
var
ColumnClass : TVirtualTreeColumnClass;
begin
FHeader := AOwner;
// Determine column class to be used in the header.
ColumnClass := Self.TreeViewControl.GetColumnClass;
// The owner tree always returns the default tree column class if not changed by application/descendants.
inherited Create(ColumnClass);
FHeaderBitmap := TBitmap.Create;
FHeaderBitmap.PixelFormat := pf32Bit;
FHoverIndex := NoColumn;
FDownIndex := NoColumn;
FClickIndex := NoColumn;
FDropTarget := NoColumn;
FTrackIndex := NoColumn;
FDefaultWidth := 50;
Self.FColumnPopupMenu := nil;
end;
//----------------------------------------------------------------------------------------------------------------------
destructor TVirtualTreeColumns.Destroy;
begin
FreeAndNil(FColumnPopupMenu);
FreeAndNil(FHeaderBitmap);
inherited;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.GetCount : Integer;
begin
Result := inherited Count;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.GetItem(Index : TColumnIndex) : TVirtualTreeColumn;
begin
Result := TVirtualTreeColumn(inherited GetItem(Index));
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.GetNewIndex(P : TPoint; var OldIndex : TColumnIndex) : Boolean;
var
NewIndex : Integer;
begin
Result := False;
// convert to local coordinates
Inc(P.Y, Header.Height);
NewIndex := ColumnFromPosition(P);
if NewIndex <> OldIndex then
begin
if OldIndex > NoColumn then
Header.Invalidate(Items[OldIndex], False, True);
OldIndex := NewIndex;
if OldIndex > NoColumn then
Header.Invalidate(Items[OldIndex], False, True);
Result := True;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.SetDefaultWidth(Value : Integer);
begin
FDefaultWidth := Value;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.SetItem(Index : TColumnIndex; Value : TVirtualTreeColumn);
begin
inherited SetItem(Index, Value);
end;
function TVirtualTreeColumns.StyleServices(AControl : TControl) : TCustomStyleServices;
begin
if AControl = nil then
AControl := TreeView;
Result := VTStyleServices(AControl);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.AdjustAutoSize(CurrentIndex : TColumnIndex; Force : Boolean = False);
// Called only if the header is in auto-size mode which means a column needs to be so large
// that it fills all the horizontal space not occupied by the other columns.
// CurrentIndex (if not InvalidColumn) describes which column has just been resized.
var
NewValue,
AutoIndex,
Index,
RestWidth : Integer;
WasUpdating : Boolean;
begin
if Count > 0 then
begin
// Determine index to be used for auto resizing. This is usually given by the owner's AutoSizeIndex, but
// could be different if the column whose resize caused the invokation here is either the auto column itself
// or visually to the right of the auto size column.
AutoIndex := Header.AutoSizeIndex;
if (AutoIndex < 0) or (AutoIndex >= Count) then
AutoIndex := Count - 1;
if AutoIndex >= 0 then
begin
with TreeView do
begin
if HandleAllocated then
RestWidth := ClientWidth
else
RestWidth := Width;
end;
// Go through all columns and calculate the rest space remaining.
for Index := 0 to Count - 1 do
if (Index <> AutoIndex) and (coVisible in Items[Index].Options) then
Dec(RestWidth, Items[Index].Width);
with Items[AutoIndex] do
begin
NewValue := Max(MinWidth, Min(MaxWidth, RestWidth));
if Force or (FWidth <> NewValue) then
begin
FWidth := NewValue;
UpdatePositions;
WasUpdating := csUpdating in TreeViewControl.ComponentState;
if not WasUpdating then
TreeViewControl.Updating(); // Fixes #398
try
TreeViewControl.DoColumnResize(AutoIndex);
finally
if not WasUpdating then
TreeViewControl.Updated();
end;
end;
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.AdjustDownColumn(P : TPoint) : TColumnIndex;
// Determines the column from the given position and returns it. If this column is allowed to be clicked then
// it is also kept for later use.
begin
// Convert to local coordinates.
Inc(P.Y, Header.Height);
Result := ColumnFromPosition(P);
if (Result > NoColumn) and (Result <> FDownIndex) and (coAllowClick in Items[Result].Options) and
(coEnabled in Items[Result].Options) then
begin
if FDownIndex > NoColumn then
Header.Invalidate(Items[FDownIndex]);
FDownIndex := Result;
FCheckBoxHit := Items[Result].HasImage and PtInRect(Items[Result].ImageRect, P) and Items[Result].CheckBox;
Header.Invalidate(Items[FDownIndex]);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.AdjustHoverColumn(P : TPoint) : Boolean;
// Determines the new hover column index and returns True if the index actually changed else False.
begin
Result := GetNewIndex(P, FHoverIndex);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.AdjustPosition(Column : TVirtualTreeColumn; Position : Cardinal);
// Reorders the column position array so that the given column gets the given position.
var
OldPosition : Cardinal;
begin
OldPosition := Column.Position;
if OldPosition <> Position then
begin
if OldPosition < Position then
begin
// column will be moved up so move down other entries
Move(FPositionToIndex[OldPosition + 1], FPositionToIndex[OldPosition], (Position - OldPosition) * SizeOf(Cardinal));
end
else
begin
// column will be moved down so move up other entries
Move(FPositionToIndex[Position], FPositionToIndex[Position + 1], (OldPosition - Position) * SizeOf(Cardinal));
end;
FPositionToIndex[Position] := Column.Index;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.CanSplitterResize(P : TPoint; Column : TColumnIndex) : Boolean;
begin
Result := (Column > NoColumn) and ([coResizable, coVisible] * Items[Column].Options = [coResizable, coVisible]);
DoCanSplitterResize(P, Column, Result);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.DoCanSplitterResize(P : TPoint; Column : TColumnIndex; var Allowed : Boolean);
begin
if Assigned(TreeViewControl.OnCanSplitterResizeColumn) then
TreeViewControl.OnCanSplitterResizeColumn(Header, P, Column, Allowed);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.DrawButtonText(DC : HDC; Caption : string; Bounds : TRect; Enabled, Hot : Boolean;
DrawFormat : Cardinal; WrapCaption : Boolean);
var
TextSpace : Integer;
Size : TSize;
begin
if not WrapCaption then
begin
// Do we need to shorten the caption due to limited space?
GetTextExtentPoint32W(DC, PWideChar(Caption), Length(Caption), Size);
TextSpace := Bounds.Right - Bounds.Left;
if TextSpace < Size.cx then
Caption := ShortenString(DC, Caption, TextSpace);
end;
SetBkMode(DC, TRANSPARENT);
if not Enabled then
if TreeViewControl.VclStyleEnabled then
begin
SetTextColor(DC, ColorToRGB(TreeViewControl.Colors.HeaderFontColor));
WinApi.Windows.DrawTextW(DC, PWideChar(Caption), Length(Caption), Bounds, DrawFormat);
end
else
begin
OffsetRect(Bounds, 1, 1);
SetTextColor(DC, ColorToRGB(clBtnHighlight));
WinApi.Windows.DrawTextW(DC, PWideChar(Caption), Length(Caption), Bounds, DrawFormat);
OffsetRect(Bounds, - 1, - 1);
SetTextColor(DC, ColorToRGB(clBtnShadow));
WinApi.Windows.DrawTextW(DC, PWideChar(Caption), Length(Caption), Bounds, DrawFormat);
end
else
begin
if Hot then
SetTextColor(DC, ColorToRGB(TreeViewControl.Colors.HeaderHotColor))
else
SetTextColor(DC, ColorToRGB(TreeViewControl.Colors.HeaderFontColor));
WinApi.Windows.DrawTextW(DC, PWideChar(Caption), Length(Caption), Bounds, DrawFormat);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.FixPositions;
// Fixes column positions after loading from DFM or Bidi mode change.
var
I : Integer;
begin
for I := 0 to Count - 1 do
FPositionToIndex[Items[I].Position] := I;
FNeedPositionsFix := False;
UpdatePositions(True);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.GetColumnAndBounds(P : TPoint; var ColumnLeft, ColumnRight : Integer;
Relative : Boolean = True) : Integer;
// Returns the column where the mouse is currently in as well as the left and right bound of
// this column (Left and Right are undetermined if no column is involved).
var
I : Integer;
begin
Result := InvalidColumn;
if Relative and (P.X >= Header.Columns.GetVisibleFixedWidth) then
ColumnLeft := - TreeViewControl.EffectiveOffsetX
else
ColumnLeft := 0;
if TreeViewControl.UseRightToLeftAlignment then
Inc(ColumnLeft, TreeViewControl.ComputeRTLOffset(True));
for I := 0 to Count - 1 do
with Items[FPositionToIndex[I]] do
if coVisible in FOptions then
begin
ColumnRight := ColumnLeft + FWidth;
//fix: in right to left alignment, X can be in the
//area on the left of first column which is OUT.
if (P.X < ColumnLeft) and (I = 0) then
begin
Result := InvalidColumn;
Exit;
end;
if P.X < ColumnRight then
begin
Result := FPositionToIndex[I];
Exit;
end;
ColumnLeft := ColumnRight;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.GetOwner : TPersistent;
begin
Result := FHeader;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.HandleClick(P : TPoint; Button : TMouseButton; Force, DblClick : Boolean) : Boolean;
// Generates a click event if the mouse button has been released over the same column it was pressed first.
// Alternatively, Force might be set to True to indicate that the down index does not matter (right, middle and
// double click).
// Returns true if the click was handled, False otherwise.
var
HitInfo : TVTHeaderHitInfo;
NewClickIndex : Integer;
Menu : TPopupMenu;
begin
Result := False;
if (csDesigning in TreeViewControl.ComponentState) then
Exit;
// Convert vertical position to local coordinates.
Inc(P.Y, Header.Height);
NewClickIndex := ColumnFromPosition(P);
with HitInfo do
begin
X := P.X;
Y := P.Y;
Shift := Header.GetShiftState;
if DblClick then
Shift := Shift + [ssDouble];
end;
HitInfo.Button := Button;
if (NewClickIndex > NoColumn) and (coAllowClick in Items[NewClickIndex].Options) and
((NewClickIndex = FDownIndex) or Force) then
begin
FClickIndex := NewClickIndex;
HitInfo.Column := NewClickIndex;
HitInfo.HitPosition := [hhiOnColumn];
if Items[NewClickIndex].HasImage and PtInRect(Items[NewClickIndex].ImageRect, P) then
begin
Include(HitInfo.HitPosition, hhiOnIcon);
if Items[NewClickIndex].CheckBox then
begin
if Button = mbLeft then
TreeViewControl.UpdateColumnCheckState(Items[NewClickIndex]);
Include(HitInfo.HitPosition, hhiOnCheckbox);
end;
end;
end
else
begin
FClickIndex := NoColumn;
HitInfo.Column := NoColumn;
HitInfo.HitPosition := [hhiNoWhere];
end;
if DblClick then
TreeViewControl.DoHeaderDblClick(HitInfo)
else
begin
if (hoHeaderClickAutoSort in Header.Options) and (HitInfo.Button = mbLeft) and not (hhiOnCheckbox in HitInfo.HitPosition) and (HitInfo.Column >= 0) then
begin
// handle automatic setting of SortColumn and toggling of the sort order
if HitInfo.Column <> Header.SortColumn then
begin
// set sort column
Header.DoSetSortColumn(HitInfo.Column, Self[HitInfo.Column].DefaultSortDirection);
end//if
else
begin
// toggle sort direction
if Header.SortDirection = sdDescending then
Header.SortDirection := sdAscending
else
Header.SortDirection := sdDescending;
end; //else
Result := True;
end; //if
if (Button = mbRight) then
begin
Dec(P.Y, Header.Height); // popup menus at actual clicked point
FreeAndNil(FColumnPopupMenu); // Attention: Do not free the TVTHeaderPopupMenu at the end of this method, otherwise the clikc events of the menu item will not be fired.
Self.FDownIndex := NoColumn;
Self.FTrackIndex := NoColumn;
Self.FCheckBoxHit := False;
Menu := Header.DoGetPopupMenu(Self.ColumnFromPosition(Point(P.X, P.Y + Integer(TreeViewControl.Height))), P);
if Assigned(Menu) then
begin
TreeViewControl.StopTimer(ScrollTimer);
TreeViewControl.StopTimer(HeaderTimer);
Header.Columns.SetHoverIndex(NoColumn);
TreeViewControl.DoStateChange([], [tsScrollPending, tsScrolling]);
Menu.PopupComponent := TreeView;
With TreeViewControl.ClientToScreen(P) do
Menu.Popup(X, Y);
Result := True;
end
else if (hoAutoColumnPopupMenu in Header.Options) then
begin
FColumnPopupMenu := TVTHeaderPopupMenu.Create(TreeView);
TVTHeaderPopupMenu(FColumnPopupMenu).OnAddHeaderPopupItem := HeaderPopupMenuAddHeaderPopupItem;
FColumnPopupMenu.PopupComponent := TreeView;
if (hoDblClickResize in Header.Options) and ((TreeViewControl.ChildCount[nil] > 0) or (hoAutoResizeInclCaption in Header.Options)) then
TVTHeaderPopupMenu(FColumnPopupMenu).Options := TVTHeaderPopupMenu(FColumnPopupMenu).Options + [poResizeToFitItem]
else
TVTHeaderPopupMenu(FColumnPopupMenu).Options := TVTHeaderPopupMenu(FColumnPopupMenu).Options - [poResizeToFitItem];
With TreeViewControl.ClientToScreen(P) do
FColumnPopupMenu.Popup(X, Y);
Result := True;
end; // if hoAutoColumnPopupMenu
end; //if mbRight
TreeViewControl.DoHeaderClick(HitInfo);
end; //else (not DblClick)
if not (hhiNoWhere in HitInfo.HitPosition) then
Header.Invalidate(Items[NewClickIndex]);
if (FClickIndex > NoColumn) and (FClickIndex <> NewClickIndex) then
Header.Invalidate(Items[FClickIndex]);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.HeaderPopupMenuAddHeaderPopupItem(const Sender : TObject; const Column : TColumnIndex; var Cmd : TAddPopupItemType);
begin
TBaseVirtualTreeCracker(Sender).DoHeaderAddPopupItem(Column, Cmd);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.IndexChanged(OldIndex, NewIndex : Integer);
// Called by a column when its index in the collection changes. If NewIndex is -1 then the column is
// about to be removed, otherwise it is moved to a new index.
// The method will then update the position array to reflect the change.
var
I : Integer;
Increment : Integer;
Lower,
Upper : Integer;
begin
if NewIndex = - 1 then
begin
// Find position in the array with the old index.
Upper := High(FPositionToIndex);
for I := 0 to Upper do
begin
if FPositionToIndex[I] = OldIndex then
begin
// Index found. Move all higher entries one step down and remove the last entry.
if I < Upper then
Move(FPositionToIndex[I + 1], FPositionToIndex[I], (Upper - I) * SizeOf(TColumnIndex));
end;
// Decrease all indices, which are greater than the index to be deleted.
if FPositionToIndex[I] > OldIndex then
Dec(FPositionToIndex[I]);
end;
SetLength(FPositionToIndex, High(FPositionToIndex));
end
else
begin
if OldIndex < NewIndex then
Increment := - 1
else
Increment := 1;
Lower := Min(OldIndex, NewIndex);
Upper := Max(OldIndex, NewIndex);
for I := 0 to High(FPositionToIndex) do
begin
if (FPositionToIndex[I] >= Lower) and (FPositionToIndex[I] < Upper) then
Inc(FPositionToIndex[I], Increment)
else
if FPositionToIndex[I] = OldIndex then
FPositionToIndex[I] := NewIndex;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.InitializePositionArray;
// Ensures that the column position array contains as many entries as columns are defined.
// The array is resized and initialized with default values if needed.
var
I, OldSize : Integer;
Changed : Boolean;
begin
if Count <> Length(FPositionToIndex) then
begin
OldSize := Length(FPositionToIndex);
SetLength(FPositionToIndex, Count);
if Count > OldSize then
begin
// New items have been added, just set their position to the same as their index.
for I := OldSize to Count - 1 do
FPositionToIndex[I] := I;
end
else
begin
// Items have been deleted, so reindex remaining entries by decrementing values larger than the highest
// possible index until no entry is higher than this limit.
repeat
Changed := False;
for I := 0 to Count - 1 do
if FPositionToIndex[I] >= Count then
begin
Dec(FPositionToIndex[I]);
Changed := True;
end;
until not Changed;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.Notify(Item : TCollectionItem; Action : System.Classes.TCollectionNotification);
var
I : Integer;
lRemovedPosition: TColumnPosition;
begin
if Action in [cnDeleting] then
begin
lRemovedPosition := TVirtualTreeColumn(Item).Position;
// Adjust all positions larger than the deleted column's position. Fixes #959, #1049
for I := Count - 1 downto 0 do
begin
if Items[I].Position > lRemovedPosition then
Items[I].Position := Items[I].Position - 1;
end; //for I
with TreeViewControl do
if not (csLoading in ComponentState) and (FocusedColumn = Item.Index) then
InternalSetFocusedColumn(NoColumn); //bypass side effects in SetFocusedColumn
end; // if cnDeleting
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.ReorderColumns(RTL : Boolean);
var
I : Integer;
begin
if RTL then
begin
for I := 0 to Count - 1 do
FPositionToIndex[I] := Count - I - 1;
end
else
begin
for I := 0 to Count - 1 do
FPositionToIndex[I] := I;
end;
UpdatePositions(True);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.SetHoverIndex(Index : TColumnIndex);
begin
FHoverIndex := index;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.EndUpdate;
begin
InitializePositionArray();
FixPositions(); // Accept the cuurent order. See issue #753
inherited;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.Update(Item : TCollectionItem);
begin
// This is the only place which gets notified when a new column has been added or removed
// and we need this event to adjust the column position array.
InitializePositionArray;
if csLoading in TreeViewControl.ComponentState then
FNeedPositionsFix := True
else
UpdatePositions;
// The first column which is created is by definition also the main column.
if (Count > 0) and (Header.MainColumn < 0) then
Header.MainColumn := 0;
if not (csLoading in TreeViewControl.ComponentState) and not (hsLoading in Header.States) then
begin
with Header do
begin
if hoAutoResize in Options then
AdjustAutoSize(InvalidColumn);
if Assigned(Item) then
Invalidate(Item as TVirtualTreeColumn)
else
if Self.TreeViewControl.HandleAllocated then
begin
Self.TreeViewControl.UpdateHorizontalScrollBar(False);
Invalidate(nil);
TreeViewControl.Invalidate;
end;
if not (Self.TreeViewControl.IsUpdating) then
// This is mainly to let the designer know when a change occurs at design time which
// doesn't involve the object inspector (like column resizing with the mouse).
// This does NOT include design time code as the communication is done via an interface.
Self.TreeViewControl.UpdateDesigner;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.UpdatePositions(Force : Boolean = False);
// Recalculates the left border of every column and updates their position property according to the
// PostionToIndex array which primarily determines where each column is placed visually.
var
I, RunningPos : Integer;
begin
if not (csDestroying in TreeViewControl.ComponentState) and not FNeedPositionsFix and (Force or (UpdateCount = 0)) then
begin
RunningPos := 0;
for I := 0 to High(FPositionToIndex) do
with Items[FPositionToIndex[I]] do
begin
FPosition := I;
FLeft := RunningPos;
if coVisible in FOptions then
Inc(RunningPos, FWidth);
end;
TreeViewControl.UpdateHorizontalScrollBar(False);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.Add : TVirtualTreeColumn;
begin
Assert(GetCurrentThreadId = MainThreadId, 'UI controls may only be changed in UI thread.');
Result := TVirtualTreeColumn(inherited Add);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.AnimatedResize(Column : TColumnIndex; NewWidth : Integer);
// Resizes the given column animated by scrolling the window DC.
var
OldWidth : Integer;
DC : HDC;
I,
Steps,
DX : Integer;
HeaderScrollRect,
ScrollRect,
R : TRect;
NewBrush,
LastBrush : HBRUSH;
begin
if not IsValidColumn(Column) then
Exit; // Just in case.
// Make sure the width constrains are considered.
if NewWidth < Items[Column].MinWidth then
NewWidth := Items[Column].MinWidth;
if NewWidth > Items[Column].MaxWidth then
NewWidth := Items[Column].MaxWidth;
OldWidth := Items[Column].Width;
// Nothing to do if the width is the same.
if OldWidth <> NewWidth then
begin
if not ((hoDisableAnimatedResize in Header.Options) or
(coDisableAnimatedResize in Items[Column].Options)) then
begin
DC := GetWindowDC(TreeViewControl.Handle);
with TreeViewControl do
try
Steps := 32;
DX := (NewWidth - OldWidth) div Steps;
// Determination of the scroll rectangle is a bit complicated since we neither want
// to scroll the scrollbars nor the border of the treeview window.
HeaderScrollRect := HeaderRect;
ScrollRect := HeaderScrollRect;
// Exclude the header itself from scrolling.
ScrollRect.Top := ScrollRect.Bottom;
ScrollRect.Bottom := ScrollRect.Top + ClientHeight;
ScrollRect.Right := ScrollRect.Left + ClientWidth;
with Items[Column] do
Inc(ScrollRect.Left, FLeft + FWidth);
HeaderScrollRect.Left := ScrollRect.Left;
HeaderScrollRect.Right := ScrollRect.Right;
// When the new width is larger then avoid artefacts on the left hand side
// by deleting a small stripe
if NewWidth > OldWidth then
begin
R := ScrollRect;
NewBrush := CreateSolidBrush(ColorToRGB(Color));
LastBrush := SelectObject(DC, NewBrush);
R.Right := R.Left + DX;
FillRect(DC, R, NewBrush);
SelectObject(DC, LastBrush);
DeleteObject(NewBrush);
end
else
begin
Inc(HeaderScrollRect.Left, DX);
Inc(ScrollRect.Left, DX);
end;
for I := 0 to Steps - 1 do
begin
ScrollDC(DC, DX, 0, HeaderScrollRect, HeaderScrollRect, 0, nil);
Inc(HeaderScrollRect.Left, DX);
ScrollDC(DC, DX, 0, ScrollRect, ScrollRect, 0, nil);
Inc(ScrollRect.Left, DX);
Sleep(1);
end;
finally
ReleaseDC(Handle, DC);
end;
end;
Items[Column].Width := NewWidth;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.Assign(Source : TPersistent);
begin
// Let the collection class assign the items.
inherited;
if Source is TVirtualTreeColumns then
begin
// Copying the position array is the only needed task here.
FPositionToIndex := Copy(TVirtualTreeColumns(Source).FPositionToIndex, 0, MaxInt);
// Make sure the left edges are correct after assignment.
FNeedPositionsFix := False;
UpdatePositions(True);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.Clear;
begin
FClearing := True;
try
TreeViewControl.CancelEditNode;
// Since we're freeing all columns, the following have to be true when we're done.
FHoverIndex := NoColumn;
FDownIndex := NoColumn;
FTrackIndex := NoColumn;
FClickIndex := NoColumn;
FCheckBoxHit := False;
with Header do
if not (hsLoading in States) then
begin
InternalSetAutoSizeIndex(NoColumn); //bypass side effects in SetAutoSizeColumn
MainColumn := NoColumn;
InternalSetSortColumn(NoColumn); //bypass side effects in SetSortColumn
end;
with TreeViewControl do
if not (csLoading in ComponentState) then
InternalSetFocusedColumn(NoColumn); //bypass side effects in SetFocusedColumn
inherited Clear;
finally
FClearing := False;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.ColumnFromPosition(P : TPoint; Relative : Boolean = True) : TColumnIndex;
// Determines the current column based on the position passed in P.
var
I, Sum : Integer;
begin
Result := InvalidColumn;
// The position must be within the header area, but we extend the vertical bounds to the entire treeview area.
if (P.X >= 0) and (P.Y >= 0) and (P.Y <= TreeViewControl.Height) then
with FHeader, TreeViewControl do
begin
if Relative and (P.X >= GetVisibleFixedWidth) then
Sum := - EffectiveOffsetX
else
Sum := 0;
if UseRightToLeftAlignment then
Inc(Sum, ComputeRTLOffset(True));
for I := 0 to Count - 1 do
if coVisible in Items[FPositionToIndex[I]].Options then
begin
Inc(Sum, Items[FPositionToIndex[I]].Width);
if P.X < Sum then
begin
Result := FPositionToIndex[I];
Break;
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.ColumnFromPosition(PositionIndex : TColumnPosition) : TColumnIndex;
// Returns the index of the column at the given position.
begin
if Integer(PositionIndex) < Length(FPositionToIndex) then
Result := FPositionToIndex[PositionIndex]
else
Result := NoColumn;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.Equals(OtherColumnsObj : TObject) : Boolean;
// Compares itself with the given set of columns and returns True if all published properties are the same
// (including column order), otherwise False is returned.
var
I : Integer;
OtherColumns : TVirtualTreeColumns;
begin
if not (OtherColumnsObj is TVirtualTreeColumns) then
begin
Result := False;
Exit;
end;
OtherColumns := TVirtualTreeColumns(OtherColumnsObj);
// Same number of columns?
Result := OtherColumns.Count = Count;
if Result then
begin
// Same order of columns?
Result := CompareMem(Pointer(FPositionToIndex), Pointer(OtherColumns.FPositionToIndex),
Length(FPositionToIndex) * SizeOf(TColumnIndex));
if Result then
begin
for I := 0 to Count - 1 do
if not Items[I].Equals(OtherColumns[I]) then
begin
Result := False;
Break;
end;
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.GetColumnBounds(Column : TColumnIndex; var Left, Right : Integer);
// Returns the left and right bound of the given column. If Column is NoColumn then the entire client width is returned.
begin
if Column <= NoColumn then
begin
Left := 0;
Right := TreeViewControl.ClientWidth;
end
else
begin
Left := Items[Column].Left;
Right := Left + Items[Column].Width;
if TreeViewControl.UseRightToLeftAlignment then
begin
Inc(Left, TreeViewControl.ComputeRTLOffset(True));
Inc(Right, TreeViewControl.ComputeRTLOffset(True));
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.GetScrollWidth : Integer;
// Returns the average width of all visible, non-fixed columns. If there is no such column the indent is returned.
var
I : Integer;
ScrollColumnCount : Integer;
begin
Result := 0;
ScrollColumnCount := 0;
for I := 0 to Header.Columns.Count - 1 do
begin
if ([coVisible, coFixed] * Header.Columns[I].Options = [coVisible]) then
begin
Inc(Result, Header.Columns[I].Width);
Inc(ScrollColumnCount);
end;
end;
if ScrollColumnCount > 0 then // use average width
Result := Round(Result / ScrollColumnCount)
else // use indent
Result := Integer(TreeViewControl.Indent);
end;
function TVirtualTreeColumns.GetTreeView: TCustomControl;
begin
Result := TBaseVirtualTreeCracker(Header.GetOwner);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.GetFirstVisibleColumn(ConsiderAllowFocus : Boolean = False) : TColumnIndex;
// Returns the index of the first visible column or "InvalidColumn" if either no columns are defined or
// all columns are hidden.
// If ConsiderAllowFocus is True then the column has not only to be visible but also focus has to be allowed.
var
I : Integer;
begin
Result := InvalidColumn;
if (UpdateCount > 0) or (csLoading in TreeViewControl.ComponentState) then
Exit; // See issue #760
for I := 0 to Count - 1 do
if (coVisible in Items[FPositionToIndex[I]].Options) and
((not ConsiderAllowFocus) or
(coAllowFocus in Items[FPositionToIndex[I]].Options)
) then
begin
Result := FPositionToIndex[I];
Break;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.GetLastVisibleColumn(ConsiderAllowFocus : Boolean = False) : TColumnIndex;
// Returns the index of the last visible column or "InvalidColumn" if either no columns are defined or
// all columns are hidden.
// If ConsiderAllowFocus is True then the column has not only to be visible but also focus has to be allowed.
var
I : Integer;
begin
Result := InvalidColumn;
if (UpdateCount > 0) or (csLoading in TreeViewControl.ComponentState) then
Exit; // See issue #760
for I := Count - 1 downto 0 do
if (coVisible in Items[FPositionToIndex[I]].Options) and
((not ConsiderAllowFocus) or
(coAllowFocus in Items[FPositionToIndex[I]].Options)
) then
begin
Result := FPositionToIndex[I];
Break;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.GetFirstColumn : TColumnIndex;
// Returns the first column in display order.
begin
if Count = 0 then
Result := InvalidColumn
else
Result := FPositionToIndex[0];
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.GetNextColumn(Column : TColumnIndex) : TColumnIndex;
// Returns the next column in display order. Column is the index of an item in the collection (a column).
var
Position : Integer;
begin
if Column < 0 then
Result := InvalidColumn
else
begin
Position := Items[Column].Position;
if Position < Count - 1 then
Result := FPositionToIndex[Position + 1]
else
Result := InvalidColumn;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.GetNextVisibleColumn(Column : TColumnIndex; ConsiderAllowFocus : Boolean = False) : TColumnIndex;
// Returns the next visible column in display order, Column is an index into the columns list.
// If ConsiderAllowFocus is True then the column has not only to be visible but also focus has to be allowed.
begin
Result := Column;
repeat
Result := GetNextColumn(Result);
until (Result = InvalidColumn) or
((coVisible in Items[Result].Options) and
((not ConsiderAllowFocus) or
(coAllowFocus in Items[Result].Options)
)
);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.GetPreviousColumn(Column : TColumnIndex) : TColumnIndex;
// Returns the previous column in display order, Column is an index into the columns list.
var
Position : Integer;
begin
if Column < 0 then
Result := InvalidColumn
else
begin
Position := Items[Column].Position;
if Position > 0 then
Result := FPositionToIndex[Position - 1]
else
Result := InvalidColumn;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.GetPreviousVisibleColumn(Column : TColumnIndex; ConsiderAllowFocus : Boolean = False) : TColumnIndex;
// Returns the previous visible column in display order, Column is an index into the columns list.
// If ConsiderAllowFocus is True then the column has not only to be visible but also focus has to be allowed.
begin
Result := Column;
repeat
Result := GetPreviousColumn(Result);
until (Result = InvalidColumn) or
((coVisible in Items[Result].Options) and
((not ConsiderAllowFocus) or
(coAllowFocus in Items[Result].Options)
)
);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.GetVisibleColumns : TColumnsArray;
// Returns a list of all currently visible columns in actual order.
var
I, Counter : Integer;
begin
SetLength(Result, Count);
Counter := 0;
for I := 0 to Count - 1 do
if coVisible in Items[FPositionToIndex[I]].Options then
begin
Result[Counter] := Items[FPositionToIndex[I]];
Inc(Counter);
end;
// Set result length to actual visible count.
SetLength(Result, Counter);
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.GetVisibleFixedWidth : Integer;
// Determines the horizontal space all visible and fixed columns occupy.
var
I : Integer;
begin
Result := 0;
for I := 0 to Count - 1 do
begin
if Items[I].Options * [coVisible, coFixed] = [coVisible, coFixed] then
Inc(Result, Items[I].Width);
end;
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.IsValidColumn(Column : TColumnIndex) : Boolean;
// Determines whether the given column is valid or not, that is, whether it is one of the current columns.
begin
Result := (Column > NoColumn) and (Column < Count);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.LoadFromStream(const Stream : TStream; Version : Integer);
var
I,
ItemCount : Integer;
begin
Clear;
Stream.ReadBuffer(ItemCount, SizeOf(ItemCount));
// number of columns
if ItemCount > 0 then
begin
BeginUpdate;
try
for I := 0 to ItemCount - 1 do
Add.LoadFromStream(Stream, Version);
SetLength(FPositionToIndex, ItemCount);
Stream.ReadBuffer(FPositionToIndex[0], ItemCount * SizeOf(TColumnIndex));
UpdatePositions(True);
finally
EndUpdate;
end;
end;
// Data introduced with header stream version 5
if Version > 4 then
Stream.ReadBuffer(FDefaultWidth, SizeOf(FDefaultWidth));
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.PaintHeader(DC : HDC; R : TRect; HOffset : Integer);
// Backward compatible header paint method. This method takes care of visually moving floating columns
var
VisibleFixedWidth : Integer;
RTLOffset : Integer;
procedure PaintFixedArea;
begin
if VisibleFixedWidth > 0 then
PaintHeader(FHeaderBitmap.Canvas,
Rect(0, 0, Min(R.Right, VisibleFixedWidth), R.Bottom - R.Top),
Point(R.Left, R.Top), RTLOffset);
end;
begin
// Adjust size of the header bitmap
FHeaderBitmap.SetSize(Max(TreeViewControl.HeaderRect.Right, R.Right - R.Left), TreeViewControl.HeaderRect.Bottom);
VisibleFixedWidth := GetVisibleFixedWidth;
// Consider right-to-left directionality.
if TreeViewControl.UseRightToLeftAlignment then
RTLOffset := TreeViewControl.ComputeRTLOffset
else
RTLOffset := 0;
if RTLOffset = 0 then
PaintFixedArea;
// Paint the floating part of the header.
PaintHeader(FHeaderBitmap.Canvas,
Rect(VisibleFixedWidth - HOffset, 0, R.Right + VisibleFixedWidth - HOffset, R.Bottom - R.Top),
Point(R.Left + VisibleFixedWidth, R.Top), RTLOffset);
// In case of right-to-left directionality we paint the fixed part last.
if RTLOffset <> 0 then
PaintFixedArea;
// Blit the result to target.
BitBlt(DC, R.Left, R.Top, R.Right - R.Left, R.Bottom - R.Top, FHeaderBitmap.Canvas.Handle, R.Left, R.Top, SRCCOPY);
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.PaintHeader(TargetCanvas : TCanvas; R : TRect; const Target : TPoint;
RTLOffset : Integer = 0);
// Main paint method to draw the header.
// This procedure will paint the a slice (given in R) out of HeaderRect into TargetCanvas starting at position Target.
// This function does not offer the option to visually move floating columns due to scrolling. To accomplish this you
// need to call this method twice.
var
Run : TColumnIndex;
RightBorderFlag,
NormalButtonStyle,
NormalButtonFlags,
PressedButtonStyle,
PressedButtonFlags,
RaisedButtonStyle,
RaisedButtonFlags : Cardinal;
Images : TCustomImageList;
OwnerDraw,
AdvancedOwnerDraw : Boolean;
PaintInfo : THeaderPaintInfo;
RequestedElements,
ActualElements : THeaderPaintElements;
//--------------- local functions -------------------------------------------
procedure PrepareButtonStyles;
// Prepare the button styles and flags for later usage.
begin
RaisedButtonStyle := 0;
RaisedButtonFlags := 0;
case Header.Style of
hsThickButtons :
begin
NormalButtonStyle := BDR_RAISEDINNER or BDR_RAISEDOUTER;
NormalButtonFlags := BF_LEFT or BF_TOP or BF_BOTTOM or BF_MIDDLE or BF_SOFT or BF_ADJUST;
PressedButtonStyle := BDR_RAISEDINNER or BDR_RAISEDOUTER;
PressedButtonFlags := NormalButtonFlags or BF_RIGHT or BF_FLAT or BF_ADJUST;
end;
hsFlatButtons :
begin
NormalButtonStyle := BDR_RAISEDINNER;
NormalButtonFlags := BF_LEFT or BF_TOP or BF_BOTTOM or BF_MIDDLE or BF_ADJUST;
PressedButtonStyle := BDR_SUNKENOUTER;
PressedButtonFlags := BF_RECT or BF_MIDDLE or BF_ADJUST;
end;
else
// hsPlates or hsXPStyle, values are not used in the latter case
begin
NormalButtonStyle := BDR_RAISEDINNER;
NormalButtonFlags := BF_RECT or BF_MIDDLE or BF_SOFT or BF_ADJUST;
PressedButtonStyle := BDR_SUNKENOUTER;
PressedButtonFlags := BF_RECT or BF_MIDDLE or BF_ADJUST;
RaisedButtonStyle := BDR_RAISEDINNER;
RaisedButtonFlags := BF_LEFT or BF_TOP or BF_BOTTOM or BF_MIDDLE or BF_ADJUST;
end;
end;
end;
//---------------------------------------------------------------------------
procedure DrawBackground;
// Draw the header background.
var
BackgroundRect : TRect;
Details : TThemedElementDetails;
Theme : HTHEME;
begin
BackgroundRect := Rect(Target.X, Target.Y, Target.X + R.Right - R.Left, Target.Y + Header.Height);
with TargetCanvas do
begin
if hpeBackground in RequestedElements then
begin
PaintInfo.PaintRectangle := BackgroundRect;
TreeViewControl.DoAdvancedHeaderDraw(PaintInfo, [hpeBackground]);
end
else
begin
if (TreeViewControl.VclStyleEnabled and (seClient in TreeViewControl.StyleElements)) then
begin
Details := StyleServices.GetElementDetails(thHeaderItemRightNormal);
StyleServices.DrawElement(Handle, Details, BackgroundRect, @BackgroundRect {$IF CompilerVersion >= 34}, TreeViewControl.FCurrentPPI{$IFEND});
end
else
if tsUseThemes in TreeViewControl.TreeStates then
begin
Theme := OpenThemeData(TreeViewControl.Handle, 'HEADER');
DrawThemeBackground(Theme, Handle, HP_HEADERITEM, HIS_NORMAL, BackgroundRect, nil);
CloseThemeData(Theme);
end
else
begin
Brush.Color := Header.Background;
FillRect(BackgroundRect);
end;
end;
end;
end;
//---------------------------------------------------------------------------
procedure PaintColumnHeader(AColumn : TColumnIndex; ATargetRect : TRect);
// Draw a single column to TargetRect. The clipping rect needs to be set before
// this procedure is called.
var
SavedDC : Integer;
ColCaptionText : string;
ColImageInfo : TVTImageInfo;
Glyph : TThemedHeader;
Details : TThemedElementDetails;
WrapCaption : Boolean;
DrawFormat : Cardinal;
Pos : TRect;
DrawHot : Boolean;
ImageWidth : Integer;
Theme : HTHEME;
IdState : Integer;
begin
ColImageInfo.Ghosted := False;
PaintInfo.Column := Items[AColumn];
with PaintInfo, Column do
begin
IsHoverIndex := (AColumn = FHoverIndex) and (hoHotTrack in Header.Options) and (coEnabled in Options);
IsDownIndex := (AColumn = FDownIndex) and not FCheckBoxHit;
if (coShowDropMark in FOptions) and (AColumn = FDropTarget) and (AColumn <> FDragIndex) then
begin
if FDropBefore then
DropMark := dmmLeft
else
DropMark := dmmRight;
end
else
DropMark := dmmNone;
//Fix for issue 643
//Do not show the left drop mark if the position to drop is just preceding the target which means
//the dragged column will stay where it is
if (DropMark = dmmLeft) and (Items[FDragIndex].Position = TColumnPosition(Max(Integer(Items[FDropTarget].Position) - 1, 0)))
then
DropMark := dmmNone
else
//Do not show the right drop mark if the position to drop is just following the target which means
//the dragged column will stay where it is
if (DropMark = dmmRight) and (Items[FDragIndex].Position = Items[FDropTarget].Position + 1)
then
DropMark := dmmNone;
IsEnabled := (coEnabled in FOptions) and (TreeViewControl.Enabled);
ShowHeaderGlyph := (hoShowImages in Header.Options) and ((Assigned(Images) and (FImageIndex > - 1)) or FCheckBox);
ShowSortGlyph := (AColumn = Header.SortColumn) and (hoShowSortGlyphs in Header.Options);
WrapCaption := coWrapCaption in FOptions;
PaintRectangle := ATargetRect;
// This path for text columns or advanced owner draw.
if (Style = vsText) or not OwnerDraw or AdvancedOwnerDraw then
begin
// See if the application wants to draw part of the header itself.
RequestedElements := [];
if AdvancedOwnerDraw then
begin
PaintInfo.Column := Items[AColumn];
TreeViewControl.DoHeaderDrawQueryElements(PaintInfo, RequestedElements);
end;
if ShowRightBorder or (AColumn < Count - 1) then
RightBorderFlag := BF_RIGHT
else
RightBorderFlag := 0;
if hpeBackground in RequestedElements then
TreeViewControl.DoAdvancedHeaderDraw(PaintInfo, [hpeBackground])
else
begin
if TreeViewControl.VclStyleEnabled and (seClient in TreeViewControl.StyleElements) then
begin
if IsDownIndex then
Details := StyleServices.GetElementDetails(thHeaderItemPressed)
else
if IsHoverIndex then
Details := StyleServices.GetElementDetails(thHeaderItemHot)
else
Details := StyleServices.GetElementDetails(thHeaderItemNormal);
StyleServices.DrawElement(TargetCanvas.Handle, Details, PaintRectangle, @PaintRectangle{$IF CompilerVersion >= 34}, TreeViewControl.CurrentPPI{$IFEND});
end
else
begin
if tsUseThemes in TreeViewControl.TreeStates then
begin
Theme := OpenThemeData(TreeViewControl.Handle, 'HEADER');
if IsDownIndex then
IdState := HIS_PRESSED
else
if IsHoverIndex then
IdState := HIS_HOT
else
IdState := HIS_NORMAL;
DrawThemeBackground(Theme, TargetCanvas.Handle, HP_HEADERITEM, IdState, PaintRectangle, nil);
CloseThemeData(Theme);
end
else
if IsDownIndex then
DrawEdge(TargetCanvas.Handle, PaintRectangle, PressedButtonStyle, PressedButtonFlags)
else
// Plates have the special case of raising on mouse over.
if (Header.Style = hsPlates) and IsHoverIndex and
(coAllowClick in FOptions) and (coEnabled in FOptions) then
DrawEdge(TargetCanvas.Handle, PaintRectangle, RaisedButtonStyle,
RaisedButtonFlags or RightBorderFlag)
else
DrawEdge(TargetCanvas.Handle, PaintRectangle, NormalButtonStyle,
NormalButtonFlags or RightBorderFlag);
end;
end;
PaintRectangle := ATargetRect;
// calculate text and glyph position
InflateRect(PaintRectangle, - 2, - 2);
DrawFormat := DT_TOP or DT_NOPREFIX;
case CaptionAlignment of
taLeftJustify :
DrawFormat := DrawFormat or DT_LEFT;
taRightJustify :
DrawFormat := DrawFormat or DT_RIGHT;
taCenter :
DrawFormat := DrawFormat or DT_CENTER;
end;
if UseRightToLeftReading then
DrawFormat := DrawFormat + DT_RTLREADING;
ComputeHeaderLayout(PaintInfo, DrawFormat);
// Move glyph and text one pixel to the right and down to simulate a pressed button.
if IsDownIndex then
begin
OffsetRect(TextRectangle, 1, 1);
Inc(GlyphPos.X);
Inc(GlyphPos.Y);
Inc(SortGlyphPos.X);
Inc(SortGlyphPos.Y);
end;
// Advanced owner draw allows to paint elements, which would normally not be painted (because of space
// limitations, empty captions etc.).
ActualElements := RequestedElements * [hpeHeaderGlyph, hpeSortGlyph, hpeDropMark, hpeText, hpeOverlay];
// main glyph
FHasImage := False;
if Assigned(Images) then
ImageWidth := Images.Width
else
ImageWidth := 0;
if not (hpeHeaderGlyph in ActualElements) and ShowHeaderGlyph and
(not ShowSortGlyph or (FBiDiMode <> bdLeftToRight) or (GlyphPos.X + ImageWidth <= SortGlyphPos.X)) then
begin
if not FCheckBox then
begin
ColImageInfo.Images := Images;
Images.Draw(TargetCanvas, GlyphPos.X, GlyphPos.Y, FImageIndex, IsEnabled);
end
else
begin
with TreeViewControl do
begin
ColImageInfo.Images := CheckImages;
ColImageInfo.Index := GetCheckImage(nil, FCheckType, FCheckState, IsEnabled);
ColImageInfo.XPos := GlyphPos.X;
ColImageInfo.YPos := GlyphPos.Y;
PaintCheckImage(TargetCanvas, ColImageInfo, False);
end;
end;
FHasImage := True;
FImageRect.Left := GlyphPos.X;
FImageRect.Top := GlyphPos.Y;
FImageRect.Right := FImageRect.Left + ColImageInfo.Images.Width;
FImageRect.Bottom := FImageRect.Top + ColImageInfo.Images.Height;
end;
// caption
if WrapCaption then
ColCaptionText := FCaptionText
else
ColCaptionText := Text;
if IsHoverIndex and TreeViewControl.VclStyleEnabled then
DrawHot := True
else
DrawHot := (IsHoverIndex and (hoHotTrack in Header.Options) and not (tsUseThemes in TreeViewControl.TreeStates));
if not (hpeText in ActualElements) and (Length(Text) > 0) then
DrawButtonText(TargetCanvas.Handle, ColCaptionText, TextRectangle, IsEnabled, DrawHot, DrawFormat, WrapCaption);
// sort glyph
if not (hpeSortGlyph in ActualElements) and ShowSortGlyph then
begin
if tsUseExplorerTheme in TreeViewControl.TreeStates then
begin
Pos.TopLeft := SortGlyphPos;
Pos.Right := Pos.Left + SortGlyphSize.cx;
Pos.Bottom := Pos.Top + SortGlyphSize.cy;
if Header.SortDirection = sdAscending then
Glyph := thHeaderSortArrowSortedUp
else
Glyph := thHeaderSortArrowSortedDown;
Details := StyleServices.GetElementDetails(Glyph);
if not StyleServices.DrawElement(TargetCanvas.Handle, Details, Pos, @Pos {$IF CompilerVersion >= 34}, TreeViewControl.CurrentPPI {$IFEND}) then
PaintInfo.DrawSortArrow(Header.SortDirection);
end
else
begin
PaintInfo.DrawSortArrow(Header.SortDirection);
end;
end;
// Show an indication if this column is the current drop target in a header drag operation.
if not (hpeDropMark in ActualElements) and (DropMark <> dmmNone) then
begin
PaintInfo.DrawDropMark();
end;
if ActualElements <> [] then
begin
SavedDC := SaveDC(TargetCanvas.Handle);
TreeViewControl.DoAdvancedHeaderDraw(PaintInfo, ActualElements);
RestoreDC(TargetCanvas.Handle, SavedDC);
end;
end
else // Let application draw the header.
TreeViewControl.DoHeaderDraw(TargetCanvas, Items[AColumn], PaintRectangle, IsHoverIndex, IsDownIndex,
DropMark);
end;
end;
//--------------- end local functions ---------------------------------------
var
TargetRect : TRect;
MaxX : Integer;
begin
if IsRectEmpty(R) then
Exit;
// If both draw posibillities are specified then prefer the advanced way.
AdvancedOwnerDraw := (hoOwnerDraw in Header.Options) and Assigned(TreeViewControl.OnAdvancedHeaderDraw) and
Assigned(TreeViewControl.OnHeaderDrawQueryElements) and not (csDesigning in TreeViewControl.ComponentState);
OwnerDraw := (hoOwnerDraw in Header.Options) and Assigned(TreeViewControl.OnHeaderDraw) and
not (csDesigning in TreeViewControl.ComponentState) and not AdvancedOwnerDraw;
ZeroMemory(@PaintInfo, SizeOf(PaintInfo));
PaintInfo.TargetCanvas := TargetCanvas;
with PaintInfo, TargetCanvas do
begin
// Use shortcuts for the images and the font.
Images := Header.Images;
Font := Header.Font;
PrepareButtonStyles;
// At first, query the application which parts of the header it wants to draw on its own.
RequestedElements := [];
if AdvancedOwnerDraw then
begin
PaintRectangle := R;
Column := nil;
TreeViewControl.DoHeaderDrawQueryElements(PaintInfo, RequestedElements);
end;
// Draw the background.
DrawBackground;
// Now that we have drawn the background, we apply the header's dimensions to R.
R := Rect(Max(R.Left, 0), Max(R.Top, 0), Min(R.Right, TotalWidth), Min(R.Bottom, Header.Height));
// Determine where to stop.
MaxX := Target.X + R.Right - R.Left
//Fixes issues #544, #427 -- MaxX should also shift on BidiMode bdRightToLeft
+ RTLOffset; //added for fix
// Determine the start column.
Run := ColumnFromPosition(Point(R.Left + RTLOffset, 0), False);
if Run <= NoColumn then
Exit;
TargetRect.Top := Target.Y;
TargetRect.Bottom := Target.Y + R.Bottom - R.Top;
TargetRect.Left := Target.X - R.Left + Items[Run].FLeft + RTLOffset;
// TargetRect.Right will be set in the loop
ShowRightBorder := (Header.Style = hsThickButtons) or not (hoAutoResize in Header.Options) or (TreeViewControl.BevelKind = bkNone);
// Now go for each button.
while (Run > NoColumn) and (TargetRect.Left < MaxX) do
begin
TargetRect.Right := TargetRect.Left + Items[Run].Width;
// create a clipping rect to limit painting to button area
ClipCanvas(TargetCanvas, Rect(Max(TargetRect.Left, Target.X), Target.Y + R.Top,
Min(TargetRect.Right, MaxX), TargetRect.Bottom));
PaintColumnHeader(Run, TargetRect);
SelectClipRgn(Handle, 0);
TargetRect.Left := TargetRect.Right;
Run := GetNextVisibleColumn(Run);
end;
end;
end;
//----------------------------------------------------------------------------------------------------------------------
procedure TVirtualTreeColumns.SaveToStream(const Stream : TStream);
var
I : Integer;
begin
I := Count;
Stream.WriteBuffer(I, SizeOf(I));
if I > 0 then
begin
for I := 0 to Count - 1 do
TVirtualTreeColumn(Items[I]).SaveToStream(Stream);
Stream.WriteBuffer(FPositionToIndex[0], Count * SizeOf(TColumnIndex));
end;
// Data introduced with header stream version 5.
Stream.WriteBuffer(DefaultWidth, SizeOf(DefaultWidth));
end;
//----------------------------------------------------------------------------------------------------------------------
function TVirtualTreeColumns.TotalWidth : Integer;
var
LastColumn : TColumnIndex;
begin
Result := 0;
if (Count > 0) and (Length(FPositionToIndex) > 0) then
begin
LastColumn := FPositionToIndex[Count - 1];
if not (coVisible in Items[LastColumn].Options) then
LastColumn := GetPreviousVisibleColumn(LastColumn);
if LastColumn > NoColumn then
with Items[LastColumn] do
Result := FLeft + FWidth;
end;
end;
{ THeaderPaintInfo }
procedure THeaderPaintInfo.DrawDropMark();
var
Y : Integer;
lArrowWidth : Integer;
begin
lArrowWidth := TBaseVirtualTreeCracker(Self.Column.TreeViewControl).ScaledPixels(5);
Y := (PaintRectangle.Top + PaintRectangle.Bottom - 3 * lArrowWidth) div 2;
if DropMark = dmmLeft then
DrawArrow(TargetCanvas, TScrollDirection.sdLeft, Point(PaintRectangle.Left, Y), lArrowWidth)
else
DrawArrow(TargetCanvas, TScrollDirection.sdRight, Point(PaintRectangle.Right - lArrowWidth - (lArrowWidth div 2) {spacing}, Y), lArrowWidth);
end;
procedure THeaderPaintInfo.DrawSortArrow(pDirection : TSortDirection);
const
cDirection : array [TSortDirection] of TScrollDirection = (TScrollDirection.sdUp, TScrollDirection.sdDown);
var
lOldColor : TColor;
begin
lOldColor := TargetCanvas.Pen.Color;
TargetCanvas.Pen.Color := clDkGray;
DrawArrow(TargetCanvas, cDirection[pDirection], Point(SortGlyphPos.X, SortGlyphPos.Y), SortGlyphSize.cy);
TargetCanvas.Pen.Color := lOldColor;
end;
{ TVirtualTreeColumnHelper }
function TVirtualTreeColumnHelper.Header : TVTHeader;
begin
Result := Owner.Header;
end;
function TVirtualTreeColumnHelper.TreeViewControl : TBaseVirtualTreeCracker;
begin
Result := TBaseVirtualTreeCracker(Owner.Header.GetOwner);
end;
{ TVirtualTreeColumnsHelper }
function TVirtualTreeColumnsHelper.TreeViewControl : TBaseVirtualTreeCracker;
begin
Result := TBaseVirtualTreeCracker(Header.GetOwner);
end;
end.