delphidelphi-7delphi-10.2-tokyo

TListView Sort Arrows are disappearing on column resize


I am working on Delphi 10.2 Tokyo.

With the help of SortArrow On ListView I am able to add sorting arrows for my list view component(added Sort Arrow logic on colclick ), when I do the column resize sorting arrows are disappearing.

How to keep sorting arrows when i resize the column(similar to Windows Explorer)?

Below is my Component code:

unit uMyListView1;


interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ComCtrls, Menus, DB, ExtCtrls, Buttons, ADODB,Commctrl;

const ARROW_SIZE = 10;

type

  TMyListView = class(TListView)
  private
    FDataSet:                   TCustomADODataSet;

    FPressedColumn:             integer; //column index
    FLastPressedColumn:         integer; //last column to be pressed
    FSortDir:                   integer; //-1 = desc, 1 = asc
    FSortOrder:                 integer;

    procedure SetListViewColumns;
  protected
    procedure ColClick(Column: TListColumn); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor  Destroy; override;
    function    BuildListView: boolean;
  published
    property DataSet: TCustomADODataSet read FDataSet write FDataSet;
  end;

implementation

procedure TMyListView.ColClick(Column: TListColumn);
var DC: HDC;
    Pos: TPoint;
  Header: HWND;
  Item: THDItem;
begin
  inherited;
  FLastPressedColumn := -1;
  FPressedColumn := Column.Index;

  FSortDir := 1;

//implement sorting

  Header := ListView_GetHeader(Self.Handle);
  ZeroMemory(@Item, SizeOf(Item));
  Item.Mask := HDI_FORMAT;

  //remove arrow old cloumn
  if FLastPressedColumn <> -1 then
  begin
    Header_GetItem(Header,  FLastPressedColumn, Item);
    Item.fmt := Item.fmt and not (HDF_SORTUP or HDF_SORTDOWN);//remove both flags
    Header_SetItem(Header, FLastPressedColumn, Item);
  end;


  Header_GetItem(Header,  Column.Index, Item);
  Item.fmt := Item.fmt and not (HDF_SORTUP or HDF_SORTDOWN);//remove both flags

  if (FSortDir = 1) then
    Item.fmt := Item.fmt or HDF_SORTUP//include the sort ascending flag
  else if (FSortDir = -1) then
    Item.fmt := Item.fmt or HDF_SORTDOWN;


  Header_SetItem(Header, Column.Index, Item);
end;

constructor TMYListView.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FPressedColumn     := -1;
  FLastPressedColumn := -1;
  FSortOrder         := 0;
end;

destructor TMYListView.Destroy;
begin
  inherited Destroy;
end;

procedure TMYListView.SetListViewColumns;
var
  NewColumn:        TListColumn;
  i:                integer;
begin
  if FDataSet <> nil then with FDataSet, Self.Columns do
  begin
    Clear;  //clears any columns
    for i := 0 to FieldCount - 1 do
      if Fields[i].Visible then
      begin
        NewColumn          := Add;
        NewColumn.Caption  := Fields[i].DisplayLabel;
        NewColumn.Width    := Fields[i].DisplayWidth * 10;
        NewColumn.Alignment := Fields[i].Alignment;
      end;
  end;
end;

function TMYListView.BuildListView: boolean;
var NewListItem:     TListItem;
    i:               integer;
begin
  Result := FALSE;

  FPressedColumn     := -1;
  FLastPressedColumn := -1;

  Items.Clear;

  if FDataSet = NIL then
  begin
    MessageDlg('The Dataset is NIL', mtError, [mbOK], 0);
    Exit;
  end;
  try
    FDataset.Open;
    SetListViewColumns;

    with FDataSet do
    Begin
      Items.BeginUpdate;
      if not EOF then while not EOF do
      begin
        NewListItem := Items.Add;
        for i := 0 to FieldCount - 1 do
        begin
          if Fields[i].Visible then
          begin
            if i = 0 then NewListItem.Caption := Fields[0].DisplayText
            else if Fields[i].Visible then NewListItem.SubItems.Add(Fields[i].DisplayText);

          end;
        end;
        Next;
        Application.ProcessMessages;
      end;
    end;

    Result := TRUE;
  finally
    FDataSet.Close;
    Items.EndUpdate;
  end;

