TListView 中 sort-arrow 处理的问题
Problem with sort-arrow handling in TListView
在 Windows 10 in Delphi 11 Alexandria 中的 32 位 VCL 应用程序中,我有一个包含两列的 TListView
,client-aligned 形式。我正在使用此代码来处理 ListView Header:
中的 sort-arrows
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;
当我点击SECOND列的header列时,出现了第二列的排序箭头,但是第一列的排序箭头并没有消失!只有当我调整 ListView 大小时(通过调整表单大小时),第一列上的排序箭头才会消失。
那么如何让点击第二列时第一列的排序箭头立即消失header?
我找到了解决问题的方法:
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;
更改标志时,您不会在将标志添加到新列之前从先前选择的列中删除标志。
尝试更像这样的东西:
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;
另请注意,排序箭头不会在 ListView 调整大小时消失,而是在 column 调整大小时消失。所以你必须挂钩 ListView 来处理 HDN_ENDTRACK
通知来检测每列何时调整大小,例如:
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;
在 Windows 10 in Delphi 11 Alexandria 中的 32 位 VCL 应用程序中,我有一个包含两列的 TListView
,client-aligned 形式。我正在使用此代码来处理 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;
当我点击SECOND列的header列时,出现了第二列的排序箭头,但是第一列的排序箭头并没有消失!只有当我调整 ListView 大小时(通过调整表单大小时),第一列上的排序箭头才会消失。 那么如何让点击第二列时第一列的排序箭头立即消失header?
我找到了解决问题的方法:
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;
更改标志时,您不会在将标志添加到新列之前从先前选择的列中删除标志。
尝试更像这样的东西:
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;
另请注意,排序箭头不会在 ListView 调整大小时消失,而是在 column 调整大小时消失。所以你必须挂钩 ListView 来处理 HDN_ENDTRACK
通知来检测每列何时调整大小,例如:
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;