2009. augusztus 5., szerda
Sort TListView columns by date or time
Problem/Question/Abstract:
Is there any way to sort columns in a TListView by date or time when a user clicks on the header of the column?
Answer:
Solve 1:
LV1 is a TListView with vsReport.
function CustomDateSortProc(Item1, Item2: TListItem; ParamSort: integer): integer;
stdcall;
begin
result := 0;
if StrToDateTime(item1.SubItems[0]) > StrToDateTime(item2.SubItems[0]) then
Result := 1
else if StrToDateTime(item1.SubItems[0]) < StrToDateTime(item2.SubItems[0]) then
Result := -1;
end;
function CustomNameSortProc(Item1, Item2: TListItem; ParamSort: integer): integer;
stdcall;
begin
Result := CompareText(Item1.Caption, Item2.Caption);
end;
procedure TForm1.GetFilesClick(Sender: TObject);
var
sr: TSearchRec;
Item: TListItem;
begin
if FindFirst('e:\*.*', faAnyFile, sr) = 0 then
repeat
if (sr.Attr and faDirectory) <> sr.Attr then
begin
item := LV1.items.add;
item.Caption := sr.name;
Item.SubItems.Add(DateTimeToStr(filedatetodatetime(sr.time)));
end;
until
FindNext(sr) <> 0;
FindClose(sr);
end;
procedure TForm1.LV1ColumnClick(Sender: TObject; Column: TListColumn);
begin
if column = LV1.columns[0] then
LV1.CustomSort(@CustomNameSortProc, 0)
else
LV1.CustomSort(@CustomDateSortProc, 0)
end;
Solve 2:
Open a new Delphi application project. Drop a listview (ListView1) onto the default form. Paste in the attached code. Hook up the FormCreate and ListView1ColumnClick event handlers.
The custom sort procedure (and the callback) save the day. There are some limits and drawbacks to this approach though. Since the listview is inherently unaware of data types, you have to bolt that onto the outside. This extra thrashing can represent a performance hit if you're doing something funky in the callback. This example uses up the TListView.Tag, TListColumn.Tag and TListItem.Data properties. This might clash with a scheme in place, or may sicken you because of its bold-faced greed. This system only allows for single-column sorts. This can easily be extended, though, by a reinterpretation of TListView.Tag into sort column_s_. No graphics in the column headers.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ComCtrls;
type
TForm1 = class(TForm)
ListView1: TListView;
procedure FormCreate(Sender: TObject);
procedure ListView1ColumnClick(Sender: TObject; Column: TListColumn);
private
public
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
function UnformatText(const Text: string; const VarType: Integer): Variant;
begin
{This is an ambitious function, in simple form. The standard text to type
variable conversion is fairly weak, so this function is a good place to
canonize that thinking.}
if Length(Text) = 0 then
Result := Null
else
begin
case VarType of
varBoolean:
if CompareText(Text, 'True') = 0 then
Result := True
else if CompareText(Text, 'False') = 0 then
Result := False
else if CompareText(Text, 'Yes') = 0 then
Result := True
else if CompareText(Text, 'No') = 0 then
Result := False
else
begin
Result := Null;
end;
else
{use the default handler}
Result := VarAsType(Text, VarType);
end;
end;
end;
function LVItemValue(const Item: TListItem; const Col, VarType: Integer): Variant;
begin
{get the indicated "cell's" text, return an empty string if either index is out of range}
if Item = nil then
Result := Null
else if Col < 0 then
Result := Null
else if Col > Item.SubItems.Count then
Result := Null
else if Col = 0 then
Result := UnformatText(Item.Caption, VarType)
else
begin
Result := UnformatText(Item.SubItems[Col - 1], VarType);
end;
end;
function LVSort(lParam1, lParam2: Integer; lParamSort: Integer): Integer; stdcall;
const
NULL_COMPARE = -1; {-1 floats nulls to top, +1, to bottom}
var
oLV: TListView;
iSortCol: Integer;
bSortAsc: Boolean;
iSortVarType: Integer;
vData1: Variant;
vData2: Variant;
begin
try
{resolve the reference to the listview being sorted}
oLV := TListView(lParamSort);
{is "no sort" being requested?}
if oLV.Tag = 0 then
begin
{not a very economic use of the data property...}
Result := Integer(TListItem(lParam1).Data) - Integer(TListItem(lParam2).Data);
exit;
end;
iSortCol := Abs(oLV.Tag) - 1;
bSortAsc := oLV.Tag >= 0;
{determine the data type}
if iSortCol < 0 then
iSortVarType := varString
else if iSortCol >= oLV.Columns.Count then
iSortVarType := varString
else
begin
iSortVarType := oLV.Columns[iSortCol].Tag;
end;
{get the data of interest}
vData1 := LVItemValue(TListItem(lParam1), iSortCol, iSortVarType);
vData2 := LVItemValue(TListItem(lParam2), iSortCol, iSortVarType);
{do some "null" handling that supercedes typed comparisons}
if VarIsNull(vData1) and VarIsNull(vData2) then
Result := 0 {they're both null}
else if VarIsNull(vData1) then
Result := NULL_COMPARE
else if VarIsNull(vData2) then
Result := -NULL_COMPARE
else if vData1 > vData2 then
Result := 1
else if vData1 < vData2 then
Result := -1
else
begin
Result := 0;
end;
if not bSortAsc then
Result := -Result;
except
Result := 0;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
function RandomNull(const Text: string): string;
begin
if Random(8) < 1 then
Result := ''
else
begin
Result := Text;
end;
end;
var
oCol: TListColumn;
oItem: TListItem;
iItem: Integer;
begin
Randomize;
{set listview properties}
with ListView1 do
begin
Items.Clear;
Columns.Clear;
Align := alClient;
ReadOnly := True;
SortType := stNone;
Tag := 0;
ViewStyle := vsReport;
end;
{default columns of different types}
oCol := ListView1.Columns.Add;
oCol.Caption := 'varDate';
oCol.Tag := varDate;
oCol.Width := 100;
oCol := ListView1.Columns.Add;
oCol.Caption := 'varBoolean';
oCol.Tag := varBoolean;
oCol.Width := 100;
oCol := ListView1.Columns.Add;
oCol.Caption := 'varInteger';
oCol.Tag := varInteger;
oCol.Width := 100;
oCol := ListView1.Columns.Add;
oCol.Caption := 'varCurrency';
oCol.Tag := varCurrency;
oCol.Width := 100;
oCol := ListView1.Columns.Add;
oCol.Caption := 'varString';
oCol.Tag := varString;
oCol.Width := 100;
{add items to the listview}
for iItem := 0 to 100 + Random(100) do
begin
{data property stores "original index" info}
oItem := ListView1.Items.Add;
oItem.Data := Pointer(iItem); {using this more like a Tag property}
{plug in some fake data}
oItem.Caption := RandomNull(FormatDateTime('dd-mmm-yyyy', Now() - Random(1000)));
if Random(2) < 1 then
oItem.SubItems.Add(RandomNull('Yes'))
else
begin
oItem.SubItems.Add(RandomNull('No'));
end;
oItem.SubItems.Add(RandomNull(FloatToStr(0.01 * Random(100000))));
oItem.SubItems.Add(RandomNull(IntToStr(Random(10000))));
oItem.SubItems.Add(RandomNull(Char(65 + Random(26)) + Char(65 + Random(26)) +
Char(65 + Random(26)) + Char(65 + Random(26)) + Char(65 + Random(26))));
end;
end;
procedure TForm1.ListView1ColumnClick(Sender: TObject; Column: TListColumn);
begin
{sort the sort column and order into the listview's tag}
if ListView1.Tag = Column.Index + 1 then
ListView1.Tag := -ListView1.Tag {desc sort}
else if ListView1.Tag = -(Column.Index + 1) then
ListView1.Tag := 0 {no sort}
else
begin
ListView1.Tag := Column.Index + 1; {asc sort}
end;
{pass the listview such that it will be sent to the sort procedure}
ListView1.CustomSort(LVSort, Integer(ListView1));
end;
end.
Feliratkozás:
Megjegyzések küldése (Atom)
Nincsenek megjegyzések:
Megjegyzés küldése