Trolltech | Documentation | Qt Quarterly | « Seriously Weird QRegExp

Sorting QListViews
by Andy Shaw
The QListView widget provides both list and tree views. The class provides locale-aware Unicode sorting by any column, or no sorting at all. There are often situations where the sorting required must be more sophisticated than straightforward lexical sorting. This article explains how to take precise control of QListView's sorting.

By default, QListView sorts on the first column in ascending order. To switch sorting off, call setSorting(-1). To sort by a particular column, call setSorting() with the column number and, optionally, an ascending/descending order flag. If two items have identical keys in the column used for sorting, Qt 3.0.2 uses the other columns, as necessary, to obtain unique comparisons. Custom sorting can be achieved by reimplementing one of the following QListViewItem functions: key(), compare() or sortChildItems().

Reimplementing sortChildItems() bypasses key() and compare(), and allows you to replace the default algorithm provided by qHeapSort() with your own. In this article we'll cover the more common cases of reimplementing key() or compare().

Email Client Example

We'll use an email client as an example as we look at how to take control of QListView sorting. We will create a QListView with Subject, Sender and Date columns.

    QListView* mail = new QListView( this );
    mail->addColumn( "Subject" );
    mail->addColumn( "Sender" );
    mail->addColumn( "Date" );

We'll also populate the email client with some email data.

    new QListViewItem( mail, "Accounts",
	    "Joe Bloggs <joe@bloggs.com>", "12/25/2001" );
    new QListViewItem( mail, "Re: Accounts",
	    "andy@nospam.com", "12/31/2001" );
    new QListViewItem( mail, "Expenses",
	    "joe@bloggs.com", "01/08/2002" );
    new QListViewItem( mail, "Re: Accounts",
	    "Joe <joe@bloggs.com>", "01/17/2002" );
    new QListViewItem( mail, "Re: Expenses",
	    "Andy <andy@nospam.com>", "02/01/2002" );

Reimplementing QListViewItem::key()

This function is most useful for comparing textual data. The default implementation simply returns the text of the relevant column.

If we were to sort the example list view on the Sender column, it would sort "Joe Bloggs <joe@bloggs.com>", "Joe <joe@bloggs.com>" and "joe@bloggs.com" into different positions even though they represent the same email address. We can solve this problem by reimplementing the key() function so that it only returns the email address, without any name.

    class MyListViewItem : public QListViewItem
    {
    public:
	MyListViewItem( QListView* parent, QString subject,
			QString sender, QString date );
	QString key( int column, bool ascending ) const;
    };

    QString MyListViewItem::key( int column,
				 bool ascending ) const
    {
	if ( column == 1 ) {
	    QString senderText = text( 1 );
	    int firstbracket = senderText.find( "<" );
	    if ( firstbracket == -1 )
		return senderText;
	    else
		return senderText.mid( firstbracket + 1,
				       senderText.length() -
				       firstbracket - 2 );
	} else {
	    return QListViewItem::key( column, ascending );
	}
    }

If the Sender column has a "<", we take the text between the angle brackets as the email address; otherwise we take the entire text as the email address. If the QListView is not sorted by the Sender column we use the default key() function.

A similar refinement can be made to sort "Accounts" and "Re: Accounts" together when the list view is sorted on the Subject column.

By reimplementing key() as we have done here, it is possible to convert any data into a string suitable for comparison, that QListView can use to sort by.

Reimplementing QListViewItem::compare()

If the key() function is reimplemented the resultant strings are compared using a locale-aware string comparison. For data that doesn't need to be locale-aware, and for faster performance, compare() can be reimplemented instead.

In our example, the Date column needs to be compared non-textually, and is an ideal candidate for a compare() reimplementation.

    int MyListViewItem::compare( QListViewItem* item,
				 int column,
				 bool ascending ) const
    {
	if ( column == 2 ) {
	    QDate d = QDate::fromString( text( 2 ), Qt::LocalDate );
	    QDate e = QDate::fromString( item->text( 2 ), Qt::LocalDate );
	    return e.daysTo( d );
	} else {
	    return QListViewItem::compare( item, column, ascending );
	}
    }

This code converts the textual dates into QDate objects and returns their difference in days (which may be negative). If the dates had been stored in ISO format (YYYY-MM-DD), we could have compared them textually (in compare(), not key() because we don't need locale-awareness), which would be faster.

Summary

The built-in QListView sorting is sufficient for most situations. Subclassing to reimplement key() or compare() is simple and provides complete control. The techniques shown here can be applied elsewhere in Qt, for example, QIconViewItem::compare(), QListBox::text() and QTableItem::key(). Now we're sorted!


This document is licensed under the Creative Commons Attribution-Share Alike 2.5 license.

Copyright © 2002 Trolltech. Trademarks Qt Quarterly