Solved

TListView Column Click Order By

Posted on 2006-06-12
14
552 Views
Last Modified: 2010-04-05
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!
0
Comment
Question by:Marius0188
  • 5
  • 4
  • 4
  • +1
14 Comments
 
LVL 26

Expert Comment

by:Russell Libby
ID: 16889569

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[Pred(Column)];
     szItem2:=lParam2.SubItems[Pred(Column)];
  end;

  // Sort the items
  case SortType of
     stStr    :  result:=CompareStr(szItem1, szItem2);
     stInt    :  result:=StrToInt(szItem1) - StrToInt(szItem2);
     stFloat  :
     begin
        dblResult:=StrToFloat(szItem1) - StrToFloat(szItem2);
        if (dblResult > 0) then
           result:=1
        else if (dblResult < 0) then
           result:=(-1)
        else
           result:=0;
     end;
     stDate   :
     begin
        dblResult:=StrToDate(szItem1) - StrToDate(szItem2);
        if (dblResult > 0) then
           result:=1
        else if (dblResult < 0) then
           result:=(-1)
        else
           result:=0;
     end;
  else
     // Sort by string
     result:=CompareStr(szItem1, szItem2);
  end;

end;

function LVStrSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
  result:=LVBaseSort(lParam1, lParam2, lParamSort, stStr);
end;

function LVIntSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
  result:=LVBaseSort(lParam1, lParam2, lParamSort, stInt);
end;

function LVFloatSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
  result:=LVBaseSort(lParam1, lParam2, lParamSort, stFloat);
end;

function LVDateSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
  result:=LVBaseSort(lParam1, lParam2, lParamSort, stDate);
end;

// Then decide how you want to sort each column.
procedure TForm1.ListView1ColumnClick(Sender: TObject; Column: TListColumn);
begin

  // Apply whatever sorting is desired based on the column index; eg date, str, etc...
  case Column.Index of
     0  :  ListView1.CustomSort(@LVDateSort, Column.Index);
     1  :  ListView1.CustomSort(@LVStrSort, Column.Index);
     2  :  ListView1.CustomSort(@LVIntSort, Column.Index);
  end;

end;

----

Regards,
Russell


0
 

Author Comment

by:Marius0188
ID: 16889882
Thanks for the help.

