TListView 排序箭头在调整列大小时消失

TListView Sort Arrows are disappearing on column resize

我正在研究 Delphi 10.2 东京。

SortArrow On ListView 的帮助下,我能够为我的列表视图组件添加排序箭头(在 colclick 上添加排序箭头逻辑),当我调整列大小时,排序箭头消失了。

如何在调整列大小时保持排序箭头(类似于 Windows Explorer)?

下面是我的组件代码:

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.

不确定为什么你的箭头会消失。更糟糕的情况是,您可能需要让 ListView 处理来自 Header 的 HDN_TRACK/HDN_ENDTRACK 通知以重置当前箭头(如果有)。

但是,您的代码中还有一些其他问题与您管理箭头的方式有关。

您的 ColClick() 方法正在将 FLastPressedColumn 重置为 -1 ,然后 使用它来清除现有箭头:

begin
  inherited;
  //FLastPressedColumn := -1; // <-- REMOVE THIS!

  ...

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

  ...
end;

事实上,您根本不需要 FLastPressedColumnFPressedColumn 本身就足够了:

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;

此外,您的组件根本不处理 window 娱乐。如果 ListView 的 HWND 被重新创建(can 并且 does 在进程的生命周期内发生),您需要恢复当前的排序箭头, 如果有的话。如果 FPressedColumn 不是 -1,您可以覆盖虚拟 CreateWnd()CreateWindowHandle() 方法来调用 Header_SetItem()

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;