PDA

View Full Version : QT4: Sorting in QTreeWidget (subclass)



Michiel
27th March 2006, 23:37
I've created a QTreeWidget subclass and a QTreeWidgetItem subclass. They work, except for the sorting. When I click on the headers, nothing changes except for the sorting indicator.

setSorting is enabled, so that isn't it. Now, it doesn't even sort by string-value. But also, I'm missing a reimplementable function in QTreeWidgetItem. I suspected something like:

virtual int QTreeWidgetItem::compare(int column); // -1, 0 or 1

or something. Because two of the columns actually represent QDateTime variables, and I don't want them to be sorted as strings.

So, how do I implement sorting?

Thanks!

jpn
28th March 2006, 06:56
You can change the sorting algorithm by overriding
virtual bool operator< (http://doc.trolltech.com/4.1/qtreewidgetitem.html#operator-lt) ( const QTreeWidgetItem & other ) const

Michiel
28th March 2006, 07:42
Ooooh. Right, I suppose I can get the sorting column and sorting order with other member functions. I was thrown off because QT3 did it differently.

Thanks!

Michiel
28th March 2006, 08:00
It doesn't seem to work.


bool MyTreeWidgetItem::operator<(const QTreeWidgetItem & other) const {
switch (treeWidget()->sortColumn()) {
case 0:
return _category < ((const MyTreeWidgetItem &)other)._category;
case 1:
return _start < ((const MyTreeWidgetItem &)other)._start;
case 2:
return _end < ((const MyTreeWidgetItem &)other)._end;
case 3:
return _note < ((const MyTreeWidgetItem &)other)._note;
}
return false;
}

_start and _end are QDateTime objects. The other two are strings. All four are member vars of MyTreeWidgetItem. I only found the sortColumn() function, so I supposed that the sort order would automatically adjust to the sorting indicator.

But it doesn't sort anything. All rows remain in place.

jpn
28th March 2006, 09:30
Did you declare the operator as public? Assure that it gets called, add a breakpoint or some debug output..
Yep, treewidget()->sortColumn() is the correct method to find out which column is used to sort.

Michiel
28th March 2006, 13:22
Yes. The function is definately being used when I click the headers. Here is the whole code of both classes. Just in case I'm doing something wrong elsewhere.

treewidget.h

#ifndef TREEWIDGET_H
#define TREEWIDGET_H

#include <QTreeWidget>
#include <QPoint>

class TreeWidgetItem;

class TreeWidget : public QTreeWidget {
Q_OBJECT
public:
TreeWidget(QWidget* parent = 0);
TreeWidgetItem* currentItem() const;
TreeWidgetItem* itemAt(const QPoint & p) const;
TreeWidgetItem* itemAt(int x, int y) const;
TreeWidgetItem* takeTopLevelItem(int index);
TreeWidgetItem* topLevelItem(int index) const;
};

#endif // TREEWIDGET_H


treewidget.cpp

#include "treewidget.h"

#include "treewidgetitem.h"

TreeWidget::TreeWidget(QWidget * parent) : QTreeWidget(parent) {
setColumnCount(4);
headerItem()->setText(0, tr("Category") );
headerItem()->setText(1, tr("Start") );
headerItem()->setText(2, tr("End") );
headerItem()->setText(3, tr("Note") );
setAlternatingRowColors(true);
}

TreeWidgetItem* TreeWidget::currentItem() const {
return (TreeWidgetItem*)QTreeWidget::currentItem();
}

TreeWidgetItem* TreeWidget::itemAt(const QPoint & p) const {
return (TreeWidgetItem*)QTreeWidget::itemAt(p);
}

TreeWidgetItem* TreeWidget::itemAt(int x, int y) const {
return (TreeWidgetItem*)QTreeWidget::itemAt(x, y);
}

TreeWidgetItem* TreeWidget::takeTopLevelItem(int index) {
return (TreeWidgetItem*)QTreeWidget::takeTopLevelItem(ind ex);
}

TreeWidgetItem* TreeWidget::topLevelItem(int index) const {
return (TreeWidgetItem*)QTreeWidget::topLevelItem(index);
}


treewidgetitem.h

#ifndef TREEWIDGETITEM_H
#define TREEWIDGETITEM_H

#include <QTreeWidgetItem>
#include <QDateTime>

class TreeWidget;

class TreeWidgetItem : public QObject, public QTreeWidgetItem {
Q_OBJECT
public:
TreeWidgetItem(TreeWidget* parent, int type = UserType);

TreeWidgetItem* clone() const;
QVariant data(int column, int role) const;
void read(QDataStream & in);
void setData(int column, int role, const QVariant & value);
void write(QDataStream & out) const;

bool operator<(const QTreeWidgetItem & other) const;

private:
QString _category;
QDateTime _start;
QDateTime _end;
QString _note;
};

#endif // TREEWIDGETITEM_H


treewidgetitem.cpp

#include "treewidgetitem.h"
#include "treewidget.h"

TreeWidgetItem::TreeWidgetItem(TreeWidget * parent, int type) : QTreeWidgetItem((QTreeWidget*)parent, type) {
} // intentionally empty

TreeWidgetItem* TreeWidgetItem::clone() const {
return NULL; // FIXME: Create this function
}

QVariant TreeWidgetItem::data(int column, int role) const {
if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::ToolTipRole) {
if (column == 0)
return QVariant(_category);

if (column == 1)
return QVariant(_start.toString());

if (column == 2)
return (_end.isValid() ? QVariant(_end.toString()) : QVariant(QString("<running>")));

if (column == 3)
return QVariant(_note);
}

if (role == Qt::UserRole) {
if (column == 0)
return QVariant(_category);

if (column == 1)
return QVariant(_start);

if (column == 2)
return QVariant(_end);

if (column == 3)
return QVariant(_note);
}

return QVariant();
}

