user1580348
user1580348

Reputation: 6051

Problem with sort-arrow handling in TListView

In a 32-bit VCL Application in Windows 10 in Delphi 11 Alexandria, I have a TListView with two columns, client-aligned to the form. I am using this code to handle the sort-arrows in the ListView Header:

procedure TformMain.ListView1ColumnClick(Sender: TObject; Column: TListColumn);
begin
  FSortedColumn := Column.Index; 
  case FSortedColumn of
    0: FColumn0SortedUp := not FColumn0SortedUp;
    1: FColumn1SortedUp := not FColumn1SortedUp;
  end;
  SetListHeaderSortArrow(FSortedColumn);
end;

procedure TformMain.SetListHeaderSortArrow(const aColumnIndex: Integer);
begin
  var Header: HWND;
  var Item: Winapi.CommCtrl.THDItem;
  case aColumnIndex of
    0:
      begin
        Header := Winapi.CommCtrl.ListView_GetHeader(ListView1.Handle);
        Winapi.Windows.ZeroMemory(@Item, SizeOf(Item));
        Item.Mask := Winapi.CommCtrl.HDI_FORMAT;
        Winapi.CommCtrl.Header_GetItem(Header, 0, Item);
        Item.fmt := Item.fmt and not (HDF_SORTUP or HDF_SORTDOWN); // remove both flags

        if FColumn0SortedUp then
          Item.fmt := Item.fmt or HDF_SORTUP // include the sort ascending flag
        else
          Item.fmt := Item.fmt or HDF_SORTDOWN; // include the sort descending flag

        Header_SetItem(Header, 0, Item);
      end;
    1:
      begin
        Header := Winapi.CommCtrl.ListView_GetHeader(ListView1.Handle);
        Winapi.Windows.ZeroMemory(@Item, SizeOf(Item));
        Item.Mask := Winapi.CommCtrl.HDI_FORMAT;
        Winapi.CommCtrl.Header_GetItem(Header, 1, Item);
        Item.fmt := Item.fmt and not (HDF_SORTUP or HDF_SORTDOWN); // remove both flags

        if FColumn1SortedUp then
          Item.fmt := Item.fmt or HDF_SORTUP // include the sort ascending flag
        else
          Item.fmt := Item.fmt or HDF_SORTDOWN; // include the sort descending flag

        Header_SetItem(Header, 1, Item);
      end;
  end;
end;

procedure TformMain.ListView1Resize(Sender: TObject);
begin
  // This gets inexplicably automatically executed 3 times at program start!!
  // This must be in OnResize, otherwise the sort-arrows get hidden when resizing the ListView:
  SetListHeaderSortArrow(FSortedColumn);
end;

When I click the column header of the SECOND column, the sort arrow on the second column appears, but the sort arrow on the first column does not disappear! Only when I resize the ListView (by resizing the form), the sort arrow on the first column disappears. So how can I make the sort arrow on the first column immediately disappear when clicking the second column header?

Upvotes: 0

Views: 313

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 595402

When changing the flags, you are not removing the flags from the previous selected column before adding the flags to the new column.

Try something more like this instead:

private:
  FColumnSortedUp: array[0..1] of Boolean;
  FSortedColumn: Integer;

...

procedure TformMain.FormCreate(Sender: TObject);
begin
  FSortedColumn := -1;
end;

procedure TformMain.ListView1ColumnClick(Sender: TObject; Column: TListColumn);
begin
  if FSortedColumn <> -1 then
    SetListHeaderSortArrow(FSortedColumn, False);

  if FSortedColumn = Column.Index then
    FColumnSortedUp[FSortedColumn] := not FColumnSortedUp[FSortedColumn];
  else
    FSortedColumn := Column.Index;

  SetListHeaderSortArrow(FSortedColumn, True);

  // sort ListView items as needed...
end;

procedure TformMain.SetListHeaderSortArrow(const aColumnIndex: Integer;
  const aEnabled: Boolean);
var
  Header: HWND;
  Item: THDItem;
begin
  Header := ListView_GetHeader(ListView1.Handle);
  ZeroMemory(@Item, SizeOf(Item));
  Item.Mask := HDI_FORMAT;
  Header_GetItem(Header, aColumnIndex, Item);
  Item.fmt := Item.fmt and not (HDF_SORTUP or HDF_SORTDOWN); // remove both flags

  if aEnabled then
  begin
    if FColumnSortedUp[aColumnIndex] then
      Item.fmt := Item.fmt or HDF_SORTUP // include the sort ascending flag
    else
      Item.fmt := Item.fmt or HDF_SORTDOWN; // include the sort descending flag
  end;

  Header_SetItem(Header, aColumnIndex, Item);
end;

Also note that the sort arrow does not disappear when the ListView is resized, but when the column is resized. So you will have to hook the ListView to handle HDN_ENDTRACK notifications to detect when each column is resized, eg:

private
  ...
  OldWndProc: TWndMethod;
  procedure ListViewWndProc(var Message: TMessage);

...

uses
  ..., Winapi.Messages, Winapi.CommCtrl;

procedure TformMain.FormCreate(Sender: TObject);
begin
  ...
  OldWndProc := ListView1.WindowProc;
  ListView1.WindowProc := ListViewWndProc;
end;

procedure TformMain.ListViewWndProc(var Message: TMessage);
begin
  OldWndProc(Message);
  if Message.Msg = WM_NOTIFY then
  begin
    if TWMNotify(Message).NMHdr.code = HDN_ENDTRACK then
    begin
      if PHDNotify(TWMNotify(Message).NMHdr).Item = FSortedColumn then
        SetListHeaderSortArrow(FSortedColumn, True);
    end;
  end;
end;

Upvotes: 2

user1580348
user1580348

Reputation: 6051

I have found a workaround that solves the problem:

procedure PALockWinControl(const WC: Vcl.Controls.TWinControl; ALock: Boolean);
begin
  if (not Assigned(WC)) or (WC.Handle = 0) then EXIT;

  if ALock then
    WC.Perform(WM_SETREDRAW, 0, 0)
  else
  begin
    WC.Perform(WM_SETREDRAW, 1, 0);
    RedrawWindow(WC.Handle, nil, 0, RDW_ERASE or RDW_FRAME or RDW_INVALIDATE or RDW_ALLCHILDREN);
  end;
end;

procedure MyRedrawWorkaround;
begin
  with formMain do
  begin
    PALockWinControl(ListView1, True);
    try
      ListView1.Align := alNone;
      ListView1.Width := lvMRUProjects.Width - 1;
      ListView1.Align := alClient;
    finally
      PALockWinControl(ListView1, False);
    end;
  end;
end;

procedure TformMain.ListView1ColumnClick(Sender: TObject; Column: TListColumn);
begin
  FSortedColumn := Column.Index;
  case FSortedColumn of
    0: FColumn0SortedUp := not FColumn0SortedUp;
    1: FColumn1SortedUp := not FColumn1SortedUp;
  end;
  SetListHeaderSortArrow(FSortedColumn);

  MyRedrawWorkaround;
end;

Upvotes: 0

Related Questions