end;

end.

Solution

  • Not sure why your arrow would disappear. Worse case, you might need to have your ListView handle HDN_TRACK/HDN_ENDTRACK notifications from the Header to reset your current arrow, if any.

    However, there are some other issues in your code in relation to how you are managing the arrow.

    Your ColClick() method is resetting FLastPressedColumn to -1 before using it to clear out an existing arrow:

    begin
      inherited;
      //FLastPressedColumn := -1; // <-- REMOVE THIS!
    
      ...
    
      //remove arrow old cloumn
      if FLastPressedColumn <> -1 then
      begin
        ...
        FLastPressedColumn := -1; // <-- MOVED DOWN HERE INSTEAD!
      end;
    
      ...
    end;
    

    In fact, you don't need FLastPressedColumn at all, FPressedColumn will suffice by itself:

    procedure TMyListView.ColClick(Column: TListColumn);
    var
      LNewColumn: Integer;
      Item: THDItem;
    begin
      inherited;
    
      if Column <> nil then
        LNewColumn := Column.Index
      else
        LNewColumn := -1;
    
      if FPressedColumn <> LNewColumn then
      begin
        FSortDir := 1;
    
        //implement sorting
    
        Header := ListView_GetHeader(Self.Handle);
        ZeroMemory(@Item, SizeOf(Item));
        Item.Mask := HDI_FORMAT;
    
        //remove arrow old cloumn
        if FPressedColumn <> -1 then
        begin
          Header_GetItem(Header,  FPressedColumn, Item);
          Item.fmt := Item.fmt and not (HDF_SORTUP or HDF_SORTDOWN);//remove both flags
          Header_SetItem(Header, FPressedColumn, Item);
        end;
    
        FPressedColumn := LNewColumn;
        if FPressedColumn <> -1 then
        begin
          Header_GetItem(Header, FPressedColumn, Item);
          Item.fmt := Item.fmt and not (HDF_SORTUP or HDF_SORTDOWN);//remove both flags
    
          if (FSortDir = 1) then
            Item.fmt := Item.fmt or HDF_SORTUP//include the sort ascending flag
          else if (FSortDir = -1) then
            Item.fmt := Item.fmt or HDF_SORTDOWN;
    
          Header_SetItem(Header, FPressedColumn, Item);
        end;
      end;
    end;
    

    Also, your component is not handling window recreation at all. If the ListView's HWND gets recreated (which can and does happen during the process's lifetime), you need to restore your current sort arrow, if any. You can override the virtual CreateWnd() or CreateWindowHandle() method to call Header_SetItem() if FPressedColumn is not -1:

    type
      TMyListView = class(TListView)
      ...
      protected
        ...
        procedure CreateWnd; override;
      ...
      end;
    
    ...
    
    procedure TMyListView.CreateWnd;
    var
      Header: HWND;
      Item: THDItem;
    begin
      inherited;
      if FPressedColumn <> -1 then
      begin
        Header := ListView_GetHeader(Self.Handle);
        ZeroMemory(@Item, SizeOf(Item));
        Item.Mask := HDI_FORMAT;
    
        Header_GetItem(Header,  FPressedColumn, Item);
        Item.fmt := Item.fmt and not (HDF_SORTUP or HDF_SORTDOWN);//remove both flags
    
        if (FSortDir = 1) then
          Item.fmt := Item.fmt or HDF_SORTUP//include the sort ascending flag
        else if (FSortDir = -1) then
          Item.fmt := Item.fmt or HDF_SORTDOWN;
    
        Header_SetItem(Header, FPressedColumn, Item);
      end;
    end;