18

I have a QTableWidget and the first column contains numbers from 1 to 1000. Now I need to sort the table based on this first column.

I'm using the function sortItems(int column, Qt::AscendingOrder), but it is displayed as:

1, 10, 100, 1000, 101, 102, ...

Yet I need this result:

1, 2, 3 ,4...., 1000.

I'm using a CSV file for populating the table.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
Shyam
  • 871
  • 6
  • 15
  • 30

5 Answers5

29

The values are sorted as strings because you stored them as such in the model.

The QVariant can remember the original type of the data if you let it do the conversion itself, and the comparison operator from that type will be used when sorting:

// Get the value from the CSV file as a numeric type
int valueFromCsvFile = ...;

// don't do this
QTableWidgetItem *item = new QTableWidgetItem(QString::number(valueFromCsvFile));

// but do this instead
QTableWidgetItem *item = new QTableWidgetItem;
item.setData(Qt::EditRole, valueFromCsvFile);    

The cell editor will also adapt to the type of the QVariant:

  • QSpinBox for int,
  • QDoubleSpinBox for double and float,
  • QDateTimeEdit for QDateTime
  • ...
alexisdm
  • 29,448
  • 6
  • 64
  • 99
  • I used 'item.setData(Qt::EditRole, valueFromCsvFile)' and now its working fine. Thanks!! – Shyam Oct 24 '11 at 09:21
  • 1
    @Shyam, why not set this as the accepted answer? I had trouble with this because I was using a long int, and weirdly the QVariant will work with long long int or int, but not long int. Casting my long int to qlonglong worked for me. – M_M Oct 27 '16 at 12:57
  • 2
    This should be the accepted answer, it is far simpler than the others! – Spencer Jun 01 '18 at 18:22
  • Not working with the table default sort, i.e. when trying to sort by clicking the header arrows. – Elad Weiss Aug 16 '23 at 11:45
16

The easiest way is probably to subclass QTableWidgetItem and then implement the < operator to be smart about the fact that you're sorting numbers and not strings.

class MyTableWidgetItem : public QTableWidgetItem {
    public:
        bool operator <(const QTableWidgetItem &other) const
        {
            return text().toInt() < other.text().toInt();
        }
};

Then when you're populating your table you can pass it instances of your custom items that know how to sort themselves properly instead of the generic ones.

Chris
  • 17,119
  • 5
  • 57
  • 60
  • Thanks, this works. In newer versions of Qt, the signature has changed slighly to `bool operator< ( const QTableWidgetItem & other ) const` – iliis Jun 27 '14 at 14:21
  • Seriously, if `setData(Qt::DisplayRole, num)` doesn't work for you, you should try this. – Wesley Mar 07 '18 at 06:36
2

One way that worked in my situation was

1) before filling the table, turn off sorting:

table.setSortingEnabled(False)

2) pad the number strings with blanks and make all strings in the column have the same length:

('    '+numStr)[-4:]

3) after filling the table, turn on sorting:

table.setSortingEnabled(True)

This fixed the row sorting problem and the numerical order.

SoloPilot
  • 1,484
  • 20
  • 17
2

I don't know if the accepted answer used to work, but with Qt5.1, it doesn't. In order to work, the operator< definition has to match the virtual definition from qtablewidget.h.

Another interesting addition is to sort items that have numbers, but start with a currency sign ($ or for instance) or end with %.

Here is the updated code:

class TableNumberItem : public QTableWidgetItem
{
public:
    TableNumberItem(const QString txt = QString("0"))
        :QTableWidgetItem(txt)
    {
    }

    bool operator < (const QTableWidgetItem &other) const
    {
        QString str1 = text();
        QString str2 = other.text();

        if (str1[0] == '$' || str1[0] == '€') {
            str1.remove(0, 1);
            str2.remove(0, 1); // we assume both items have the same format
        }

        if (str1[str1.length() - 1] == '%') {
            str1.chop(1);
            str2.chop(1); // this works for "N%" and for "N %" formatted strings
        }

        double f1 = str1.toDouble();
        double f2 = str2.toDouble();

        return str1.toDouble() < str2.toDouble();
    }
};

Then, you add the items that contain numbers using something like this:

myTableWidget->setItem(row, col, new TableNumberItem("$0"));

Note that this class must be used with numbers only, it will not sort strings correctly (as is also the case with the accepted answer).

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
Sir Athos
  • 9,403
  • 2
  • 22
  • 23
0

I had same problem and the The answer of @Chris worked for me! but a little modification is need. I can't comment. so I write here.

   class MyTableWidgetItem : public QTableWidgetItem {
    public:
        bool operator <(const QTableWidgetItem &other) const
        {
            if (text()=="")
                return text().toDouble() > other.text().toDouble();
            else
                return text().toDouble() < other.text().toDouble();
        }
    };
M.Hu
  • 121
  • 1
  • 14
  • That doesn't make sense. If `text()` is empty, then `text().toDouble()` returns -1.0 when you may be expecting 0.0 instead. You should check with that specific number instead of changing the operator. Also `other.text()` could be empty too if you expect `text()` to be empty. – Alexis Wilke Jun 16 '18 at 04:56
  • I think empty cell is empty and no need to sort. it works for me for all positive number include 0. – M.Hu Jun 23 '18 at 16:54