{$I DFS.INC}  { Standard defines for all Delphi Free Stuff components }

// Delphi 2 and C++B 1 have incorrectly declared InsertItem as private.
{$IFDEF DFS_COMPILER_3_UP}
  {$DEFINE DFS_FIXED_LIST_VIEW}
{$ENDIF}

{.$DEFINE DFS_DEBUG}


{------------------------------------------------------------------------------}
{ TdfsEnhListView v3.72                                                        }
{------------------------------------------------------------------------------}
{ A list view control that provides enhanced functionality beyond the          }
{ standard list view.  For example, automatic sorting of simple data types,    }
{ owner draw event for vsReport mode, and more.  This does NOT require any     }
{ special version of COMCTL32.DLL.                                             }
{                                                                              }
{ Copyright 1998-2001, Brad Stowers.  All Rights Reserved.                     }
{                                                                              }
{ Copyright:                                                                   }
{ All Delphi Free Stuff (hereafter "DFS") source code is copyrighted by        }
{ Bradley D. Stowers (hereafter "author"), and shall remain the exclusive      }
{ property of the author.                                                      }
{                                                                              }
{ Distribution Rights:                                                         }
{ You are granted a non-exlusive, royalty-free right to produce and distribute }
{ compiled binary files (executables, DLLs, etc.) that are built with any of   }
{ the DFS source code unless specifically stated otherwise.                    }
{ You are further granted permission to redistribute any of the DFS source     }
{ code in source code form, provided that the original archive as found on the }
{ DFS web site (http://www.delphifreestuff.com) is distributed unmodified. For }
{ example, if you create a descendant of TDFSColorButton, you must include in  }
{ the distribution package the colorbtn.zip file in the exact form that you    }
{ downloaded it from http://www.delphifreestuff.com/mine/files/colorbtn.zip.   }
{                                                                              }
{ Restrictions:                                                                }
{ Without the express written consent of the author, you may not:              }
{   * Distribute modified versions of any DFS source code by itself. You must  }
{     include the original archive as you found it at the DFS site.            }
{   * Sell or lease any portion of DFS source code. You are, of course, free   }
{     to sell any of your own original code that works with, enhances, etc.    }
{     DFS source code.                                                         }
{   * Distribute DFS source code for profit.                                   }
{                                                                              }
{ Warranty:                                                                    }
{ There is absolutely no warranty of any kind whatsoever with any of the DFS   }
{ source code (hereafter "software"). The software is provided to you "AS-IS", }
{ and all risks and losses associated with it's use are assumed by you. In no  }
{ event shall the author of the softare, Bradley D. Stowers, be held           }
{ accountable for any damages or losses that may occur from use or misuse of   }
{ the software.                                                                }
{                                                                              }
{ Support:                                                                     }
{ Support is provided via the DFS Support Forum, which is a web-based message  }
{ system.  You can find it at http://www.delphifreestuff.com/discus/           }
{ All DFS source code is provided free of charge. As such, I can not guarantee }
{ any support whatsoever. While I do try to answer all questions that I        }
{ receive, and address all problems that are reported to me, you must          }
{ understand that I simply can not guarantee that this will always be so.      }
{                                                                              }
{ Clarifications:                                                              }
{ If you need any further information, please feel free to contact me directly.}
{ This agreement can be found online at my site in the "Miscellaneous" section.}
{------------------------------------------------------------------------------}
{ The lateset version of my components are always available on the web at:     }
{   http://www.delphifreestuff.com/                                            }
{ See ELV.txt for notes, known issues, and revision history.                   }
{------------------------------------------------------------------------------}
{ Date last modified:  June 28, 2001                                           }
{------------------------------------------------------------------------------}


unit EnhListView;

interface

{$IFNDEF DFS_WIN32}
  ERROR!  This unit only available for Delphi 2.0 or higher!!!
{$ENDIF}

uses
  Forms, Windows, Messages, Classes, Controls, ComCtrls, CommCtrl, SysUtils,
  {$IFDEF DFS_COMPILER_4_UP} ImgList, {$ENDIF} Graphics, StdCtrls, Menus;


const
  { This shuts up C++Builder 3 about the redefiniton being different. There
    seems to be no equivalent in C1.  Sorry. }
  {$IFDEF DFS_CPPB_3_UP}
  {$EXTERNALSYM DFS_COMPONENT_VERSION}
  {$ENDIF}
  DFS_COMPONENT_VERSION = 'TdfsEnhListView v3.72';

  DRAWTEXTEX_FLAGS = DT_NOPREFIX or DT_SINGLELINE or DT_VCENTER or
     DT_END_ELLIPSIS;
  DRAWTEXTEX_ALIGNMENT: array[TAlignment] of UINT = (DT_LEFT, DT_RIGHT,
     DT_CENTER);
  WM_OWNERDRAWCOLUMNS = WM_USER + 143;

type
  TIntArray = array[0..(MaxInt div SizeOf(Integer)-1)] of Integer;
  PIntArray = ^TIntArray;

  TResizeMethod = (rmFitText, rmFitHeader);
  TAutoColumnSort = (acsNoSort, acsSort, acsSortToggle);
  TAutoSortStyle = (assSmart, assDefault);
  TSortAs = (saNone, saString, saNumeric, saDateTime);
  TLVStyle = (lvStandard, lvOwnerDrawFixed);
  TLVHDrawItemEvent = procedure(Control: TWinControl; var ACanvas: TCanvas;
     Index: Integer; var ARect: TRect; Selected: boolean;
     var DefaultDrawing: boolean) of object;
  TLVMeasureItemEvent = procedure(Control: TWinControl;
     var AHeight: UINT) of object;
  TLVDrawItemEvent = procedure(Control: TWinControl; var ACanvas: TCanvas;
     Index: Integer; ARect: TRect; State: TOwnerDrawState;
     var DefaultDrawing, FullRowSelect: boolean) of object;
  TLVDrawSubItemEvent = procedure(Control: TWinControl; var ACanvas: TCanvas;
     Index, SubItem: Integer; ARect: TRect; State: TOwnerDrawState;
     var DefaultDrawing: boolean) of object;
  TLVAfterDrawItemEvent = procedure(Control: TWinControl; var ACanvas: TCanvas;
     Index: Integer; ARect: TRect; State: TOwnerDrawState) of object;
  TLVSortItemsEvent = procedure(Sender: TObject; Item1, Item2: TListItem;
     SortColumn: integer; var SortAs: TSortAs; var CompResult: integer) of object;
  TLVSortStatusEvent = procedure(Sender: TObject; SortColumn: integer;
     Ascending: boolean) of object;
  TLVEditCanceled = procedure(Sender: TObject; Item: TListItem) of object;
  {$IFNDEF DFS_COMPILER_4_UP}
  TLVNotifyEvent = procedure(Sender: TObject; Item: TListItem) of object;
  {$ENDIF}

  // Class for saved settings
  TdfsEnhLVSaveSettings = class(TPersistent)
  private
    FAutoSave: boolean;
    FRegistryKey: string;
    FSaveColumnSizes: boolean;
    FSaveCurrentSort: boolean;
    FSaveViewStyle: boolean;
  public
    constructor Create; virtual;
    procedure StoreColumnSizes(ColCount: integer;
       const IntArray: array of integer);
    procedure ReadColumnSizes(ColCount: integer;
       var IntArray: array of integer);
    procedure StoreCurrentSort(Ascending: boolean; SortCol: integer);
    procedure ReadCurrentSort(var Ascending: boolean; var SortCol: integer);
    procedure StoreViewStyle(Style: TViewStyle);
    function ReadViewStyle(Default: TViewStyle): TViewStyle;
  published
    property AutoSave: boolean read FAutoSave write FAutoSave default FALSE;
    property RegistryKey: string read FRegistryKey write FRegistryKey;
    property SaveColumnSizes: boolean
       read FSaveColumnSizes
       write FSaveColumnSizes
       default TRUE;
    property SaveCurrentSort: boolean
       read FSaveCurrentSort
       write FSaveCurrentSort
       default TRUE;
    property SaveViewStyle: boolean
       read FSaveViewStyle
       write FSaveViewStyle
       default TRUE;
  end;

  { The new class }
  TCustomEnhListView = class(TCustomListView)
  private
    FSortDirty: boolean;
    FUpdateCount: integer;
    FStyle: TLVStyle;
    FAutoColumnSort: TAutoColumnSort;
    FAutoSortStyle: TAutoSortStyle;
    FAutoResort: boolean;
    FAutoSortAscending: boolean;
    FTmpAutoSortAscending: boolean;
    FLastColumnClicked: Integer;
    FSaveSettings: TdfsEnhLVSaveSettings;
    FShowSortArrows: boolean;
    FReverseSortArrows: boolean;
    FSortUpBmp,
    FSortDownBmp: TBitmap;
    FCreatingWindowHandle: boolean;
{$IFDEF BACKGROUND_FIXED}
    FBackgroundImage: TBitmap;
{$ENDIF}
    FNoColumnResize: boolean;
    FOldHeaderWndProc: pointer;
    FHeaderInstance: pointer;
    FSearchStr: string;
    FSearchTickCount: Double;
    FColumnSearch: boolean;

    FOnSortBegin: TLVSortStatusEvent;
    FOnSortFinished: TLVSortStatusEvent;
    FOnMeasureItem: TLVMeasureItemEvent;
    FOnDrawItem: TLVDrawItemEvent;
    FOnDrawSubItem: TLVDrawSubItemEvent;
    FOnAfterDefaultDrawItem: TLVAfterDrawItemEvent;
    FOnDrawHeader: TLVHDrawItemEvent;
    FOnSortItems: TLVSortItemsEvent;
    FOnEditCanceled: TLVEditCanceled;
{$IFNDEF DFS_COMPILER_4_UP}
    FOnGetImageIndex: TLVNotifyEvent;
{$ENDIF}

    procedure HeaderWndProc(var Message: TMessage);
    { Message handlers }
    procedure CMSysColorChange(var Message: TWMSysColorChange);
       message CM_SYSCOLORCHANGE;
    procedure CMFontChanged(var Messsage: TMessage); message CM_FONTCHANGED;
    procedure CNMeasureItem(var Message: TWMMeasureItem); message CN_MEASUREITEM;
    procedure CNDrawItem(var Message: TWMDrawItem); message CN_DRAWITEM;
    procedure CNNotify(var Message: TWMNotify); message CN_NOTIFY;
    procedure WMDestroy(var Message: TWMDestroy); message WM_DESTROY;
    procedure WMDrawHeader(var Message: TWMDrawItem); message WM_DRAWITEM;
    procedure WMNotify(var Message: TWMNotify); message WM_NOTIFY;
    procedure WMOwnerDrawColumns(var Message: TMessage);
       message WM_OWNERDRAWCOLUMNS;
    procedure WMParentNotify(var Message: TWMParentNotify); message WM_PARENTNOTIFY;
  protected
    { USE WITH CARE.  This can be NIL }
    FCanvas: TCanvas;
    FHeaderHandle: HWND;
    procedure KeyUp(var Key: Word; Shift: TShiftState); override;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
{$IFNDEF DFS_COMPILER_4_UP}
    function GetItem(Value: TLVItem): TListItem;
{$ENDIF}
    procedure ResetOwnerDrawHeight;
    procedure InvalidateColumnHeader(Index: integer); virtual;
    procedure DoSort(ColumnIndex:integer; Descending: boolean); virtual;
    procedure SortBegin(ColumnIndex: integer; Ascending: boolean); virtual;
    procedure SortFinished(ColumnIndex: integer; Ascending: boolean); virtual;
    procedure SortItems(const Item1, Item2: TListItem; SortColumn: integer;
       var CompResult: integer); virtual;
    procedure MeasureItem(var Height: UINT); virtual;
    procedure DefaultDrawItem(Index: Integer; Rect: TRect;
       State: TOwnerDrawState; FullRowSelect: boolean); virtual;
    procedure DefaultDrawSubItem(Index, SubItem: integer; Rect: TRect;
       State: TOwnerDrawState); virtual;
    procedure ProcessDrawItemMsg(Index: Integer;
       Rect: TRect; State: TOwnerDrawState; var DefaultDrawing,
       FullRowSelect: boolean); virtual;
    function ActualColumnIndex(Index: integer): integer; virtual;
    function GetActualColumn(Index: integer): TListColumn; virtual;
    function GetSubItemText(Index, SubItem: integer): string; virtual;
    procedure DrawSubItem(Index, SubItem: Integer; Rect: TRect;
       State: TOwnerDrawState; var DefaultDrawing: boolean); virtual;
    procedure DrawItem(var Canvas: TCanvas; Index: Integer; Rect: TRect;
       State: TOwnerDrawState; var DefaultDrawing,
       FullRowSelect: boolean);
       {$IFDEF DFS_COMPILER_4_UP} reintroduce; overload; {$ENDIF} virtual;
    procedure AfterDrawItem(var Canvas: TCanvas; Index: Integer;
       Rect: TRect; State: TOwnerDrawState); virtual;
    procedure Edit(const Item: TLVItem); override;
    procedure EditCanceled(const Item: TLVItem); virtual;
    { Overriden ancestor methods }
    procedure ColClick(Column: TListColumn); override;
{$IFDEF DFS_FIXED_LIST_VIEW}
    procedure InsertItem(Item: TListItem); override;
{$ENDIF}
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure CreateWnd; override;
    procedure DestroyWnd; override;
    procedure ProcessDrawHeaderMsg(Index: Integer; Rect: TRect;
       State: TOwnerDrawState; var DefaultDrawing: boolean); virtual;
    procedure DrawHeader(var Canvas: TCanvas; Index: Integer; var Rect: TRect;
       Selected: boolean; var DefaultDrawing: boolean); virtual;
    procedure DefaultDrawHeader(var Canvas: TCanvas; Index: Integer;
       var Rect: TRect; Selected: boolean); virtual;
    procedure SetOnDrawHeader(Value: TLVHDrawItemEvent); virtual;
    procedure SetColumnsOwnerDrawFlag(OwnerDrawn: boolean); virtual;
    procedure CreateSortBmps(var UpBmp, DownBmp: TBitmap); virtual;
{$IFNDEF DFS_COMPILER_4_UP}
    procedure GetImageIndex(Item: TListItem); virtual;
{$ENDIF}
{$IFDEF BACKGROUND_FIXED}
    procedure BackgroundImageChanged(Sender: TObject); virtual;
{$ENDIF}

    { Property methods }
    procedure SetAutoColumnSort(Value: TAutoColumnSort);
    procedure SetAutoSortStyle(Value: TAutoSortStyle);
    procedure SetCurrentSortAscending(Value: boolean);
    procedure SetAutoSortAscending(Value: boolean);
    procedure SetStyle(Value: TLVStyle);
    procedure SetShowSortArrows(Value: boolean);
    procedure SetReverseSortArrows(Value: boolean);
    procedure SetLastColumnClicked(Value: integer);
    procedure SetAutoResort(Value: boolean);
{$IFDEF BACKGROUND_FIXED}
    procedure SetBackgroundImage(const Value: TBitmap);
{$ENDIF}
    function GetSmallImages: {$IFDEF DFS_COMPILER_4_UP} TCustomImageList {$ELSE}
       TImageList {$ENDIF};
    procedure SetSmallImages(Val: {$IFDEF DFS_COMPILER_4_UP} TCustomImageList
       {$ELSE} TImageList {$ENDIF});
    function GetVersion: string; virtual;
    procedure SetVersion(const Val: string);
    function GetCurrentColumnWidth(Index: integer): integer;

    procedure CreateParams(var Params: TCreateParams); override;
    procedure Loaded; override;

    { Should probably remain protected }
    property SortUpBmp: TBitmap
      read FSortUpBmp;
    property SortDownBmp: TBitmap
      read FSortDownBmp;

    { Should be made public by descendants as needed }
    property LastColumnClicked: Integer
      read FLastColumnClicked
      write SetLastColumnClicked;

    { Should be published by descendants as needed }
    property HeaderHandle: HWnd
       read FHeaderHandle;
    property AutoColumnSort: TAutoColumnSort
       read FAutoColumnSort
       write SetAutoColumnSort
       default acsNoSort;
    property AutoSortStyle: TAutoSortStyle
       read FAutoSortStyle
       write SetAutoSortStyle
       default assSmart;
    property AutoResort: boolean
       read FAutoResort
       write SetAutoResort
       default TRUE;
    property AutoSortAscending: boolean
       read FAutoSortAscending
       write SetAutoSortAscending
       default TRUE;
    property ColumnSearch: boolean
       read FColumnSearch
       write FColumnSearch
       default FALSE;
    property ShowSortArrows: boolean
       read FShowSortArrows
       write SetShowSortArrows
       default FALSE;
    property ReverseSortArrows: boolean
       read FReverseSortArrows
       write SetReverseSortArrows
       default FALSE;
    property CurrentSortAscending: boolean
       read FTmpAutoSortAscending
       write SetCurrentSortAscending;
    property SaveSettings: TdfsEnhLVSaveSettings
       read FSaveSettings
       write FSaveSettings;
    property Style: TLVStyle
       read FStyle
       write SetStyle
       default lvStandard;
    property CurrentColumnWidth[Index: integer]: integer
       read GetCurrentColumnWidth;
{$IFDEF BACKGROUND_FIXED}
    property BackgroundImage: TBitmap
       read FBackgroundImage
       write SetBackgroundImage;
{$ENDIF}       
    property NoColumnResize: boolean
       read FNoColumnResize
       write FNoColumnResize;
    // We have to redeclare this so we can hook into the read/write methods.
    property SmallImages:
       {$IFDEF DFS_COMPILER_4_UP} TCustomImageList {$ELSE} TImageList {$ENDIF}
       read GetSmallImages
       write SetSmallImages;

    { Events }
    property OnDrawHeader: TLVHDrawItemEvent
       read FOnDrawHeader
       write SetOnDrawHeader;
    property OnMeasureItem: TLVMeasureItemEvent
       read FOnMeasureItem
       write FOnMeasureItem;
    property OnDrawItem: TLVDrawItemEvent
       read FOnDrawItem
       write FOnDrawItem;
    property OnDrawSubItem: TLVDrawSubItemEvent
       read FOnDrawSubItem
       write FOnDrawSubItem;
    property OnAfterDefaultDrawItem: TLVAfterDrawItemEvent
       read FOnAfterDefaultDrawItem
       write FOnAfterDefaultDrawItem;
    property OnSortItems: TLVSortItemsEvent
       read FOnSortItems
       write FOnSortItems;
    property OnSortBegin: TLVSortStatusEvent
       read FOnSortBegin
       write FOnSortBegin;
    property OnSortFinished: TLVSortStatusEvent
       read FOnSortFinished
       write FOnSortFinished;
    property OnEditCanceled: TLVEditCanceled
       read FOnEditCanceled
       write FOnEditCanceled;
{$IFNDEF DFS_COMPILER_4_UP}
    property OnGetImageIndex: TLVNotifyEvent
       read FOnGetImageIndex
       write FOnGetImageIndex;
{$ENDIF}
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    function StoreSettings: boolean; virtual;
    function WriteSettings: boolean; virtual;
    function LoadSettings: boolean; virtual;
    function ReadSettings: boolean; virtual;
    procedure DefaultSort(ColumnIndex:integer; Descending: boolean); virtual;
    procedure Resort; virtual;
    // Use these as replacements for Items.BeginUpdate and EndUpdate.  They
    // call those methods, but they also inhibit autosorting until after the
    // last EndUpdate.
    procedure BeginUpdate; virtual;
    procedure EndUpdate; virtual;

    // Resize all columns.
    procedure ResizeColumns(ResizeMethod: TResizeMethod); virtual;

    // Move list item to new position.
    procedure MoveItem(OriginalIndex, NewIndex: Integer); virtual;

    function StringSelect(FindStr: string; ColumnIndex: Integer): boolean; virtual;
    function SubStringSelect(FindStr: string; ColumnIndex: Integer): boolean; virtual;

    // Accounts for re-ordered columns
    property ActualColumn[Index: integer]: TListColumn
       read GetActualColumn;
  published
    property Version: string
       read GetVersion
       write SetVersion
       stored FALSE;
  end;


  TdfsEnhListView = class(TCustomEnhListView)
  public
    property HeaderHandle;
    property CurrentSortAscending;
    property LastColumnClicked;
    property CurrentColumnWidth;
  published
    property AutoColumnSort;
    property AutoSortStyle;
    property AutoResort;
    property AutoSortAscending;
{$IFDEF BACKGROUND_FIXED}
    property BackgroundImage;
{$ENDIF}
    property ColumnSearch;
    property NoColumnResize;
    property ReverseSortArrows;
    property ShowSortArrows;
    property SaveSettings;
    property Style;

    property OnMeasureItem;
    property OnDrawItem;
    property OnDrawSubItem;
    property OnAfterDefaultDrawItem;
    property OnDrawHeader;
    property OnSortItems;
    property OnSortBegin;
    property OnSortFinished;
    property OnEditCanceled;

    { Publish TCustomListView inherited protected properties }
    property Align;
{$IFDEF DFS_COMPILER_4_UP}
    property Anchors;
    property BiDiMode;
{$ENDIF}
    property BorderStyle;
{$IFDEF DFS_COMPILER_4_UP}
    property BorderWidth;
{$ENDIF}
    property Color;
    property ColumnClick;
    property OnClick;
    property OnDblClick;
    property Columns;
{$IFDEF DFS_COMPILER_4_UP}
    property Constraints;
{$ENDIF}
    property Ctl3D;
{$IFDEF DFS_COMPILER_4_UP}
    property DragKind;
{$ENDIF}
    property DragMode;
    property ReadOnly
       default False;
    property Enabled;
    property Font;
    property HideSelection;
    property IconOptions;
    property Items;
    property AllocBy;
    property MultiSelect;
    property OnChange;
    property OnChanging;
    property OnColumnClick;
    property OnDeletion;
    property OnEdited;
    property OnEditing;
{$IFDEF DFS_COMPILER_4_UP}
    property OnEndDock;
{$ENDIF}
    property OnEnter;
    property OnExit;
    property OnInsert;
    property OnDragDrop;
    property OnDragOver;
    property DragCursor;
    property OnStartDrag;
    property OnEndDrag;
    property OnGetImageIndex;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
{$IFDEF DFS_COMPILER_4_UP}
    property OnResize;
    property OnSelectItem;
    property OnStartDock;
{$ENDIF}
    property ParentColor
       default False;
    property ParentFont;
    property ParentShowHint;
{$IFDEF DFS_COMPILER_4_UP}
    property ParentBiDiMode;
{$ENDIF}
    property ShowHint;
    property PopupMenu;
    property ShowColumnHeaders;
    property TabOrder;
    property TabStop
       default True;
    property ViewStyle;
    property Visible;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property LargeImages;
    property SmallImages;
    property StateImages;
  end;

var
  { Default drawing variables }
  DefDraw_TextOffset: integer; // Offset for the text -- 5
  DefDraw_ImageOffset: integer; // Offset for image -- 2


implementation

uses
  Registry, ExtListView;


var
  FDirection,
  FSortColNum: integer;

{$IFNDEF DFS_COMPILER_4_UP}
type
  TReplaceFlags = set of (rfReplaceAll, rfIgnoreCase);

function StringReplace(const S, OldPattern, NewPattern: string;
  Flags: TReplaceFlags): string;
var
  SearchStr, Patt, NewStr: string;
  Offset: Integer;
begin
  if rfIgnoreCase in Flags then
  begin
    SearchStr := AnsiUpperCase(S);
    Patt := AnsiUpperCase(OldPattern);
  end else
  begin
    SearchStr := S;
    Patt := OldPattern;
  end;
  NewStr := S;
  Result := '';
  while SearchStr <> '' do
  begin
    Offset := {$IFDEF DFS_COMPILER_2} Pos( {$ELSE} AnsiPos( {$ENDIF} Patt, SearchStr);
    if Offset = 0 then
    begin
      Result := Result + NewStr;
      Break;
    end;
    Result := Result + Copy(NewStr, 1, Offset - 1) + NewPattern;
    NewStr := Copy(NewStr, Offset + Length(OldPattern), MaxInt);
    if not (rfReplaceAll in Flags) then
    begin
      Result := Result + NewStr;
      Break;
    end;
    SearchStr := Copy(SearchStr, Offset + Length(Patt), MaxInt);
  end;
end;
{$ENDIF}

function IsValidNumber(S: string; var V: extended): boolean;
var
  NumCode: integer;
  FirstSpace: integer;
begin
  FirstSpace := Pos(' ', S);
  if FirstSpace > 0 then
    S := Copy(S, 1, FirstSpace - 1);
  Val(S, V, NumCode);
  Result := (NumCode = 0);
  if not Result then
  begin
    // Remove all thousands seperators
    S := StringReplace(S, ThousandSeparator, '', [rfReplaceAll]);
    // change DecimalSeperator to '.' because Val only recognizes that, not
    // the locale specific decimal char.  Stupid Val.
    S := StringReplace(S, DecimalSeparator, '.', [rfReplaceAll]);
    // and try again
    Val(S, V, NumCode);
    Result := (NumCode = 0);
  End;
end;

// date conversion will fail if using long format, e.g. '1 January 1994'
function IsValidDateTime(const S: string; var D: TDateTime): boolean;
var
  i: integer;
  HasDate: boolean;
  HasTime: boolean;
begin
  // Check for two date seperators.  This is because some regions use a "-"
  //  to seperate dates, so if we just checked for one we would flag negative
  //  numbers as being dates.
  i := Pos(DateSeparator, S);
  HasDate := i > 0;
  if HasDate and (i <> Length(S)) then
    HasDate := Pos(DateSeparator, Copy(S, i+1, Length(S)-i)) > 0;
  HasTime := Pos(TimeSeparator, S) > 0;
  Result := HasDate or HasTime;
  if Result then
  begin
    try
      if HasDate and HasTime then
        D := StrToDateTime(S)
      else if HasDate then
        D := StrToDate(S)
      else if HasTime then
        D := StrToTime(S);
    except
      // Something failed to convert...
      D := 0;
      Result := FALSE;
    end;
  end;
end; { IsValidDateTime }

function __CustomSortProc1__(Item1, Item2: TListItem; Data: integer): integer;
   stdcall;
var
  Str1, Str2: string;
  Val1, Val2: extended;
  Date1, Date2: TDateTime;
  Diff: TDateTime;
begin
  if (Item1 = NIL) or (Item2 = NIL) then
  begin
    // something bad happening, I'm outta here
    Result := 0;
    exit;
  end;

  try
    if FSortColNum = -1 then
    begin
      Str1 := Item1.Caption;
      Str2 := Item2.Caption;
    end else begin
      if FSortColNum < Item1.SubItems.Count then
        Str1 := Item1.SubItems[FSortColNum]
      else
        Str1 := '';
      if FSortColNum < Item2.SubItems.Count then
        Str2 := Item2.SubItems[FSortColNum]
      else
        Str2 := '';
    end;

    if TCustomEnhListView(Data).AutoSortStyle = assSmart then
    begin
      if IsValidDateTime(Str1, Date1) and IsValidDateTime(Str2, Date2) then
      begin
        Diff := Date1 - Date2;
        if Diff < 0.0 then Result := -1
        else if Diff > 0.0 then Result := 1
        else Result := 0
      end else if IsValidNumber(Str1, Val1) and IsValidNumber(Str2, Val2) then
      begin
        if Val1 < Val2 then Result := -1
        else if Val1 > Val2 then Result := 1
        else Result := 0
      end else
        Result := AnsiCompareStr(Str1, Str2);
    end else
      Result := AnsiCompareStr(Str1, Str2);

    Result := FDirection * Result; // Set direction flag.
  except
    Result := 0;  // Something went bad in the comparison.  Say they are equal.
  end;
end;

function __CustomSortProc2__(Item1, Item2: TListItem; Data: integer): integer;
   stdcall;
var
  EvRes: integer;
begin
  EvRes := 0;
  TCustomEnhListView(Data).SortItems(Item1, Item2, FSortColNum, EvRes);
  Result := EvRes * FDirection;
end;



{ TdfsEnhLVSaveSettings }

constructor TdfsEnhLVSaveSettings.Create;
begin
  inherited Create;

  FAutoSave := FALSE;
  FRegistryKey := '';
  FSaveViewStyle := TRUE;
  FSaveColumnSizes := TRUE;
  SaveCurrentSort := TRUE;
end;

procedure TdfsEnhLVSaveSettings.StoreColumnSizes(ColCount: integer;
   const IntArray: array of integer);
var
  Reg: TRegIniFile;
  x: integer;
  s: string;
begin
  if ColCount < 1 then exit;
  s := '';
  for x := 0 to ColCount-1 do
    s := s + IntToStr(IntArray[x]) + ',';
  SetLength(s, Length(s)-1);
  Reg := TRegIniFile.Create(FRegistryKey);
  try
    Reg.WriteString('Columns', 'Sizes', s);
  finally
    Reg.Free;
  end;
end;

procedure TdfsEnhLVSaveSettings.ReadColumnSizes(ColCount: integer;
   var IntArray: array of integer);
var
  Reg: TRegIniFile;
  x,y: integer;
  s: string;
begin
  if ColCount < 1 then exit;
  s := '';
  Reg := TRegIniFile.Create(FRegistryKey);
  try
    s := Reg.ReadString('Columns', 'Sizes', '');
  finally
    Reg.Free;
  end;
  if s = '' then
  begin
    IntArray[0] := -1;
    exit;
  end;
  y := 0;
  for x := 0 to ColCount-1 do
  begin
    try
      y := Pos(',', s);
      if y = 0 then
        y := Length(s)+1;
      IntArray[x] := StrToInt(Copy(s, 1, y-1));
    except
      { Nothing, just eat the exception };
    end;
    s := copy(s, y+1, length(s));
    if s = '' then break;
  end;
end;

procedure TdfsEnhLVSaveSettings.StoreCurrentSort(Ascending: boolean;
   SortCol: integer);
var
  Reg: TRegIniFile;
begin
  Reg := TRegIniFile.Create(FRegistryKey);
  try
    Reg.WriteBool('Sort', 'Ascending', Ascending);
    Reg.WriteInteger('Sort', 'SortCol', SortCol);
  finally
    Reg.Free;
  end;
end;

procedure TdfsEnhLVSaveSettings.ReadCurrentSort(var Ascending: boolean;
   var SortCol: integer);
var
  Reg: TRegIniFile;
begin
  Reg := TRegIniFile.Create(FRegistryKey);
  try
    Ascending := Reg.ReadBool('Sort', 'Ascending', TRUE);
    SortCol := Reg.ReadInteger('Sort', 'SortCol', 0);
  finally
    Reg.Free;
  end;
end;

procedure TdfsEnhLVSaveSettings.StoreViewStyle(Style: TViewStyle);
const
  STYLE_VAL: array[TViewStyle] of integer = (0, 1, 2, 3);
var
  Reg: TRegIniFile;
begin
  Reg := TRegIniFile.Create(FRegistryKey);
  try
    Reg.WriteInteger('ViewStyle', 'ViewStyle', STYLE_VAL[Style]);
  finally
    Reg.Free;
  end;
end;

function TdfsEnhLVSaveSettings.ReadViewStyle(Default: TViewStyle): TViewStyle;
const
  STYLES: array[0..3] of TViewStyle = (vsIcon, vsSmallIcon, vsList, vsReport);
var
  Reg: TRegIniFile;
  i: integer;
begin
  Reg := TRegIniFile.Create(FRegistryKey);
  try
    i := Reg.ReadInteger('ViewStyle', 'ViewStyle', -1);
    if (i >= Low(STYLES)) and (i <= High(STYLES)) then
      Result := STYLES[i]
    else
      Result := Default;
  finally
    Reg.Free;
  end;
end;


// Override constructor to "zero out" our internal variable.
constructor TCustomEnhListView.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  FSearchStr := '';
  FSearchTickCount := 0;
  FHeaderHandle := 0;
  FSortDirty := FALSE;
  FUpdateCount := 1; // inhibit sorting until finished creating.
  FSaveSettings := TdfsEnhLVSaveSettings.Create;
  FAutoColumnSort := acsNoSort;
  FAutoResort := TRUE;
  FAutoSortStyle := assSmart;
  FAutoSortAscending := TRUE;
  FTmpAutoSortAscending := FAutoSortAscending;
  FLastColumnClicked := -1;
  FCanvas := NIL;
  FStyle  := lvStandard;
  FSortUpBmp := NIL;
  FSortDownBmp := NIL;
  FShowSortArrows := FALSE;
  FReverseSortArrows := FALSE;
{$IFDEF BACKGROUND_FIXED}
  FBackgroundImage := TBitmap.Create;
{$ENDIF}
  FHeaderInstance := MakeObjectInstance(HeaderWndProc);
end;

destructor TCustomEnhListView.Destroy;
begin
{$IFDEF BACKGROUND_FIXED}
  FBackgroundImage.Free;
{$ENDIF}
  FSortUpBmp.Free;
  FSortDownBmp.Free;
  FCanvas.Free;
  if FHeaderHandle <> 0 then
    SetWindowLong(FHeaderHandle, GWL_WNDPROC, LongInt(FOldHeaderWndProc));
  FreeObjectInstance(FHeaderInstance);

  inherited Destroy;

  FSaveSettings.Free;
end;

procedure TCustomEnhListView.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);

  if (FStyle = lvOwnerDrawFixed) then
  begin
    Params.Style := Params.Style or LVS_OWNERDRAWFIXED;
    if FCanvas = NIL then
      FCanvas := TCanvas.Create;
  end else begin
    if (not assigned(FOnDrawHeader)) and (not FShowSortArrows) then
    begin
      FCanvas.Free;
      FCanvas := NIL;
    end;
  end;
end;

procedure TCustomEnhListView.CreateWnd;
begin
//  if FCreatingWindowHandle then exit;

  FCreatingWindowHandle := TRUE;
  try
    inherited CreateWnd;
    // If we are loading object from stream (form file), we have to wait until
    // everything is loaded before populating the list.  If we are not loading,
    // i.e. the component was created dynamically or was just dropped on a form,
    // we need to reset the flag now.
    if not (csLoading in ComponentState) then
      FUpdateCount := 0;

    // Something very bizarre happens in either TCustomListView or in the
    // list view code itself in COMCTL32.DLL:  The first WM_MEASUREITEM value
    // is not honored if the listview has small images assigned to it.  Instead
    // the value is ignored and the height of the images are used.  I found that
    // by forcing Windows to ask for the item height a second time, it would
    // honor the value then.
    if Style = lvOwnerDrawFixed then
      ResetOwnerDrawHeight;
  finally
    FCreatingWindowHandle := FALSE;
  end;
end;

procedure TCustomEnhListView.Loaded;
begin
  inherited Loaded;

{$IFDEF BACKGROUND_FIXED}
  BackgroundImageChanged(Self);
{$ENDIF}

  if not FCreatingWindowHandle then
    HandleNeeded;

  FUpdateCount := 0;

  if (not LoadSettings) or (not SaveSettings.SaveCurrentSort) then
  begin
    if Columns.Count > 0 then
      FLastColumnClicked := 0;
    Resort;
  end;

  // Something flaky going on.  Hard to explain, but this clears it up.
  PostMessage(Handle, WM_OWNERDRAWCOLUMNS, 0, 0);
end;

procedure TCustomEnhListView.WMDestroy(var Message: TWMDestroy);
begin
  StoreSettings;

  inherited;
end;


function TCustomEnhListView.StoreSettings: boolean;
begin
  if FSaveSettings.AutoSave and
     (([csDesigning, csLoading, csReading] * ComponentState) = []) then
    Result := WriteSettings
  else
    Result := FALSE;
end;

function TCustomEnhListView.WriteSettings: boolean;
var
  ColCount: integer;
  ColArray: PIntArray;
  x: integer;
begin
  Result := TRUE;
  ColCount := Columns.Count;
  if ColCount > 0 then
  begin
    GetMem(ColArray, SizeOf(Integer)*ColCount);
    try
      if FSaveSettings.SaveColumnSizes then
      begin
        for x := 0 to ColCount-1 do
          ColArray[x] := ActualColumn[x].Width;
        FSaveSettings.StoreColumnSizes(ColCount, ColArray^);
      end;
      if FSaveSettings.SaveCurrentSort then
        FSaveSettings.StoreCurrentSort(CurrentSortAscending, LastColumnClicked);
      if FSaveSettings.SaveViewStyle then
        FSaveSettings.StoreViewStyle(ViewStyle);
    finally
      FreeMem(ColArray);
    end;
  end;
end;

function TCustomEnhListView.LoadSettings: boolean;
begin
  if FSaveSettings.AutoSave and (not(csDesigning in ComponentState)) then
    Result := ReadSettings
  else
    Result := FALSE;
end;

function TCustomEnhListView.ReadSettings: boolean;
var
  ColCount: integer;
  ColArray: PIntArray;
  x: integer;
  SortCol: integer;
  SortAscending: boolean;
begin
  Result := TRUE;
  ColCount := Columns.Count;
  if ColCount > 0 then
  begin
    GetMem(ColArray, SizeOf(Integer)*ColCount);
    try
      if FSaveSettings.SaveColumnSizes then
      begin
        for x := 0 to ColCount-1 do
          ColArray[x] := ActualColumn[x].Width;
        FSaveSettings.ReadColumnSizes(ColCount, ColArray^);
        if ColArray[0] <> -1 then
          for x := 0 to ColCount-1 do
            ActualColumn[x].Width := ColArray[x];
      end;
    finally
      FreeMem(ColArray);
    end;
  end;

  if FSaveSettings.SaveCurrentSort then
  begin
    FSaveSettings.ReadCurrentSort(SortAscending, SortCol);
    if SortCol >= Columns.Count then
      SortCol := Columns.Count-1;
    if SortCol < 0 then
      SortCol := 0;
    BeginUpdate;
    try
      CurrentSortAscending := SortAscending;
      LastColumnClicked := SortCol;
      Resort;
    finally
      EndUpdate;
    end;
  end;

  if FSaveSettings.SaveViewStyle then
    ViewStyle := FSaveSettings.ReadViewStyle(ViewStyle);
end;

procedure TCustomEnhListView.DoSort(ColumnIndex:integer; Descending: boolean);
begin
  FSortDirty := FALSE;
  LastColumnClicked := ColumnIndex;
  SortBegin(ColumnIndex, not Descending);
  if Descending then
    FDirection := 1
  else
    FDirection := -1;
  FSortColNum := ColumnIndex - 1;
  if assigned(FOnSortItems) then
    CustomSort(@__CustomSortProc2__, integer(Self))
  else
    CustomSort(@__CustomSortProc1__, integer(Self));
  SortFinished(ColumnIndex, not Descending);
end;

procedure TCustomEnhListView.DefaultSort(ColumnIndex: integer;
   Descending: boolean);
begin
  // Check if the sort order should be toggled
  if FAutoColumnSort = acsSortToggle then
    if LastColumnClicked = ColumnIndex then
      FTmpAutoSortAscending := not Descending
    else
      FTmpAutoSortAscending := Descending;

  InvalidateColumnHeader(ColumnIndex);
  DoSort(ColumnIndex, Descending);
end;

procedure TCustomEnhListView.SortItems(const Item1, Item2: TListItem;
   SortColumn: integer; var CompResult: integer);
var
  SortAs: TSortAs;
  Str1, Str2: string;
  F1, F2: extended;
  Date1, Date2, Diff: TDateTime;
begin
  // The only way to get in here is if FOnSortItems is assigned, so don't bother
  //  checking for NIL
  SortAs := saNone;
  FonSortItems(Self, Item1, Item2, SortColumn, SortAs, CompResult);
  // Do they want us to sort it?
  if SortAs <> saNone then
  begin
    if SortColumn = -1 then
    begin
      Str1 := Item1.Caption;
      Str2 := Item2.Caption;
    end else begin
      if SortColumn < Item1.SubItems.Count then
        Str1 := Item1.SubItems[SortColumn]
      else
        Str1 := '';
      if SortColumn < Item2.SubItems.Count then
        Str2 := Item2.SubItems[SortColumn]
      else
        Str2 := '';
    end;

    case SortAs of
      saString: CompResult := AnsiCompareStr(Str1, Str2);
      saNumeric:
        begin
          if not IsValidNumber(Str1, F1) then
            F1 := 0;
          if not IsValidNumber(Str2, F2) then
            F2 := 0;
          if F1 < F2 then CompResult := -1
          else if F1 > F2 then CompResult := 1
          else CompResult := 0;
        end;
      saDateTime:
        begin
          if not IsValidDateTime(Str1, Date1) then
            Date1 := 0;
          if not IsValidDateTime(Str2, Date2) then
            Date1 := 0;
          Diff := Date1 - Date2;
          if Diff < 0.0 then CompResult := -1
          else if Diff > 0.0 then CompResult := 1
          else CompResult := 0
        end;
    end;
  end;
end;

procedure TCustomEnhListView.SortBegin(ColumnIndex: integer;
   Ascending: boolean);
begin
  if assigned(FOnSortBegin) then
    FOnSortBegin(Self, ColumnIndex, Ascending);
end;

procedure TCustomEnhListView.SortFinished(ColumnIndex: integer;
   Ascending: boolean);
begin
  if assigned(FOnSortFinished) then
    FOnSortFinished(Self, ColumnIndex, Ascending);
end;

procedure TCustomEnhListView.ColClick(Column: TListColumn);
begin
  // Check if the sort order should be toggled
  if FAutoColumnSort = acsSortToggle then
    if LastColumnClicked = Column.Index then
      FTmpAutoSortAscending := not FTmpAutoSortAscending
    else
      FTmpAutoSortAscending := FAutoSortAscending;

  inherited ColClick(Column);

  if (FAutoColumnSort <> acsNoSort) and (Column.Index < Columns.Count) then
    DoSort(Column.Index, FTmpAutoSortAscending);

  LastColumnClicked := Column.Index;
end;

{$IFDEF DFS_FIXED_LIST_VIEW}
procedure TCustomEnhListView.InsertItem(Item: TListItem);
begin
  inherited InsertItem(Item);
  if FAutoResort then
    Resort;
end;
{$ENDIF}


procedure TCustomEnhListView.Edit(const Item: TLVItem);
begin
  inherited Edit(Item);
  if FAutoResort then
    Resort;
end;

type
  THackListItems = class(TListItems)
  end;

procedure TCustomEnhListView.EditCanceled(const Item: TLVItem);
begin
  if assigned(FOnEditCanceled) then
    with Item do
      FOnEditCanceled(Self, THackListItems(Items).GetItem(iItem));
end;

{$IFNDEF DFS_COMPILER_4_UP}
function TCustomEnhListView.GetItem(Value: TLVItem): TListItem;
begin
  with Value do
    if (mask and LVIF_PARAM) <> 0 then Result := TListItem(lParam)
    else Result := Items[IItem];
end;
{$ENDIF}


{$IFNDEF DFS_COMPILER_4_UP}
type
  THackdfsExtListView = class(TdfsExtListView);
{$ENDIF}
procedure TCustomEnhListView.CNNotify(var Message: TWMNotify);
{$IFNDEF DFS_COMPILER_4_UP}
var
  Item: TListItem;
{$ENDIF}
begin
  inherited;

  with Message.NMHdr^ do
    case code of
{$IFNDEF DFS_FIXED_LIST_VIEW}
      LVN_INSERTITEM:
        if FAutoResort then
          Resort;
{$ENDIF}
      LVN_ENDLABELEDIT:
        with PLVDispInfo(Pointer(Message.NMHdr))^ do
          if (item.pszText = NIL) and (item.IItem <> -1) then
            EditCanceled(item);
{$IFNDEF DFS_COMPILER_4_UP}
      LVN_GETDISPINFO:
        begin
          Item := GetItem(PLVDispInfo(Message.NMHdr)^.item);
          if Item <> NIL then
            with PLVDispInfo(Message.NMHdr)^.item do
            begin
              if (mask and LVIF_IMAGE) <> 0 then
              begin
                if iSubItem = 0 then
                begin
                  GetImageIndex(Item);
                  iImage := Item.ImageIndex;
                  if Assigned(StateImages) then
                  begin
                    state := IndexToStateImageMask(Item.StateIndex + 1);
                    stateMask := $F000;
                    mask := mask or LVIF_STATE;
                  end;
                end;
              end;
            end;
        end;
{$ENDIF}
    end;
end;

procedure TCustomEnhListView.SetAutoColumnSort(Value: TAutoColumnSort);
begin
  if FAutoColumnSort <> Value then
  begin
    FAutoColumnSort := Value;
    if FAutoColumnSort <> acsNoSort then
      Resort;
  end;
end;

procedure TCustomEnhListView.SetAutoSortStyle(Value: TAutoSortStyle);
begin
  if FAutoSortStyle <> Value then
  begin
    FAutoSortStyle := Value;
    Resort;
  end;
end;

procedure TCustomEnhListView.SetAutoResort(Value: boolean);
begin
  if FAutoResort <> Value then
    FAutoResort := Value;
end;

procedure TCustomEnhListView.SetCurrentSortAscending(Value: boolean);
begin
  if FTmpAutoSortAscending <> Value then
  begin
    FTmpAutoSortAscending := Value;
    InvalidateColumnHeader(FLastColumnClicked);
  end;
end;

procedure TCustomEnhListView.SetAutoSortAscending(Value: Boolean);
begin
  if FAutoSortAscending <> Value then
  begin
    FAutoSortAscending := Value;
    FTmpAutoSortAscending := Value;
  end;
end;

procedure TCustomEnhListView.Resort;
begin
  FSortDirty := TRUE;
  if ((FAutoColumnSort <> acsNoSort) and (LastColumnClicked >= 0) and
     (LastColumnClicked < Columns.Count)) or (assigned(FOnSortItems)) then
  begin
    if FUpdateCount < 1 then
      DoSort(LastColumnClicked, FTmpAutoSortAscending);
  end;
end;

procedure TCustomEnhListView.BeginUpdate;
begin
  Items.BeginUpdate;
  inc(FUpdateCount);
end;


procedure TCustomEnhListView.EndUpdate;
begin
  dec(FUpdateCount);
  if FUpdateCount < 0 then
    FUpdateCount := 0; // In case someone gets overly happy with EndUpdate calls
  if FUpdateCount = 0 then
  begin
    // Need to resort?
    if FSortDirty then
      Resort;
  end;

  // Call this last so resort happens before screen redraw is re-enabled.
  Items.EndUpdate;
end;


procedure TCustomEnhListView.DrawItem(var Canvas: TCanvas; Index: Integer;
   Rect: TRect; State: TOwnerDrawState; var DefaultDrawing,
   FullRowSelect: boolean);
begin
  DefaultDrawing := not assigned(FOnDrawItem);
  if assigned(FOnDrawItem) then
    FOnDrawItem(Self, Canvas, Index, Rect, State, DefaultDrawing,FullRowSelect);
end;

procedure TCustomEnhListView.AfterDrawItem(var Canvas: TCanvas; Index: Integer;
   Rect: TRect; State: TOwnerDrawState);
begin
  if assigned(FOnAfterDefaultDrawItem) then
    FOnAfterDefaultDrawItem(Self, Canvas, Index, Rect, State);
end;

procedure TCustomEnhListView.CMSysColorChange(var Message: TWMSysColorChange);
begin
  // Need to recreate the sort arrow bmps to use the new system colors
  if ShowSortArrows then
    CreateSortBmps(FSortUpBmp, FSortDownBmp);
  inherited;
end;

procedure TCustomEnhListView.CMFontChanged(var Messsage: TMessage);
begin
  if HandleAllocated and (Style = lvOwnerDrawFixed) then
    RecreateWnd
  else
    inherited;
end;

procedure TCustomEnhListView.CNMeasureItem(var Message: TWMMeasureItem);
var
  DC: HDC;
  OldFont: HFONT;
  Size: TSize;
begin
  inherited;

  DC := CreateCompatibleDC(0);
  OldFont := SelectObject(DC, Font.Handle);
  try
    GetTextExtentPoint32(DC, 'Wy', 2, Size);
    // Owner drawing only happens in vsReport mode, so no need to check anything
    // besides that.
    // I'm checking SmallImages.Height here, but I don't think it'll do any
    // good.  From what I can tell, if you have SmallImages assigned, this
    // handler will get called but the value you give it is ignored and the
    // list uses it's normal item height.  Strange....
    if assigned(SmallImages) and (SmallImages.Height > Size.cy) then
      Message.MeasureItemStruct.itemHeight := SmallImages.Height
    else
      Message.MeasureItemStruct.itemHeight := Size.cy + 1;
  finally
    SelectObject(DC, OldFont);
    DeleteDC(DC);
  end;
  MeasureItem(Message.MeasureItemStruct.itemHeight);
  Message.Result := 1;
end;

procedure TCustomEnhListView.MeasureItem(var Height: UINT);
begin
  if assigned(FOnMeasureItem) then
    FOnMeasureItem(Self, Height);
end;


procedure TCustomEnhListView.CNDrawItem(var Message: TWMDrawItem);
var
  State: TOwnerDrawState;
  DoDefaultDrawing: boolean;
  FullRowSelect: boolean;
  SavedDC: integer;
begin { CNDrawItem }
  if FCanvas = NIL then exit;

  with Message.DrawItemStruct^ do
  begin
    {$IFDEF DFS_COMPILER_5_UP}
    State := TOwnerDrawState(LongRec(itemState).Lo);
    {$ELSE}
    State := TOwnerDrawState(WordRec(LongRec(itemState).Lo).Lo);
    {$ENDIF}
    SavedDC := SaveDC(hDC);
    FCanvas.Handle := hDC;
    try
      FCanvas.Font := Font;
      FCanvas.Brush := Brush;
      DoDefaultDrawing := FALSE;
      FullRowSelect := FALSE;
      ProcessDrawItemMsg(itemID, rcItem, State, DoDefaultDrawing, FullRowSelect);
    finally
      FCanvas.Handle := 0;
      RestoreDC(hDC, SavedDC);
    end;
  end;

  Message.Result := 1;
end;

function TCustomEnhListView.GetActualColumn(Index: integer): TListColumn;
begin
  // Delphi 2 and C++B 1 have a bug in TListColumn.GetWidth.  It returns zero
  // for the width if the handle hasn't been allocated yet instead of returning
  // the value of the internal storage variable like Delphi 3 does.  I've also
  // had some problems similar under Delphi 3, so I'm just always requiring the
  // handle to be valid.
  HandleNeeded;

  if Index >= Columns.Count then
    Result := NIL
  else
    Result := Columns[Index];
end;

function TCustomEnhListView.GetSubItemText(Index, SubItem: integer): string;
begin
  if SubItem < 0 then
    Result := Items[Index].Caption
  else
    Result := Items[Index].SubItems[SubItem];
end;

// SubItem is -1 for Caption item
procedure TCustomEnhListView.DrawSubItem(Index, SubItem: Integer; Rect: TRect;
   State: TOwnerDrawState; var DefaultDrawing: boolean);
begin
  DefaultDrawing := not assigned(FOnDrawSubItem);
  if assigned(FOnDrawSubItem) then
    FOnDrawSubItem(Self, FCanvas, Index, SubItem, Rect, State, DefaultDrawing);
end;

procedure TCustomEnhListView.DefaultDrawSubItem(Index, SubItem: Integer;
   Rect: TRect; State: TOwnerDrawState);
var
  DoDefaultDrawing: boolean;
  SavedDC: integer;
begin
  DoDefaultDrawing := csDesigning in ComponentState;
  SavedDC := SaveDC(FCanvas.Handle);
  try
    if not (csDesigning in ComponentState) then
      DrawSubItem(Index, SubItem, Rect, State, DoDefaultDrawing);

    if DoDefaultDrawing then
    begin
      if SubItem >= 0 then
        InflateRect(Rect, -4, 0);
      if ActualColumn[SubItem+1].Alignment = taLeftJustify then
        Inc(Rect.Left, DefDraw_TextOffset);
      DrawTextEx(FCanvas.Handle, PChar(GetSubItemText(Index, SubItem)), -1, Rect,
         DRAWTEXTEX_FLAGS or
         DRAWTEXTEX_ALIGNMENT[ActualColumn[SubItem+1].Alignment], NIL);
    end;
  finally
    RestoreDC(FCanvas.Handle, SavedDC);
  end;
end;

{$IFDEF DFS_COMPILER_4_UP}
type
  THackImageList = class(TCustomImageList);
{$ENDIF}

procedure TCustomEnhListView.DefaultDrawItem(Index: Integer; Rect: TRect;
   State: TOwnerDrawState; FullRowSelect: boolean);
{$IFDEF DFS_COMPILER_4_UP}
const
  DrawingStyles: array[TDrawingStyle] of Longint = (ILD_FOCUS, ILD_SELECTED,
    ILD_NORMAL, ILD_TRANSPARENT);
  Images: array[TImageType] of Longint = (0, ILD_MASK);
{$ENDIF}
var
{$IFDEF DFS_COMPILER_4_UP}
  DS: TDrawingStyle;
  x: integer;
{$ELSE}
  OldStyle: TDrawingStyle;
{$ENDIF}
  OldBlend: TColor;
  Count: Integer;
  SubRect: TRect;
  ImgTop: integer;
begin
  if Items[Index] = NIL then
    // something bad happening, I'm outta here
    exit;

  if Columns.Count > 0 then
  begin
    if (odSelected in State) then
    begin
      if Focused then
      begin
        FCanvas.Brush.Color := clHighlight;
        FCanvas.Font.Color := clHighlightText;
      end else begin
        if not HideSelection then
        begin
          FCanvas.Brush.Color := clBtnFace;
          FCanvas.Font.Color := clBtnText;
        end;
      end;
    end;
    SubRect := Rect;
    SubRect.Right := Rect.Left + CurrentColumnWidth[0]{ - 2};

    if assigned(StateImages) then
    begin
      StateImages.Draw(FCanvas, SubRect.Left + DefDraw_ImageOffSet,
         SubRect.Top + (SubRect.Bottom - SubRect.Top - StateImages.Height) div 2,
         Items[Index].StateIndex);
      Inc(SubRect.Left, StateImages.Width);
    end;

    if assigned(SmallImages) then
    begin
      OldBlend := SmallImages.BlendColor;
      SmallImages.BlendColor := clHighlight;
      ImgTop := SubRect.Top + (SubRect.Bottom - SubRect.Top -
        SmallImages.Height) div 2;
      {$IFDEF DFS_COMPILER_4_UP}
      { Changing DrawStyle causes an invalidate, which is very nasty since we
        are in the process of repainting here.  Continuous flickering.... }
      if Focused and ((odSelected in State) or Items[Index].Focused) then
        DS := dsSelected
      else
        DS := dsTransparent;
      // Draw OverlayImage
      if (Items[Index].OverlayIndex >= 0) and
         (Items[Index].OverlayIndex <= 3) then // vadid overlay index?
      begin
        x := IndexToOverlayMask(Items[Index].OverlayIndex+1);
        THackImageList(SmallImages).DoDraw(Items[Index].ImageIndex, FCanvas,
           SubRect.Left + DefDraw_ImageOffSet, ImgTop, DrawingStyles[DS] or
           Images[SmallImages.ImageType] or ILD_OVERLAYMASK and x, Enabled);
      end else
        THackImageList(SmallImages).DoDraw(Items[Index].ImageIndex, FCanvas,
           SubRect.Left + DefDraw_ImageOffSet, ImgTop,
           DrawingStyles[DS] or Images[SmallImages.ImageType], Enabled);


      {$ELSE}
      OldStyle := SmallImages.DrawingStyle;
      if Focused and ((odSelected in State) or Items[Index].Focused) then
        SmallImages.DrawingStyle := dsSelected
      else
        SmallImages.DrawingStyle := dsTransparent;

      SmallImages.Draw(FCanvas, SubRect.Left + DefDraw_ImageOffSet, ImgTop,
         Items[Index].ImageIndex);

      // Draw OverlayImage
      if (Items[Index].OverlayIndex >= 0) and
         (Items[Index].OverlayIndex <= 3) then // vadid overlay index?
        SmallImages.DrawOverlay(FCanvas, SubRect.Left + DefDraw_ImageOffSet,
           ImgTop, Items[Index].ImageIndex, Items[Index].OverlayIndex);

      SmallImages.DrawingStyle := OldStyle;
      {$ENDIF}

      SmallImages.BlendColor := OldBlend;
      if ActualColumn[0].Alignment = taLeftJustify then
        Inc(SubRect.Left, {DefDraw_TextOffset + }SmallImages.Width);
{    end else begin
      if ActualColumn[0].Alignment = taLeftJustify then
        Inc(SubRect.Left, DefDraw_TextOffset);}
    end;

    DefaultDrawSubItem(Index, -1, SubRect, State);

    // Already done column 0, start at 1.
    for Count := 1 to Columns.Count-1 do
    begin
      { Restore this through each iteration since they may screw with it in
        the OnDrawSubItem event. }
      if not FullRowSelect then
      begin
        FCanvas.Brush.Color := clWindow;
        FCanvas.Font.Color := clWindowText;
      end;

      if Count > Items[Index].SubItems.Count then
        continue; // Hidden item
      if ActualColumn[Count].Alignment = taLeftJustify then
      begin
        SubRect.Left := SubRect.Right;
        SubRect.Right := SubRect.Left + CurrentColumnWidth[Count];
//        Inc(SubRect.Left, DefDraw_TextOffset)
      end else begin
        SubRect.Left := SubRect.Right;// + DefDraw_TextOffset;
        SubRect.Right := SubRect.Left + CurrentColumnWidth[Count];
//        Dec(SubRect.Right, DefDraw_TextOffset);
      end;
      DefaultDrawSubItem(Index, Count-1, SubRect, State);
    end;
  end;
end;


procedure TCustomEnhListView.ProcessDrawItemMsg(Index: Integer; Rect: TRect;
   State: TOwnerDrawState; var DefaultDrawing, FullRowSelect: boolean);
var
  SubRect: TRect;
begin
  DefaultDrawing := csDesigning in ComponentState;
  if not (csDesigning in ComponentState) then
    DrawItem(FCanvas, Index, Rect, State, DefaultDrawing, FullRowSelect);

  if DefaultDrawing then
  begin
    FCanvas.FillRect(Rect);

    if (Index >= 0) then
    begin
      if (odSelected in State) then
      begin
        if (not HideSelection) or Focused then
        begin
          if Focused then
            FCanvas.Brush.Color := clHighlight
          else
            FCanvas.Brush.Color := clBtnFace;

          SubRect := Rect;
//          Inc(SubRect.Left, DefDraw_TextOffset - 2);
//          Dec(SubRect.Left, 2);
          if (not FullRowSelect) then
          begin
            if assigned(Items[Index]) then
              SubRect.Right := SubRect.Left +
                 FCanvas.TextWidth(Items[Index].Caption) + 8;
            if assigned(StateImages) then
              OffsetRect(SubRect, StateImages.Width, 0);
            if assigned(SmallImages) then
              OffsetRect(SubRect, SmallImages.Width, 0);
            // Don't let it go past first column width
            if (Columns.Count > 0) and
               (CurrentColumnWidth[0] < SubRect.Right) then
              SubRect.Right := CurrentColumnWidth[0];
          end else begin
            if assigned(StateImages) then
              Inc(SubRect.Left, StateImages.Width);
            if assigned(SmallImages) then
              Inc(SubRect.Left, SmallImages.Width);
          end;
          FCanvas.FillRect(SubRect);
        end;
      end;
      DefaultDrawItem(Index, Rect, State, FullRowSelect);
      if (odFocused in State) and Focused then
      begin
        SubRect := Rect;
//        Inc(SubRect.Left, DefDraw_TextOffset - 2);
//        Dec(SubRect.Left, 2);
        if (not FullRowSelect) then
        begin
          if assigned(Items[Index]) then
            SubRect.Right := SubRect.Left +
               FCanvas.TextWidth(Items[Index].Caption) + 8;
            if assigned(SmallImages) then
              OffsetRect(SubRect, SmallImages.Width, 0);
            if assigned(StateImages) then
              OffsetRect(SubRect, StateImages.Width, 0);
            // Don't let it go past first column width
            if (Columns.Count > 0) and
               (CurrentColumnWidth[0] < SubRect.Right) then
              SubRect.Right := CurrentColumnWidth[0];
        end else begin
          if assigned(StateImages) then
            Inc(SubRect.Left, StateImages.Width);
          if assigned(SmallImages) then
            Inc(SubRect.Left, SmallImages.Width);
        end;
        FCanvas.DrawFocusRect(SubRect);
      end;
    end else
      FCanvas.FillRect(Rect);

    if (not (csDesigning in ComponentState)) then
      AfterDrawItem(FCanvas, Index, Rect, State);
  end;
end;


procedure TCustomEnhListView.SetStyle(Value: TLVStyle);
begin
  if FStyle <> Value then
  begin
    FStyle := Value;
    if HandleAllocated then
      RecreateWnd;
  end;
end;

procedure TCustomEnhListView.SetReverseSortArrows(Value: boolean);
begin
  if Value <> FReverseSortArrows then
  begin
    FReverseSortArrows := Value;
    if ShowSortArrows then
    begin
      CreateSortBmps(FSortUpBmp, FSortDownBmp);
      InvalidateColumnHeader(FLastColumnClicked);
    end;
  end;
end;

procedure TCustomEnhListView.SetShowSortArrows(Value: boolean);
begin
  if Value <> FShowSortArrows then
    FShowSortArrows := Value;
  FSortUpBmp.Free;
  FSortDownBmp.Free;
  if FShowSortArrows then
  begin
    FSortUpBmp := TBitmap.Create;
    FSortDownBmp := TBitmap.Create;
    CreateSortBmps(FSortUpBmp, FSortDownBmp);
    if not (csReading in ComponentState) then
      SetColumnsOwnerDrawFlag(TRUE);
  end else begin
    FSortUpBmp := NIL;
    FSortDownBmp := NIL;

    if not (csReading in ComponentState) then
      SetColumnsOwnerDrawFlag(assigned(FOnDrawHeader))
  end;
  if HandleAllocated then
    Invalidate;
end;

procedure TCustomEnhListView.CreateSortBmps(var UpBmp, DownBmp: TBitmap);
var
  HeaderHeight: integer;
  MidPoint: integer;
  Bmp: TBitmap;
begin
  if UpBmp = NIL then
    UpBmp := TBitmap.Create;
  if DownBmp = NIL then
    DownBmp := TBitmap.Create;

  UpBmp.Canvas.Font.Assign(Font);
  HeaderHeight := UpBmp.Canvas.TextHeight('Wy') - 6;
  if HeaderHeight > 0 then
  begin
    if Odd(HeaderHeight) then
      Inc(HeaderHeight);
    UpBmp.Width := HeaderHeight;
    UpBmp.Height := HeaderHeight;
    DownBmp.Width := HeaderHeight;
    DownBmp.Height := HeaderHeight;
    MidPoint := HeaderHeight div 2;

    { Don't ask about the drawing.  I just fooled around until I got
      something I liked. }
    if FReverseSortArrows then
      Bmp := UpBmp
    else
      Bmp := DownBmp;
    with Bmp.Canvas do
    begin
      Brush.Color := clBtnFace;
      FillRect(Rect(0, 0, HeaderHeight, HeaderHeight));
      Pen.Color := clBtnShadow;
      MoveTo(MidPoint, HeaderHeight-2);
      LineTo(HeaderHeight-1, 0);
      Pixels[HeaderHeight-1, 0] := Pen.Color;
      Pen.Color := clBtnHighlight;
      MoveTo(HeaderHeight-2, 0);
      LineTo(0, 0);
      LineTo(MidPoint-1, HeaderHeight-2);
      Pixels[MidPoint-1, HeaderHeight-2] := Pen.Color;
    end;

    if FReverseSortArrows then
      Bmp := DownBmp
    else
      Bmp := UpBmp;
    with Bmp.Canvas do
    begin
      Brush.Color := clBtnFace;
      FillRect(Rect(0, 0, HeaderHeight, HeaderHeight));
      Pen.Color := clBtnHighlight;
      MoveTo(0, HeaderHeight-1);
      LineTo(MidPoint-1, 0);
      Pen.Color := clBtnShadow;
      MoveTo(MidPoint, 0);
      LineTo(HeaderHeight-1, HeaderHeight-1);
      LineTo(-1, HeaderHeight-1);
      Pixels[MidPoint, 0] := clBtnFace;
    end;
  end;
end;

procedure TCustomEnhListView.DestroyWnd;
begin
  if not FCreatingWindowHandle then
  begin
    inherited DestroyWnd;

    FHeaderHandle := 0;
  end;
end;

procedure TCustomEnhListView.DrawHeader(var Canvas: TCanvas; Index: Integer;
   var Rect: TRect; Selected: boolean; var DefaultDrawing: boolean);
begin
  DefaultDrawing := not assigned(FOnDrawHeader);
  if assigned(FOnDrawHeader) then
    FOnDrawHeader(Self, Canvas, Index, Rect, Selected, DefaultDrawing);
end;

procedure TCustomEnhListView.WMNotify(var Message: TWMNotify);
const
  RECURSE_FLAG: boolean = FALSE;
begin
  if NoColumnResize then
    case Message.NMHdr.code of
      HDN_BEGINTRACK, HDN_TRACK, HDN_BEGINTRACKW, HDN_TRACKW:
      begin
        Message.Result := 1;
        exit;
      end;
    end;

  inherited;
  // Note the recursion flag.  This is needed since the SetColumnsOwnerDrawFlag
  // call below will cause some HDN_xxx notification messages.
  if RECURSE_FLAG then
    exit;

  // For some reason, the SECOND time you drag a header width, it toasts the
  // column index in the draw item message.  Also seems to reset owner draw
  // info at times, too.  Anyway, the best fix I could come up with was to
  // always reset the owner draw flag.
  case Message.NMHdr.code of
    HDN_BEGINTRACK, HDN_ITEMCHANGED, HDN_BEGINTRACKW, HDN_ITEMCHANGEDW:
      begin
        if Message.NMHdr.code <> HDN_TRACK then
        begin
          RECURSE_FLAG := TRUE;
          try
            SetColumnsOwnerDrawFlag(assigned(FOnDrawHeader) or FShowSortArrows);
          finally
            RECURSE_FLAG := FALSE;
          end;
        end;
      end;
    HDN_DIVIDERDBLCLICK, HDN_DIVIDERDBLCLICKW:
      { D4 (and others probably) don't update column width when this happens. }
      begin
        with PHDNotify(Pointer(Message.NMHdr))^ do
          if Item < Columns.Count then
            {$IFDEF DFS_COMPILER_4_UP}
            Column[Item].Width :=
            {$ELSE}
            ActualColumn[Item].Width :=
            {$ENDIF}
              ListView_GetColumnWidth(Handle, Item);
      end;
  end;

(*  old way.  had some performance problems when used in conjunction with
    TToolbar97 component.  No idea why that would cause it, though.
  // For some reason, the SECOND time you drag a header width, it toasts the
  // column index in the draw item message.  Also seems to reset owner draw
  // info at times, too.  Anyway, the best fix I could come up with was to
  // always watch for a change in the header handle, and always reset the owner
  // draw flag.  Note the recursion flag.  This is needed since the
  // SetColumnsOwnerDrawFlag will cause some HDN_xxx notification messages.

  // Best way that I can find to snag the real header handle.  Kludgy at best,
  // but what else are you gonna do?
  case Message.NMHdr.code of
    HDN_LAST..HDN_FIRST:
      begin
        if Message.NMHdr.hwndFrom <> FHeaderHandle then
          FHeaderHandle := Message.NMHdr^.hwndFrom;

        if RECURSE_FLAG or (FUpdateCount > 0) then exit;

        RECURSE_FLAG := TRUE;
        try
          SetColumnsOwnerDrawFlag(assigned(FOnDrawHeader) or FShowSortArrows);
        finally
          RECURSE_FLAG := FALSE;
        end;
      end;
  end;
*)
end;

procedure TCustomEnhListView.WMDrawHeader(var Message: TWMDrawItem);
var
  State: TOwnerDrawState;
  DoDefaultDrawing: boolean;
  SavedDC: integer;
begin { CNDrawItem }
  if FCanvas = NIL then exit;

  with Message.DrawItemStruct^ do
  begin
    Message.Result := 1;
    {$IFDEF DFS_COMPILER_5_UP}
    State := TOwnerDrawState(LongRec(itemState).Lo);
    {$ELSE}
    State := TOwnerDrawState(WordRec(LongRec(itemState).Lo).Lo);
    {$ENDIF}
    SavedDC := SaveDC(hDC);
    FCanvas.Handle := hDC;
    try
      FCanvas.Font := Font;
      FCanvas.Brush := Brush;
      DoDefaultDrawing := FALSE;
      ProcessDrawHeaderMsg(itemID, rcItem, State, DoDefaultDrawing);
    finally
      FCanvas.Handle := 0;
      RestoreDC(hDC, SavedDC);
    end;
  end;
end;

procedure TCustomEnhListView.ProcessDrawHeaderMsg(Index: Integer; Rect: TRect;
   State: TOwnerDrawState; var DefaultDrawing: boolean);
begin
  FCanvas.Font.Assign(Font);
  FCanvas.Brush.Assign(Brush);
  FCanvas.Brush.Style := bsClear;
  FCanvas.Brush.Color := clBtnFace;

  DefaultDrawing := csDesigning in ComponentState;
  if not (csDesigning in ComponentState) then
    DrawHeader(FCanvas, Index, Rect, odSelected in State, DefaultDrawing);

  if DefaultDrawing then
    DefaultDrawHeader(FCanvas, Index, Rect, odSelected in State);
end;

procedure TCustomEnhListView.DefaultDrawHeader(var Canvas: TCanvas;
   Index: Integer; var Rect: TRect; Selected: boolean);
var
  TheColumn: TListColumn;
  Offset: integer;
  R, CR: TRect;
  Bmp: TBitmap;
begin

(******************************************************************************)
(* NOTE:  This method is overriden and replaced in TExtListView.  That means  *)
(*   that if changes are made here, they will also need to be made in         *)
(*   ExtListView.pas' DefaultDrawHeader method.                               *)
(******************************************************************************)

  if not Selected then
    InflateRect(Rect, -2, -2);
  Canvas.FillRect(Rect);
  if Selected then
    InflateRect(Rect, -2, -2);

  if (Index >= 0) and (Index < Columns.Count) then
  begin
    // Don't use ActualColumn[] here!  That's for SubItem foolery, not header.
    TheColumn := Columns[Index];

    if Selected then
    begin
      inc(Rect.Top);
      inc(Rect.Left);
    end;

    R := Rect;

    case TheColumn.Alignment of
      taRightJustify:
        Dec(R.Right, 4);
      taLeftJustify:
        Inc(R.Left, 4);
      // taCenter needs no modification
    end;

    if FShowSortArrows and (LastColumnClicked = Index) and
       (AutoColumnSort <> acsNoSort) then
    begin
      if CurrentSortAscending then
        Bmp := FSortUpBmp
      else
        Bmp := FSortDownBmp;

      if TheColumn.Alignment = taRightJustify then
        Inc(R.Left, Bmp.Width + 8)
      else
        Dec(R.Right, Bmp.Width + 8);

      { How big of a rectangle do we have to work with for the text? }
      CR := R;
      DrawTextEx(FCanvas.Handle, PChar(TheColumn.Caption), -1, CR,
         DRAWTEXTEX_FLAGS or DT_CALCRECT or
         DRAWTEXTEX_ALIGNMENT[TheColumn.Alignment], NIL);
      { Note that DT_CALCRECT does not adjust for alignment. We must do that }
      case TheColumn.Alignment of
        taRightJustify:
          R.Left := R.Right - (CR.Right - CR.Left);
        taCenter:
          begin
            R.Left := R.Left + (((R.Right - R.Left) - (CR.Right - CR.Left)) div
               2);
            R.Right := R.Left + (CR.Right - CR.Left);
          end;
      else // taLeftJustify: doesn't matter, that is what DT_CALCRECT returns
        R := CR;
      end;
      if R.Left < Rect.Left then
        R.Left := Rect.Left;
      if R.Right > Rect.Right then
        R.Right := Rect.Right;

      if Selected then
        OffsetRect(R, 1, 1);
      // Draw the caption in the rect available
      DrawTextEx(FCanvas.Handle, PChar(TheColumn.Caption), -1, R,
         DRAWTEXTEX_FLAGS or DRAWTEXTEX_ALIGNMENT[TheColumn.Alignment], NIL);

      // Draw the sort arrow bitmap
      Offset := (Rect.Bottom - Rect.Top - Bmp.Height) div 2;
      case TheColumn.Alignment of
        taRightJustify:
          // Only draw if we have enough room
          if (R.Left - Bmp.Width - 8) >= Rect.Left then
            Canvas.Draw(R.Left - Bmp.Width - 8, R.Top + Offset, Bmp);
      else // taLeftJustify, taCenter
        // Only draw if we have enough room
        if (R.Right + Bmp.Width + 8) <= Rect.Right then
          Canvas.Draw(R.Right + 8, R.Top + Offset, Bmp);
      end;
    end else begin
      if Selected then
        OffsetRect(R, 1, 1);
      DrawTextEx(FCanvas.Handle, PChar(TheColumn.Caption), -1, R,
         DRAWTEXTEX_FLAGS or DRAWTEXTEX_ALIGNMENT[TheColumn.Alignment], NIL);
    end;
  end;
end;

procedure TCustomEnhListView.SetOnDrawHeader(Value: TLVHDrawItemEvent);
begin
  FOnDrawHeader := Value;
  SetColumnsOwnerDrawFlag(assigned(FOnDrawHeader) or FShowSortArrows);
end;

procedure TCustomEnhListView.SetColumnsOwnerDrawFlag(OwnerDrawn: boolean);
var
  Item: THDItem;
  x: integer;
begin
  if not HandleAllocated then exit;

  for x := 0 to Columns.Count-1 do
  begin
    Item.Mask := HDI_FORMAT;
    if Header_GetItem(HeaderHandle, x, Item) then
    begin
      if OwnerDrawn then
        Item.Fmt := Item.Fmt or HDF_OWNERDRAW
      else
        Item.Fmt := Item.Fmt and not HDF_OWNERDRAW;
      Header_SetItem(HeaderHandle, x, Item);
    end;
  end;

  if OwnerDrawn then
  begin
    if (FCanvas = NIL) then
      FCanvas := TCanvas.Create;
  end else begin
    if (Style = lvStandard) and (FCanvas <> NIL) then
    begin
      FCanvas.Free;
      FCanvas := NIL;
    end;
  end;
end;

procedure TCustomEnhListView.SetLastColumnClicked(Value: integer);
var
  OldValue: integer;
begin
  if Value <> FLastColumnClicked then
  begin
    OldValue := FLastColumnClicked;
    FLastColumnClicked := Value;
    // If showing arrows and clicked column changes, we have to get rid of the
    // old sorting arrow by causing the header to be repainted.
    if FShowSortArrows then
      // Can't do this above because FLastColumnClicked is used to paint the
      // arrow
      InvalidateColumnHeader(OldValue);
  end;
end;

function TCustomEnhListView.ActualColumnIndex(Index: integer): integer;
begin
  Result := Index;
end;

procedure TCustomEnhListView.InvalidateColumnHeader(Index: integer);
  function RealColWidth(i: integer): integer;
  {$IFDEF DFS_COMPILER_4_UP}
  var
    Column: TLVColumn;
  {$ENDIF}
  begin
    {$IFDEF DFS_COMPILER_4_UP}
    Column.mask := LVCF_WIDTH;
    ListView_GetColumn(Handle, i, Column);
    Result := Column.cx;
    {$ELSE}
    Result := Columns[i].Width;
    {$ENDIF}
  end;
var
  R: TRect;
  x: integer;
  w: integer;
begin
  if (Index < 0) or (Index >= Columns.Count) or (HeaderHandle = 0) then
    exit;

  w := RealColWidth(Index);
  // We have to turn this into the actual column index if drag-drop headers have
  // re-arranged stuff in the TExtListView descendant component.
  Index := ActualColumnIndex(Index);

  Windows.GetClientRect(HeaderHandle, R);
  for x := 0 to Columns.Count - 1 do
    if ActualColumnIndex(x) < Index then
      inc(R.Left, RealColWidth(x));
  R.Right := R.Left + w;

  // Adjust for shadow
  InflateRect(R, -2, -2);
  InvalidateRect(HeaderHandle, @R, FALSE);
end;

procedure TCustomEnhListView.WMOwnerDrawColumns(var Message: TMessage);
begin
  SetColumnsOwnerDrawFlag(assigned(FOnDrawHeader) or FShowSortArrows);
  Update;
end;

function TCustomEnhListView.GetVersion: string;
begin
  Result := DFS_COMPONENT_VERSION;
end;

procedure TCustomEnhListView.SetVersion(const Val: string);
begin
  { empty write method, just needed to get it to show up in Object Inspector }
end;

procedure TCustomEnhListView.ResizeColumns(ResizeMethod: TResizeMethod);
var
  i: integer;
begin
  BeginUpdate;
  Columns.BeginUpdate;
  try
    for i := 0 to Columns.Count - 1 do
      if ResizeMethod = rmFitText then
        Columns[i].Width := -1
      else
        Columns[i].Width := -2;
  finally
    EndUpdate;
    Columns.EndUpdate;
  end;
end;


function TCustomEnhListView.GetCurrentColumnWidth(Index: integer): integer;
{$IFDEF DFS_COMPILER_4_UP}
var
  Column: TLVColumn;
{$ENDIF}
begin
{$IFDEF DFS_COMPILER_4_UP}
  if HandleAllocated then
  begin
    Column.mask := LVCF_WIDTH;
    ListView_GetColumn(Handle, ActualColumnIndex(Index), Column);
    Result := Column.cx;
  end else
    Result := ActualColumn[Index].Width;
{$ELSE}
  Result := ActualColumn[Index].Width;
{$ENDIF}
end;


{$IFDEF BACKGROUND_FIXED}
procedure TCustomEnhListView.SetBackgroundImage(
   const Value: TBitmap);
begin
  FBackgroundImage.Assign(Value);
  BackgroundImageChanged(Self);
end;
{$ENDIF}

{$IFDEF BACKGROUND_FIXED}
procedure TCustomEnhListView.BackgroundImageChanged(Sender: TObject);
begin
  Brush.Bitmap := NIL;
  if (FBackgroundImage <> NIL) and (not FBackgroundImage.Empty) then
  begin
    // Transparent text
    ListView_SetTextBkColor(Handle, $FFFFFFFF);
    Brush.Bitmap := FBackgroundImage;
  end else begin
    ListView_SetTextBkColor(Handle, ColorToRGB(Color));
    Brush.Color := Color;
  end;
  Invalidate;
end;
{$ENDIF}

function TCustomEnhListView.GetSmallImages:
   {$IFDEF DFS_COMPILER_4_UP} TCustomImageList {$ELSE} TImageList {$ENDIF};
begin
  Result := inherited SmallImages;
end;

procedure TCustomEnhListView.SetSmallImages(Val:
   {$IFDEF DFS_COMPILER_4_UP} TCustomImageList {$ELSE} TImageList {$ENDIF});
begin
  inherited SmallImages := Val;

  // If owner drawn, we have to recreate the window so that the WM_MEASUREITEM
  // will get sent to us again, and we can handle it to account for image list
  // size.
  if HandleAllocated and (Style = lvOwnerDrawFixed) and (not (csLoading in
    ComponentState)) then
    ResetOwnerDrawHeight;
end;

procedure TCustomEnhListView.HeaderWndProc(var Message: TMessage);
  function DisallowColumnResize: boolean;
  var
    HTI: THDHitTestInfo;
    pt: TPoint;
  begin
    Result := NoColumnResize;
    if (not Result) and (Self is TCustomExtListView) then
    begin
      // get cursor position
      GetCursorPos(pt);
      // convert to coordinates on header control of the listview
      Windows.ScreentoClient(HeaderHandle, pt);
      // fill in hittest structure
      HTI.flags := HHT_ONHEADER Or HHT_ONDIVIDER;
      HTI.point.x := pt.x;
      HTI.point.y := pt.y;
      //  get the header's hit-test info
      SendMessage(HeaderHandle, HDM_HITTEST, LongInt(0),LongInt(@HTI));
      if (HTI.Item >=0) and (HTI.Item <
        TdfsExtListView(Self).ColumnsFormat.Count) then
        Result := not TdfsExtListView(Self).ColumnsFormat[HTI.Item].AllowResize;
    end;
  end;
var
  HTI: THDHitTestInfo;
  Icon: HICON;
begin
  try
    with Message do
    begin
      case Msg of
        WM_SETCURSOR:
          begin
            if DisallowColumnResize then
//            if NoColumnResize then
            begin
              Icon := GetClassLong(FHeaderHandle, GCL_HICON);
              if Icon = 0 then
                Icon := LoadCursor(0, IDC_ARROW);
              SetCursor(Icon);
              exit;
            end;
          end;
        WM_NCHITTEST:
          begin
            with TWMNCHitTest(Message) do
              if csDesigning in ComponentState then
              begin
                Result := Windows.HTTRANSPARENT;
                exit;
              end
              else if DisallowColumnResize then
              begin
                HTI.Point := Point(LoWord(Message.LParam), HiWord(Message.LParam));
                Windows.ScreenToClient(FHeaderHandle, HTI.Point);
                SendMessage(FHeaderHandle, HDM_HITTEST, 0, integer(@HTI));
                if ((HTI.Flags and HHT_ONHeader) = 0) then
                begin
                  Result := Windows.HTNOWHERE;
                  exit;
                end;
              end;
          end;
        WM_NCDESTROY:
          begin
            Result := CallWindowProc(FOldHeaderWndProc, FHeaderHandle, Msg, WParam, LParam);
            FHeaderHandle := 0;
            FOldHeaderWndProc := nil;
            Exit;
          end;
      end;
      Result := CallWindowProc(FOldHeaderWndProc, FHeaderHandle, Msg, WParam, LParam);
    end;
  except
    Application.HandleException(Self);
  end;
end;

procedure TCustomEnhListView.WMParentNotify(var Message: TWMParentNotify);
begin
  with Message do
    if (Event = WM_CREATE) and (FHeaderHandle = 0) then
    begin
      FHeaderHandle := ChildWnd;
      FOldHeaderWndProc := Pointer(GetWindowLong(FHeaderHandle, GWL_WNDPROC));
      SetWindowLong(FHeaderHandle, GWL_WNDPROC, LongInt(FHeaderInstance));
    end;
  inherited;
end;

procedure TCustomEnhListView.KeyDown(var Key: Word; Shift: TShiftState);
begin
  inherited;
  // Ctrl + causes all columns to change size as if their dividers had been
  // double-clicked.  Can't have that.
  if NoColumnResize and (Key = VK_ADD) and (Shift = [ssCtrl]) then
    Key := VK_SUBTRACT;
end;

procedure TCustomEnhListView.ResetOwnerDrawHeight;
var
  r: TRect;
  wp: TWindowPos;
begin
  // Found this code on www.codeguru.com in an article talking about how to get
  // an owner draw listview to ask for the item height (WM_MEASUREITEM) again.  
	GetWindowRect(Handle, r);
	wp.hwnd := Handle;
	wp.cx := Width;
	wp.cy := Height;
	wp.flags := SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOOWNERZORDER or SWP_NOZORDER;
	SendMessage(Handle, WM_WINDOWPOSCHANGED, 0, LPARAM(@wp));
end;

procedure TCustomEnhListView.MoveItem(OriginalIndex, NewIndex: Integer);
var
  Selected, Focused: boolean;
  ListItem:  TListItem;
begin
  if ((OriginalIndex < 0) or (OriginalIndex > Items.Count)) or
    ((NewIndex < 0) or (NewIndex > Items.Count)) then
    Exit;
    
  BeginUpdate;
  try
    Selected := Items[OriginalIndex].Selected;
    Focused := Items[OriginalIndex].Focused;
    if NewIndex < OriginalIndex then
      inc(OriginalIndex);
    if (NewIndex > OriginalIndex) then
      ListItem := Items.Insert(NewIndex + 1)
    else
      ListItem := Items.Insert(NewIndex);
    ListItem.Assign(Items[OriginalIndex]);
    Items.Delete(OriginalIndex);
    ListItem.Selected := Selected;
    ListItem.Focused := Focused;
  finally
    EndUpdate;
  end;
end;

procedure TCustomEnhListView.KeyUp(var Key: Word; Shift: TShiftState);
var
  PrevSearch: string;
  Ascii: array[0..1] of char;
  KBState: TKeyboardState;
begin
  inherited;
  if ColumnSearch then
  begin
    GetKeyboardState(KBState);
    if (ToAscii(Key, 0, KBState, Ascii, 0) = 1) and (Ascii[0] in [#32..#127]) then
    begin
      PrevSearch := FSearchStr;                      // remember searchstring
      if GetTickCount > FSearchTickCount + 1000 then // last search over one second ago?
        PrevSearch := '';                            // reset searchstring
      FSearchStr := PrevSearch + Ascii[0];           // Append searchstring
      FSearchTickCount := GetTickCount;              // remember last search time
      Key := 0;                                      // prevent automatic search on first column
      if not StringSelect(FSearchStr, LastColumnClicked) then
      begin
        MessageBeep(MB_ICONSTOP);
        FSearchStr := PrevSearch;
      end;
    end;
  end;
end;

function TCustomEnhListView.StringSelect(FindStr: string; ColumnIndex: Integer): boolean;
var
  SearchLen,
  SearchIndex,
  SearchStart: Integer;
begin
  Result := FALSE;
  SearchLen := Length(FindStr);
  if Assigned(Selected) then   // determine starting item
    SearchStart := Selected.Index + 1
  else
    SearchStart := 1;

  // Searches from currently selected item to last item
  // and from first item to currently selected item until result(found)

  SearchIndex := 0;
  while (SearchIndex < Items.Count) and not Result do
  begin
    if ColumnIndex = 0 then                                // find main or subitem?
      Result := AnsiCompareText(Copy(Items[(SearchStart + SearchIndex) mod
        Items.Count].Caption, 0, SearchLen), FindStr) = 0
    else
      Result := AnsiCompareText(Copy(Items[(SearchStart + SearchIndex) mod
        Items.Count].SubItems[ColumnIndex - 1], 0, SearchLen), FindStr) = 0;
    Inc(SearchIndex);
  end;
  if Result then
  begin
    Selected := Items[(SearchStart + SearchIndex - 1) mod Items.Count];
    ItemFocused := Selected;
  end;
end;

function TCustomEnhListView.SubStringSelect(FindStr: string;
  ColumnIndex: Integer): boolean;
var
  SearchIndex,
  SearchStart: Integer;
begin
  Result := FALSE;
  if Assigned(Selected) then  // determine starting item
    SearchStart := Selected.Index + 1
  else
    SearchStart := 1;

  // Searches from currently selected item to last item
  // and from first item to currently selected item until result(found)

  SearchIndex := 0;
  while (SearchIndex < Items.Count) and not Result do
  begin
    if ColumnIndex = 0 then                                // find main or subitem?
      Result := Pos(FindStr, Items[(SearchStart + SearchIndex) mod
        Items.Count].Caption) > 0
    else
      Result := Pos(FindStr, Items[(SearchStart + SearchIndex) mod
        Items.Count].SubItems[ColumnIndex - 1]) > 0;
    Inc(SearchIndex);
  end;
  if Result then
  begin
    Selected := Items[(SearchStart + SearchIndex - 1) mod Items.Count];
    ItemFocused := Selected;
  end;
end;

{$IFNDEF DFS_COMPILER_4_UP}
procedure TCustomEnhListView.GetImageIndex(Item: TListItem);
begin
  if Assigned(FOnGetImageIndex) then
    FOnGetImageIndex(Self, Item);
end;
{$ENDIF}

procedure TCustomEnhListView.Notification(AComponent: TComponent; Operation: TOperation);
begin
  if (Operation = opRemove) and (AComponent = SmallImages) then
    SmallImages := NIL;
  inherited Notification(AComponent, Operation);
end;

initialization
  DefDraw_TextOffset := 4;
  DefDraw_ImageOffset := 2;
end.

