Column sorting in a listview

greenspun.com : LUSENET : OCI Best Practices : One Thread

How to implement sorting on a column when a user clicks on the column title:
The only thing you have to implement the sorting is to implement the two events you can see further down on this page. I have included the help file for OnColumnClick and OnCompare. Do NOT change the value of the list view's SortType property. SortType should be stNone.

As you can see from OnColumnClick help, the event is fired when a user for example click on the column header (OBS when viewstyle is vsReport). The VCL architecture is then fireing the onCompare event for us. The onCompare is just returning whether an item is 'greater','less' or'equal' to another item. It is up to you to implement the 'greater','less','equal' logic.

I have also included an example from the MiscellaneousAncestorForm and how to typecaste your item in the listview to your specific needs.

TCustomListView.OnColumnClick
Occurs when the user clicks on a column header in a list view.

type TLVColumnClickEvent = procedure(Sender: TObject; Column: TListColumn) of object; property OnColumnClick: TLVColumnClickEvent;

Description

Write an OnColumnClick event handler to respond to mouse clicks made in the column header when ViewStyle is vsReport. The Column parameter is the TListColumn object from the Columns property that describes the column that was clicked. OnColumnClick only occurs when ViewStyle is vsReport, ShowColumnHeaders is True, and ColumnClick is True.

TCustomListView.OnCompare
Occurs to when two items need to be compared during a sort of the list.

type TLVCompareEvent = procedure(Sender: TObject; Item1, Item2: TListItem; Data: Integer; var Compare: Integer) of object; property OnCompare: TLVCompareEvent;

Description

Write an OnCompare event handler to implement a sort order for the list. An OnCompare event handler is called when the SortType property is stData or stBoth, when the AlphaSort method is called, or when the CustomSort method is called without a SortProc parameter.

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. The Data parameter is 0 when the event handler is called to maintain the sort order of a list view with a SortType of stData or stBoth. Similarly, when OnCompare occurs in response to the AlphaSort method, the Data parameter is 0. When OnCompare occurs in response to the CustomSort method, the Data parameter is the value of the LParam parameter of CustomSort.

EXAMPLE:


//-------------------------------------------------------------------------------

procedure TMiscellaneousAncestorForm.ListViewNameHistoryCollectionColumnClick(
  Sender: TObject; Column: TListColumn);
begin
  inherited;
  // The user clicked on the listview column. Have it sort passing the column
  // index of the selected column.
	ListViewNameHistoryCollection.CustomSort(NIL, Column.Index);
end;

//-------------------------------------------------------------------------------

procedure TMiscellaneousAncestorForm.ListViewNameHistoryCollectionCompare(
  Sender: TObject; Item1, Item2: TListItem; Data: Integer;
  var Compare: Integer);
begin
  inherited;
  // Sort the listview based on the value passed in Data. Data corresponds
  // to the column clicked by the user.
	case Data of
  	0: Compare := CompareStr(Item1.Caption, Item2.Caption);
    else Compare := CompareStr(Item1.SubItems.Strings[Data-1], Item2.SubItems.Strings[Data-1]);
    end; // case
end;

//-------------------------------------------------------------------------------

What you can do if you do not want to compare the caption, you can typecast the item.data to your 

specific item and access the element of you object instead.
For example:
  // (A < B) = minus value, (A = B) = 0, (A > B) = positive value
  Compare := TCompanyStatusItem(Item1.data).DateEff - TCompanyStatusItem(Item2.data).DateEff then


-- Anonymous, August 18, 1998

Answers

Here's a more complicated example. Here there are six columns. Some columns contain dates, and some dates allow nulls. (And date columns sort earliest dates to the bottom.)

var collectionItem1, collectionItem2: TProducerCollectionListingHistoryItem;
var d1, d2: TDateTime;
begin
  inherited;
  collectionItem1 := TProducerCollectionListingHistoryItem(Item1.Data);
  collectionItem2 := TProducerCollectionListingHistoryItem(Item2.Data);
  case Data of
    0: Compare := (collectionItem1.IdCmp.Value - collectionItem2.IdCmp.Value);
    1: Compare := CompareText(Item1.SubItems.Strings[Data-1], Item2.SubItems.Strings[Data-1]);
    2: Compare := -CompareDate(collectionItem1.DateEffective.Value, collectionItem2.DateEffective.Value);
    3: begin
      // This column can contain null dates. Treat null dates as later than any other date.
      if (collectionItem1.DateEnded.IsNull)
        then d1 := EncodeDate(9999,12,31)
        else d1 := collectionItem1.DateEnded.Value;
      if (collectionItem2.DateEnded.IsNull)
        then d2 := EncodeDate(9999,12,31)
        else d2 := collectionItem2.DateEnded.Value;
      Compare := -CompareDate(d1, d2);
      end; // 3: begin
    4: Compare := CompareText(collectionItem1.CodeTerminated.Value, collectionItem2.CodeTerminated.Value);
    5: Compare := CompareText(Item1.SubItems.Strings[Data-1], Item2.SubItems.Strings[Data-1]);
    6: begin
      // This column can contain null dates. Treat null dates as later than any other date.
      if (collectionItem1.DateLastBilled.IsNull)
        then d1 := EncodeDate(9999,12,31)
        else d1 := collectionItem1.DateLastBilled.Value;
      if (collectionItem2.DateLastBilled.IsNull)
        then d2 :=EncodeDate(9999,12,31)
        else d2 := collectionItem2.DateLastBilled.Value;
      Compare := -CompareDate(d1, d2);
      end; // 6: begin
    end; // case
end;


-- Anonymous, September 25, 1998

Moderation questions? read the FAQ