void TreeWidgetItem::read(QDataStream & /*in*/) {
// FIXME: Create this function
}

void TreeWidgetItem::setData(int column, int role, const QVariant & value) {
if (role == Qt::EditRole) {
if (column == 0)
_category = value.toString();

if (column == 1)
_start = (QDateTime::fromString(value.toString()).isValid() ? QDateTime::fromString(value.toString()) : _start);

if (column == 2)
_end = (QDateTime::fromString(value.toString()).isValid() ? QDateTime::fromString(value.toString()) : _end);

if (column == 3)
_note = value.toString();
}

if (role == Qt::UserRole) {
if (column == 0)
_category = value.toString();

if (column == 1)
_start = value.toDateTime();

if (column == 2)
_end = value.toDateTime();

if (column == 3)
_note = value.toString();
}
}

void TreeWidgetItem::write(QDataStream & /*out*/) const {
// FIXME: Create this function
}

#include <iostream>
using namespace std;

bool TreeWidgetItem::operator<(const QTreeWidgetItem & other) const {
cerr << "TEST" << endl;
switch (treeWidget()->sortColumn()) {
case 0:
return _category < ((const TreeWidgetItem &)other)._category;
case 1:
return _start < ((const TreeWidgetItem &)other)._start;
case 2:
return _end < ((const TreeWidgetItem &)other)._end;
case 3:
return _note < ((const TreeWidgetItem &)other)._note;
}
return false;
}


I inherited QObject too, because I want to add signals and slots later.

So, what am I doing wrong?

Thanks!

incubator
28th March 2006, 13:25
you dont need to inherit QObject becausse the Q_OBJECT macro does that

Michiel
28th March 2006, 13:26
Oh? Is that new in QT4, because as I recall, QT3 needed both.

wysota
28th March 2006, 13:38
you dont need to inherit QObject becausse the Q_OBJECT macro does that

Q_OBJECT just puts some methods in class definition and marks the class for MOC to process. It doesn't cause QObject to be inherited, you have to explicitely inherit it.

jpn
28th March 2006, 14:24
I think it might be your TreeWidget::data() and TreeWidget::setData() implementations which mess it up..
You could try re-formatting the bodies of those functions to something like this:


QVariant TreeWidgetItem::data(int column, int role) const {
if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::ToolTipRole)
...
else if (role == Qt::UserRole)
...
else
return QTreeWidgetItem::data(column, role);
}

void TreeWidgetItem::setData(int column, int role, const QVariant & value) {
if (role == Qt::EditRole)
..
else if (role == Qt::UserRole)
..
else
QTreeWidgetItem::setData(column, role, value);
}

It's important that all needed roles become handled correctly. So you may call base class implementation in case you're not interested of some specific role.

A side note:
In my opinion, overriding data() and setData() for this purpose is a bit futile. ;)

You could use setText() for all TreeWidgetItem columns, even for the datetimes.
Then in the compare operator you simply handle those 2 datetime columns explicitly:


