Marius0188
asked on
TListView Column Click Order By
Dear Experts,
I need a elegant way to order a TListViews items according to the column header that has been clicked.
I have a function to order only string types but need a way to order:
1. Integer/Float
2. String
3. Date
values as well.
Please help!
Thanks in advance!
I need a elegant way to order a TListViews items according to the column header that has been clicked.
I have a function to order only string types but need a way to order:
1. Integer/Float
2. String
3. Date
values as well.
Please help!
Thanks in advance!
ASKER
Thanks for the help.
Why are there 2 TListItem params in the function:
>>function LVBaseSort(lParam1, lParam2: TListItem ...........
Why are there 2 TListItem params in the function:
>>function LVBaseSort(lParam1, lParam2: TListItem ...........
Because the CustomSort (of the listview) calls the passed function which needs to be written to take 2 list items and a user defined integer. The 2 items being passed are the items that are currently being compared in the internal quick sort routine.
Russell
Hi,
See the delphi example for OnCompare event of listview.
See the delphi example for OnCompare event of listview.
ASKER
Unable to find that demo for Delphi 7.
Can you please provide the project folder?
I like the first example by rllibby.
Would love to see more so will wait a day or two.
Thanks all!
Can you please provide the project folder?
I like the first example by rllibby.
Would love to see more so will wait a day or two.
Thanks all!
From Delphi help file.
This example shows how to use the OnColumnClick and OnCompare events of a list view to let users sort the columns in a report-style list view by clicking on the column headers. This requires a global variable to keep track of the column that was clicked:
var ColumnToSort: Integer;
The OnColumnClick event handler sets the global variable to indicate the column to sort and calls AlphaSort:
procedure TForm1.ListView1ColumnClic k(Sender: TObject; Column: TListColumn);
begin
ColumnToSort := Column.Index;
(Sender as TCustomListView).AlphaSort ;
end;
The OnCompare event handler causes the list view to sort on the selected column:
procedure TForm1.ListView1Compare(Se nder: TObject; Item1, Item2: TListItem; Data: Integer; var Compare: Integer);
var
ix: Integer;
begin
if ColumnToSort = 0 then
Compare := CompareText(Item1.Caption, Item2.Capt ion)
else begin
ix := ColumnToSort - 1;
Compare := CompareText(Item1.SubItems [ix],Item2 .SubItems[ ix]);
end;
end;
Note: This OnCompare event handler uses the global CompareText function. An application may want to use AnsiCompareText, CompareStr, or AnsiCompareStr instead, depending on whether the comparison should be case-sensitive and whether the locale should be considered.
This example shows how to use the OnColumnClick and OnCompare events of a list view to let users sort the columns in a report-style list view by clicking on the column headers. This requires a global variable to keep track of the column that was clicked:
var ColumnToSort: Integer;
The OnColumnClick event handler sets the global variable to indicate the column to sort and calls AlphaSort:
procedure TForm1.ListView1ColumnClic
begin
ColumnToSort := Column.Index;
(Sender as TCustomListView).AlphaSort
end;
The OnCompare event handler causes the list view to sort on the selected column:
procedure TForm1.ListView1Compare(Se
var
ix: Integer;
begin
if ColumnToSort = 0 then
Compare := CompareText(Item1.Caption,
else begin
ix := ColumnToSort - 1;
Compare := CompareText(Item1.SubItems
end;
end;
Note: This OnCompare event handler uses the global CompareText function. An application may want to use AnsiCompareText, CompareStr, or AnsiCompareStr instead, depending on whether the comparison should be case-sensitive and whether the locale should be considered.
ASKER
Will this OnCompare example allow to order by any value type?
For example: TDate?
For example: TDate?
Yes, all you have to do is to set the Compare variable.
The OnCompare event handler compares the list items passed as the Item1 and Item2 parameters. If Item1 is the same as Item2 in the sort order, set the Compare parameter to 0. If Item1 is less than Item2, set the Compare parameter to a value less than 0. If Item1 is greater than Item2, set the Compare parameter to a value greater than 0.
e.g.
if Date1 = Date2 then
Compare := 0
else if Date1 < Date2 then
Compare := -1
else
Compare := 1;
The OnCompare event handler compares the list items passed as the Item1 and Item2 parameters. If Item1 is the same as Item2 in the sort order, set the Compare parameter to 0. If Item1 is less than Item2, set the Compare parameter to a value less than 0. If Item1 is greater than Item2, set the Compare parameter to a value greater than 0.
e.g.
if Date1 = Date2 then
Compare := 0
else if Date1 < Date2 then
Compare := -1
else
Compare := 1;
similarly reverse the assignment if you want to sort in descending
e.g.
if Date1 = Date2 then
Compare := 0
else if Date1 < Date2 then
Compare := 1
else
Compare := -1;
e.g.
if Date1 = Date2 then
Compare := 0
else if Date1 < Date2 then
Compare := 1
else
Compare := -1;
Here is a better Integer sort for Listviews.
{************************* ********** ********** ********** ****
Listview Integer column sort courtesy of "Grinder"
http://216.101.185.148/scripts/isapi.dll/execute?SearchFor=LVSort+Grinder&RecordLimit=250
Originally written to handle any type data, modified to work with Integer data only.
Don't forget stdcall!!!
Instead of using the tag for the direction, I just use the global variable FAscending.
************************** ********** ********** ********** ***}
var
FSortColumn: Integer;
FAscending : Boolean;
function LVIntegerSort
( lParam1
, lParam2: Integer
; lParamSort: Integer
): Integer; stdcall;
function LVItemValue(const Item: TListItem; Col: Integer): Integer;
begin
if Item = nil then
Result := -1
else
Result := StrToInt(Item.SubItems[Col - 1]);
end;
var
vData1: Integer;
vData2: Integer;
begin
try
vData1 := LVItemValue(TListItem(lPar am1), 3);
vData2 := LVItemValue(TListItem(lPar am2), 3);
if vData1 > vData2 then
Result := 1
else if vData1 < vData2 then
Result := -1
else begin
Result := 0;
end;
if not FAscending then
Result := -Result;
except
Result := 0;
end;
end;
{*************************
Listview Integer column sort courtesy of "Grinder"
http://216.101.185.148/scripts/isapi.dll/execute?SearchFor=LVSort+Grinder&RecordLimit=250
Originally written to handle any type data, modified to work with Integer data only.
Don't forget stdcall!!!
Instead of using the tag for the direction, I just use the global variable FAscending.
**************************
var
FSortColumn: Integer;
FAscending : Boolean;
function LVIntegerSort
( lParam1
, lParam2: Integer
; lParamSort: Integer
): Integer; stdcall;
function LVItemValue(const Item: TListItem; Col: Integer): Integer;
begin
if Item = nil then
Result := -1
else
Result := StrToInt(Item.SubItems[Col
end;
var
vData1: Integer;
vData2: Integer;
begin
try
vData1 := LVItemValue(TListItem(lPar
vData2 := LVItemValue(TListItem(lPar
if vData1 > vData2 then
Result := 1
else if vData1 < vData2 then
Result := -1
else begin
Result := 0;
end;
if not FAscending then
Result := -Result;
except
Result := 0;
end;
end;
To simplify things further, here is a solution that requires ONE line of code to create a sorting hander for any list view. This will provide ascending/descending sorting for your columns, and allows you to specify the sort type for each column:
Sort Types
--------------------------
stString - string
stInteger - integer
stFloat - floating point number
stDate - date value
stTime - time value
stDateTime - date and time value
Just save the unit code (provided below) as ListViewSort.pas and make sure it is accessible (..\lib is a good place, or the application's project path). Add ListViewSort to your uses clause, then in the FormCreate just add:
// Example only; you need to specify the desired sort type for your columns
TListViewSort.CreateOwned(
You should change the "[stString, stInteger, stDate]" to apply whatever sort you desire against each column. Note: if you have (for example) 3 columns, and provide less than 3 sort types, eg [stString, stInteger], then string sorting will be applied to the remaining columns.
Let me know if you have any problems,
Russell
---- ListViewSort.Pas ----
unit ListViewSort;
//////////////////////////
//
// Unit : ListViewSort
// Author : rllibby
// Date : 06.13.2006
// Description : Sorting class for list view control. Allows sorting to be
// handled for multiple columns (with different sorting), using
// a minimal amount of code.
//
//////////////////////////
interface
//////////////////////////
// Include units
//////////////////////////
uses
Windows, SysUtils, Classes, ComCtrls;
//////////////////////////
// Sorting types
//////////////////////////
type
TSortType = (stString, stInteger, stFloat, stDate, stTime, stDateTime);
//////////////////////////
// TListViewSort
//////////////////////////
type
TListViewSort = class(TComponent)
private
// Private declarations
FListView: TListView;
FSortTypes: TList;
FLastSort: Integer;
FAscending: Boolean;
protected
// Protected declarations
function GetSortType(Column: Integer): TSortType;
procedure OnColumnClick(Sender: TObject; Column: TListColumn);
public
// Public declarations
constructor CreateOwned(ListView: TListView; ColumnSorting: Array of TSortType);
destructor Destroy; override;
procedure ApplyLastSort;
end;
//////////////////////////
// Utility functions
//////////////////////////
function MakeColumnSort(Column: Integer; Ascending: Boolean): Integer;
function GetColumn(Value: Integer): Integer;
function GetAscending(Value: Integer): Boolean;
implementation
//// Base sorting function //////////////////////////
function LVBaseSort(lParam1, lParam2: TListItem; Column: Integer; Ascending: Boolean; SortType: TSortType): Integer;
var szItem1: String;
szItem2: String;
dwSubItem: Integer;
dblResult: Double;
begin
// Check items
if (lParam1 = nil) then
result:=(-1)
else if (lParam2 = nil) then
result:=1
else
begin
// Clear strings
SetLength(szItem1, 0);
SetLength(szItem2, 0);
// Resource protection
try
// Get values to sort on
if (Column = 0) then
begin
// Get values from caption
szItem1:=lParam1.Caption;
szItem2:=lParam2.Caption
end
else
begin
// Get subitem index
dwSubItem:=Pred(Column);
// Get subitems
if (dwSubItem >= 0) then
begin
if (dwSubItem < lParam1.SubItems.Count) then szItem1:=lParam1.SubItems[
if (dwSubItem < lParam2.SubItems.Count) then szItem2:=lParam2.SubItems[
end;
end;
// Perform comparison
case SortType of
stString : result:=CompareStr(szItem1
stInteger : result:=StrToInt(szItem1) - StrToInt(szItem2);
stFloat :
begin
dblResult:=StrToFloat(szIt
if (dblResult > 0) then
result:=1
else if (dblResult < 0) then
result:=(-1)
else
result:=0;
end;
stDate :
begin
dblResult:=StrToDate(szIte
if (dblResult > 0) then
result:=1
else if (dblResult < 0) then
result:=(-1)
else
result:=0;
end;
stTime :
begin
dblResult:=StrToTime(szIte
if (dblResult > 0) then
result:=1
else if (dblResult < 0) then
result:=(-1)
else
result:=0;
end;
stDateTime :
begin
dblResult:=StrToDateTime(s
if (dblResult > 0) then
result:=1
else if (dblResult < 0) then
result:=(-1)
else
result:=0;
end;
else
// Sort by string
result:=CompareStr(szItem1
end;
except
// Conversion error, compare by string values
result:=CompareStr(szItem1
end;
end;
// Reverse the sort if not ascending
if not(Ascending) then result:=result * (-1);
end;
//// Specialized sorting functions //////////////////////////
function LVStringSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
result:=LVBaseSort(lParam1
end;
function LVIntegerSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
result:=LVBaseSort(lParam1
end;
function LVFloatSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
result:=LVBaseSort(lParam1
end;
function LVDateSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
result:=LVBaseSort(lParam1
end;
function LVTimeSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
result:=LVBaseSort(lParam1
end;
function LVDateTimeSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
result:=LVBaseSort(lParam1
end;
//// TListViewSort //////////////////////////
procedure TListViewSort.OnColumnClic
var stColumn: TSortType;
lParam: Integer;
begin
// Check list view
if Assigned(FListView) and Assigned(Column) then
begin
// Get sort type for the column
stColumn:=GetSortType(Colu
// Resource protection
try
// Update the sorting direction
if (Column.Index <> FLastSort) then
// Ascending sort
FAscending:=True
else
// Reverse the current sorting
FAscending:=not(FAscending
// Encode into column and sort direction into an lParam
lParam:=MakeColumnSort(Col
// Perform the sort
case stColumn of
stString : FListView.CustomSort(@LVSt
stInteger : FListView.CustomSort(@LVIn
stFloat : FListView.CustomSort(@LVFl
stDate : FListView.CustomSort(@LVDa
stTime : FListView.CustomSort(@LVTi
stDateTime : FListView.CustomSort(@LVDa
else
// Use string sorting
FListView.CustomSort(@LVSt
end;
finally
// Save last sorted column index
FLastSort:=Column.Index;
end;
end;
end;
function TListViewSort.GetSortType(
begin
// Check column against the list count
if (Column >= FSortTypes.Count) then
// Default sorting will be by string
result:=stString
else
// Return the stored sort type
result:=TSortType(FSortTyp
end;
procedure TListViewSort.ApplyLastSor
var stColumn: TSortType;
lParam: Integer;
begin
// Check list view and last sort column
if Assigned(FListView) and (FLastSort >= 0) and (FLastSort < FListView.Columns.Count) then
begin
// Apply the last used sort against the list view; this is useful if the
// list view data is reloaded (or newly loaded) and you wish to apply the previous
// sort against the new data.
stColumn:=GetSortType(FLas
// Encode into column and sort direction into an lParam
lParam:=MakeColumnSort(FLa
// Perform the sort
case stColumn of
stString : FListView.CustomSort(@LVSt
stInteger : FListView.CustomSort(@LVIn
stFloat : FListView.CustomSort(@LVFl
stDate : FListView.CustomSort(@LVDa
stTime : FListView.CustomSort(@LVTi
stDateTime : FListView.CustomSort(@LVDa
else
// Use string sorting
FListView.CustomSort(@LVSt
end;
end;
end;
constructor TListViewSort.CreateOwned(
var dwIndex: Integer;
begin
// Perform inherited (list view becomes our owner)
inherited Create(ListView);
// Set defaults
FSortTypes:=TList.Create;
FLastSort:=(-1);
FAscending:=True;
// Add sorting types to the list
for dwIndex:=0 to High(ColumnSorting) do FSortTypes.Add(Pointer(Col
// Save instance of list view
FListView:=ListView;
// Bind the OnColumnClick
if Assigned(FListView) then FListView.OnColumnClick:=O
end;
destructor TListViewSort.Destroy;
begin
// Resource protection
try
// Unbind the list views OnColumnClick method
if Assigned(FListView) then FListView.OnColumnClick:=n
// Free the sorting list
FSortTypes.Free;
finally
// Perform inherited
inherited Destroy;
end;
end;
//// Utility functions //////////////////////////
function MakeColumnSort(Column: Integer; Ascending: Boolean): Integer;
begin
// Encode into integer
if Ascending then
result:=MakeLong(Word(Colu
else
result:=MakeLong(Word(Colu
end;
function GetColumn(Value: Integer): Integer;
begin
// Decode from value
result:=LoWord(Value);
end;
function GetAscending(Value: Integer): Boolean;
begin
// Decode from value
result:=(HiWord(Value) <> 0);
end;
end.
ASKER
I like the one by rllibby.
Generic and effortless.
I got it to work quick.
Though one question.
How can I have case to be ignored in String?
For example:
When clicking an column with string items the sort
places "Troskie" before "andre".
This should not be.
Generic and effortless.
I got it to work quick.
Though one question.
How can I have case to be ignored in String?
For example:
When clicking an column with string items the sort
places "Troskie" before "andre".
This should not be.
ASKER
Regarding my last post.
Ignore it please cause I got it to work with AnsiCompareStr();
Thanks for everyone!!!
Ignore it please cause I got it to work with AnsiCompareStr();
Thanks for everyone!!!
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
One way of doing it:
type
TLVSortType = (stStr, stInt, stFloat, stDate);
function LVBaseSort(lParam1, lParam2: TListItem; Column: Integer; SortType: TLVSortType): Integer;
var szItem1: String;
szItem2: String;
dblResult: Double;
begin
// Get values to sort on
if (Column = 0) then
begin
// Get values from caption
szItem1:=lParam1.Caption;
szItem2:=lParam2.Caption
end
else
begin
// Get value from subitems
szItem1:=lParam1.SubItems[
szItem2:=lParam2.SubItems[
end;
// Sort the items
case SortType of
stStr : result:=CompareStr(szItem1
stInt : result:=StrToInt(szItem1) - StrToInt(szItem2);
stFloat :
begin
dblResult:=StrToFloat(szIt
if (dblResult > 0) then
result:=1
else if (dblResult < 0) then
result:=(-1)
else
result:=0;
end;
stDate :
begin
dblResult:=StrToDate(szIte
if (dblResult > 0) then
result:=1
else if (dblResult < 0) then
result:=(-1)
else
result:=0;
end;
else
// Sort by string
result:=CompareStr(szItem1
end;
end;
function LVStrSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
result:=LVBaseSort(lParam1
end;
function LVIntSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
result:=LVBaseSort(lParam1
end;
function LVFloatSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
result:=LVBaseSort(lParam1
end;
function LVDateSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
result:=LVBaseSort(lParam1
end;
// Then decide how you want to sort each column.
procedure TForm1.ListView1ColumnClic
begin
// Apply whatever sorting is desired based on the column index; eg date, str, etc...
case Column.Index of
0 : ListView1.CustomSort(@LVDa
1 : ListView1.CustomSort(@LVSt
2 : ListView1.CustomSort(@LVIn
end;
end;
----
Regards,
Russell