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.
