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.
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;