bool TreeWidgetItem::operator<(const QTreeWidgetItem & other) const
{
int column = treeWidget()->sortColumn();
if (column == 1 || column == 2)
{
QDateTime thisDate = QDateTime::fromString(text(column));
QDateTime otherDate = QDateTime::fromString(other.text(column));
return thisDate < otherDate;
}
return QTreeWidgetItem::operator <(other);
}

By this way you could just forget complicated data() and setData()... ;)

Michiel
28th March 2006, 14:47
Ah. Call the base class implementation. That makes sense. :) I'll try that and post the result later today. Thanks.

Michiel
28th March 2006, 17:42
Yep, that was it, of course. Thanks for your help!

Michiel
28th March 2006, 18:50
How do I tell a row in the treewidget to redraw itself after it has been changed by a signal/slot connection?

I can't find anything. But I must be looking in the wrong place.

Thanks.

jpn
28th March 2006, 19:11
In what way do you change the content of that row? It should update itself.

Of course you can force an update of a certain part of the widget/view, but I think there might be something wrong with your approach..

The forced update would look for example something like:

update(visualRect(item));
or

setDirtyRegion(visualRect(item));

Michiel
28th March 2006, 19:23
It updates itself when I call the base class setData function inside the derived setData function. If I don't, it won't. In some cases (some roles), I want to do something else with incoming data, and not call the base class function.

I assume that the base class function calls one of those update functions (which is how it happens 'automatically'). And maybe I have to do that too.

The update() function doesn't work.

Just to clarify, my setData function only changes one of those four private variables. So I'm not surprised the update doesn't happen automatically.

EDIT: I've found a solution. Not sure it's the best one. In the setData function, I call the QTreeWidgetItem::setText(int column, QString text). If there's a better way (I suspect there is a way which calls getData), please let me know.

jpn
28th March 2006, 19:37
Yes, the base class implementation of setData() leads the internal tree model to emit dataChanged() signal which furthermore causes the view to update itself.

jpn
28th March 2006, 19:39
In the setData function, I call the QTreeWidgetItem::setText(int column, QString text).
QTreeWidgetItem::setText(column, text) is exactly the same than
QTreeWidgetItem::setData(column, Qt::DisplayRole, text)

Edit: I would suggest you to simply call the base class implementation, or what's the loss with that? You can pick your member variables and still call the base class implementation..



void TreeWidgetItem::setData(int column, int role, const QVariant & value) {
QTreeWidgetItem::setData(column, role, value);
// pick your variables
if (role == Qt::EditRole)
..
else if (role == Qt::UserRole)
..
}

Michiel
28th March 2006, 19:59
It might do a lot of redundant stuff. Like, set the text itself, then overwrite it again with my code.

Also, wouldn't it update before my data is changed?

jpn
28th March 2006, 20:31
Umm..

Just to clarify, my setData function only changes one of those four private variables.

Well, change the order. Do your stuff first and call the base class implementation afterwards.. :)
You could also do a trick like when your setData() receives a QDateTime variant with user role, you pick that date as a member variable and pass QDateTime.toString() with display role to the base class implementation.

But anyway.. don't take this as offense, but is there really any need for overriding data() and setData() at all? Unless you are gonna do some more customize stuff, you could avoid all this hassle and handle it by just simply overriding the operator used for comparing..

Michiel
28th March 2006, 20:43
How could I intercept a user-edit of a field otherwise? If a user changes a date with the keyboard, I'll need QDateTime::fromString() to convert it to a date. As far as I can see, overwriting setData is the only way. And since I have to use it anyway, why not put everything else relevant in there too? I need a function to change _start and _end from the outside anyway. setData seems to do the trick.

That, and I'm just trying to program the way the qt developers probably intend me to. Since those functions are virtual, I think they should be overwritten by subclasses. I didn't know what other parts of QT relied on them.

jpn
29th March 2006, 07:58
How could I intercept a user-edit of a field otherwise?
You could even place a QDateTimeEdit widget as an item widget in each item you intend to.


I didn't know what other parts of QT relied on them.
The problem with overriding data() and/or setData() is that Qt's "convenience" itemviews rely on their strictly private model. So you really have to know what you're doing..
You can set custom data with custom roles without overriding data() or setData(). Qt will just not handle them. Delegates are there for display and editing facilities for items.

Michiel
29th March 2006, 18:08
I think I should have used QTreeView anyway. I'll probably change to that later. For now, I want a working program, and at the moment, it works. :)

Thanks again for your help.