Why are there 2 TListItem params in the function:
>>function LVBaseSort(lParam1, lParam2: TListItem ...........


0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 16889939

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
0
 
LVL 10

Expert Comment

by:atul_parmar
ID: 16892240
Hi,

See the delphi example for OnCompare event of listview.
0
 

Author Comment

by:Marius0188
ID: 16892423
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!
0
 
LVL 10

Expert Comment

by:atul_parmar
ID: 16892439
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.ListView1ColumnClick(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(Sender: TObject; Item1, Item2: TListItem; Data: Integer; var Compare: Integer);
var
  ix: Integer;
begin
  if ColumnToSort = 0 then
    Compare := CompareText(Item1.Caption,Item2.Caption)
  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.

0
 

Author Comment

by:Marius0188
ID: 16893988
Will this OnCompare example allow to order by any value type?
For example: TDate?
0
Why You Should Analyze Threat Actor TTPs

After years of analyzing threat actor behavior, it’s become clear that at any given time there are specific tactics, techniques, and procedures (TTPs) that are particularly prevalent. By analyzing and understanding these TTPs, you can dramatically enhance your security program.

 
LVL 10

Expert Comment

by:atul_parmar
ID: 16894081
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;
0
 
LVL 10

Expert Comment

by:atul_parmar
ID: 16894088
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;
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 16894169
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(lParam1), 3);
   vData2 := LVItemValue(TListItem(lParam2), 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;
0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 16896702

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(ListView1, [stString, stInteger, stDate]);

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[dwSubItem];
              if (dwSubItem < lParam2.SubItems.Count) then szItem2:=lParam2.SubItems[dwSubItem];
           end;
        end;
        // Perform comparison
        case SortType of
           stString    :  result:=CompareStr(szItem1, szItem2);
           stInteger   :  result:=StrToInt(szItem1) - StrToInt(szItem2);
           stFloat     :
           begin
              dblResult:=StrToFloat(szItem1) - StrToFloat(szItem2);
              if (dblResult > 0) then
                 result:=1
              else if (dblResult < 0) then
                 result:=(-1)
              else
                 result:=0;
           end;
           stDate      :
           begin
              dblResult:=StrToDate(szItem1) - StrToDate(szItem2);
              if (dblResult > 0) then
                 result:=1
              else if (dblResult < 0) then
                 result:=(-1)
              else
                 result:=0;
           end;
           stTime      :
           begin
              dblResult:=StrToTime(szItem1) - StrToTime(szItem2);
              if (dblResult > 0) then
                 result:=1
              else if (dblResult < 0) then
                 result:=(-1)
              else
                 result:=0;
           end;
           stDateTime  :
           begin
              dblResult:=StrToDateTime(szItem1) - StrToDateTime(szItem2);
              if (dblResult > 0) then
                 result:=1
              else if (dblResult < 0) then
                 result:=(-1)
              else
                 result:=0;
           end;
        else
           // Sort by string
           result:=CompareStr(szItem1, szItem2);
        end;
     except
        // Conversion error, compare by string values
        result:=CompareStr(szItem1, szItem2);
     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, lParam2, GetColumn(lParamSort), GetAscending(lParamSort), stString);
end;

function LVIntegerSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
  result:=LVBaseSort(lParam1, lParam2, GetColumn(lParamSort), GetAscending(lParamSort), stInteger);
end;

function LVFloatSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
  result:=LVBaseSort(lParam1, lParam2, GetColumn(lParamSort), GetAscending(lParamSort), stFloat);
end;

function LVDateSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
  result:=LVBaseSort(lParam1, lParam2, GetColumn(lParamSort), GetAscending(lParamSort), stDate);
end;

function LVTimeSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
  result:=LVBaseSort(lParam1, lParam2, GetColumn(lParamSort), GetAscending(lParamSort), stTime);
end;

function LVDateTimeSort(lParam1, lParam2: TListItem; lParamSort: Integer): Integer stdcall;
begin
  result:=LVBaseSort(lParam1, lParam2, GetColumn(lParamSort), GetAscending(lParamSort), stDateTime);
end;

//// TListViewSort /////////////////////////////////////////////////////////////
procedure TListViewSort.OnColumnClick(Sender: TObject; Column: TListColumn);
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(Column.Index);
     // 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(Column.Index, FAscending);
        // Perform the sort
        case stColumn of
           stString    :  FListView.CustomSort(@LVStringSort, lParam);
           stInteger   :  FListView.CustomSort(@LVIntegerSort, lParam);
           stFloat     :  FListView.CustomSort(@LVFloatSort, lParam);
           stDate      :  FListView.CustomSort(@LVDateSort, lParam);
           stTime      :  FListView.CustomSort(@LVTimeSort, lParam);
           stDateTime  :  FListView.CustomSort(@LVDateTimeSort, lParam);
        else
           // Use string sorting
           FListView.CustomSort(@LVStringSort, lParam);
        end;
     finally
        // Save last sorted column index
        FLastSort:=Column.Index;
     end;
  end;

end;

function TListViewSort.GetSortType(Column: Integer): TSortType;
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(FSortTypes[Column]);

end;

procedure TListViewSort.ApplyLastSort;
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(FLastSort);
     // Encode into column and sort direction into an lParam
     lParam:=MakeColumnSort(FLastSort, FAscending);
     // Perform the sort
     case stColumn of
        stString    :  FListView.CustomSort(@LVStringSort, lParam);
        stInteger   :  FListView.CustomSort(@LVIntegerSort, lParam);
        stFloat     :  FListView.CustomSort(@LVFloatSort, lParam);
        stDate      :  FListView.CustomSort(@LVDateSort, lParam);
        stTime      :  FListView.CustomSort(@LVTimeSort, lParam);
        stDateTime  :  FListView.CustomSort(@LVDateTimeSort, lParam);
     else
        // Use string sorting
        FListView.CustomSort(@LVStringSort, lParam);
     end;
  end;

end;

constructor TListViewSort.CreateOwned(ListView: TListView; ColumnSorting: Array of TSortType);
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(ColumnSorting[dwIndex]));

  // Save instance of list view
  FListView:=ListView;

  // Bind the OnColumnClick
  if Assigned(FListView) then FListView.OnColumnClick:=OnColumnClick;

end;

destructor TListViewSort.Destroy;
begin

  // Resource protection
  try
     // Unbind the list views OnColumnClick method
     if Assigned(FListView) then FListView.OnColumnClick:=nil;
     // 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(Column), 1)
  else
     result:=MakeLong(Word(Column), 0);

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.



0
 

Author Comment

by:Marius0188
ID: 16901916
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.

0
 

Author Comment

by:Marius0188
ID: 16901960
Regarding my last post.
Ignore it please cause I got it to work with AnsiCompareStr();


Thanks for everyone!!!
0
 
LVL 26

Accepted Solution

by:
Russell Libby earned 125 total points
ID: 16927091

Just out of curiousity, are you planning on closing the question, or did you need further assistance?

Regards,
Russell
0

Featured Post

Free Trending Threat Insights Every Day

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

Join & Write a Comment

In this tutorial I will show you how to use the Windows Speech API in Delphi. I will only cover basic functions such as text to speech and controlling the speed of the speech. SAPI Installation First you need to install the SAPI type library, th…
In my programming career I have only very rarely run into situations where operator overloading would be of any use in my work.  Normally those situations involved math with either overly large numbers (hundreds of thousands of digits or accuracy re…
Excel styles will make formatting consistent and let you apply and change formatting faster. In this tutorial, you'll learn how to use Excel's built-in styles, how to modify styles, and how to create your own. You'll also learn how to use your custo…
This demo shows you how to set up the containerized NetScaler CPX with NetScaler Management and Analytics System in a non-routable Mesos/Marathon environment for use with Micro-Services applications.

708 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now