PDA

View Full Version : QFileSystemModel with checkboxes...



been_1990
15th January 2010, 00:21
Is there a way to make a QFileSystemModel add a checkbox to each of its rows? I know I can create a QStandardItemModel and add a QStandardItem with a checkbox added to it, but I need the functionality the QFileSystemModel gives me.

Coises
15th January 2010, 08:56
Subclass QFileSystemModel and provide custom implementations of the data, flags and setData functions:

QVariant CustomModel::data(const QModelIndex& index, int role) const {
if (role == Qt::CheckStateRole) {
/* determine whether the box should be checked or unchecked */
return shouldBeChecked ? Qt::Checked : Qt::Unchecked;
}
return QFileSystemModel::data(index, role);
}

Qt::ItemFlags CustomModel::flags(const QModelIndex& index) const {
return QFileSystemModel::flags(index) | Qt::ItemIsUserCheckable;
}

bool CustomModel::setData(const QModelIndex& index, const QVariant& value, int role) {
if (role == Qt::CheckStateRole) {
/* set indicator in your backing store according to whether value == Qt::Checked */
emit dataChanged(index, index);
return true;
}
return QFileSystemModel::setData(index, value, role);
}
You will have to invent a way of keeping track of which items are checked and unchecked, so that you can access that information in data and update it in setData.

been_1990
15th January 2010, 18:52
If this implementation of the flags() function returns "Qt::ItemIsUserCheckable", then each rows checkbox should be checkable, right?
That doesn't happen. It would be nicer to be able to check-uncheck without having to grab the mouse-click then call on setData().

Coises
15th January 2010, 19:28
If this implementation of the flags() function returns "Qt::ItemIsUserCheckable", then each rows checkbox should be checkable, right?
That doesn't happen.

Yes, but there’s an easy mistake to make, so I’ll take a guess that it’s relevant and describe it.

When the value returned by flags includes Qt::ItemIsUserCheckable (http://doc.trolltech.com/latest/qt.html#ItemFlag-enum), clicking on the checkbox causes setData to be called specifying the appropriate item index, Qt::CheckStateRole (http://doc.trolltech.com/latest/qt.html#ItemDataRole-enum) and either Qt::Checked (http://doc.trolltech.com/latest/qt.html#CheckState-enum) or Qt::Unchecked (http://doc.trolltech.com/latest/qt.html#CheckState-enum) according to the state to which the checkbox should change... but it doesn’t change the state for you.

You have to emit the dataChanged signal and return true, but you must also do whatever is required in your implementation to be sure that when data is called for that item index with Qt::CheckStateRole (http://doc.trolltech.com/latest/qt.html#ItemDataRole-enum) it returns the new check state.

If you don’t do those things, when you click on the checkboxes, they won’t change state. None of the Qt classes involved here will maintain the state of the checkboxes for you, not even as far as changing their appearance when you click them. The state is will always show as whatever your data function returns.

been_1990
16th January 2010, 00:09
Let's see if I got it right:
When I click on a checkbox, setData() is called.
It then emits dataChanged(), that probably calls the flag() function. Which then updates all indexes.

But then:
How can I check whether the checkbox state is checked/unchecked?
And how can I change only the state of the checkbox that was clicked on?(When I click on 1, all other rows get checked also)

It seems that setData()'s int role is always the same, 10(or Qt::CheckStateRole) whenever I click the checkbox.

Coises
16th January 2010, 00:45
Let's see if I got it right:
When I click on a checkbox, setData() is called.
It then emits dataChanged(), that probably calls the flag() function.

I haven’t checked the Qt source code, but I think that’s wrong. I think:

When displaying an item, Qt calls the data function with Qt::CheckStateRole (http://doc.trolltech.com/latest/qt.html#ItemDataRole-enum) to determine whether to show a box with a check mark, a box without a check mark, or no checkbox at all.
When you click on a checkbox, Qt calls the flag function to find out whether it should pay any attention.
If the flag function returns a value including Qt::ItemIsUserCheckable (http://doc.trolltech.com/latest/qt.html#ItemFlag-enum), Qt calls the setData function with a value that is the opposite of the one currently displayed by the checkbox.
The setData function now must emit dataChanged and return true; it must also do “something” that will tell the data function to return the opposite value from the one it returned before for this index.




And how can I change only the state of the checkbox that was clicked on?(When I click on 1, all other rows get checked also)


Qt doesn’t do that for you; you have to keep track. I’d suggest getting a QPersistentModelIndex in the setData function and storing those in a QSet<QPersistentModelIndex> that lists the ones that are checked. The data function could then use this to determine the proper check state to return.


Let's see if I got it right:
It seems that setData()'s int role is always the same, 10(or Qt::CheckStateRole) whenever I click the checkbox.

That’s expected. The value should change between checked and unchecked... if you’ve implemented data correctly.

been_1990
17th January 2010, 19:21
If the flag function returns a value including Qt::ItemIsUserCheckable, Qt calls the setData function with a value that is the opposite of the one currently displayed by the checkbox.
So if it's checked it will call setData with the Qt::Unchecked value?

setData(const QModelIndex& index, const QVariant& value, int role)
Trough which : "QVariant& value" or "int role" ?


The setData function now must emit dataChanged and return true; it must also do “something” that will tell the data function to return the opposite value from the one it returned before for this index.
So:

setData(const QModelIndex& index, const QVariant& value, int role) {
if (role == Qt::CheckStateRole) {
///////*
If the checkbox is checked, tell data() to uncheck it;
If unchecked, tell data() to check it;
*////////
emit dataChanged(index, index);
return true;
}
return QFileSystemModel::setData(index, value, role);
}


The value should change between checked and unchecked... if you’ve implemented data correctly.
You mean QVariant& value?

Coises
18th January 2010, 08:18
So if it's checked it will call setData with the Qt::Unchecked value?
[...]
Trough which : "QVariant& value" or "int role" ?


The value. We’re discussing only the case where role == Qt::CheckStateRole (http://doc.trolltech.com/latest/qt.html#ItemDataRole-enum); otherwise, the call applies to something else, not the checkbox.




The value should change between checked and unchecked... if you’ve implemented data correctly.
You mean QVariant& value?

Right. The aggravating part of this is that nothing in any of the Qt classes will maintain the state of each item’s checkbox for you. You have to invent a way of keeping track of that, feeding the correct value to the data function, and updating it in the setData function. If you don’t return the correct state from data when it’s called on Qt::CheckStateRole (http://doc.trolltech.com/latest/qt.html#ItemDataRole-enum), nothing else will work right.

been_1990
18th January 2010, 14:06
I already can change the checkbox state from checked to unchecked. But that still checks all of them(like you said it would..)
Do you think the code of QStandardItemModel would have the implementation to check/uncheck items independently? I'm gonna look at it, but I might get lost with all the code. (if there really is a check/uncheck mechanism that I could copy)

been_1990
18th January 2010, 14:19
Ok, here is the code. It aparently does what you suggested:


void QStandardItem::setData(const QVariant &value, int role)
{
Q_D(QStandardItem);
role = (role == Qt::EditRole) ? Qt::DisplayRole : role;
QVector<QWidgetItemData>::iterator it;
for (it = d->values.begin(); it != d->values.end(); ++it) {
if ((*it).role == role) {
if (value.isValid()) {
if ((*it).value.type() == value.type() && (*it).value == value)
return;
(*it).value = value;
} else {
d->values.erase(it);
}
if (d->model)
d->model->d_func()->itemChanged(this);
return;
}
}
d->values.append(QWidgetItemData(role, value));
if (d->model)
d->model->d_func()->itemChanged(this);
}


QVariant QStandardItem::data(int role) const
{
Q_D(const QStandardItem);
role = (role == Qt::EditRole) ? Qt::DisplayRole : role;
QVector<QWidgetItemData>::const_iterator it;
for (it = d->values.begin(); it != d->values.end(); ++it) {
if ((*it).role == role)
return (*it).value;
}
return QVariant();
}


Qt::ItemFlags QStandardItem::flags() const
{
QVariant v = data(Qt::UserRole - 1);
if (!v.isValid())
return (Qt::ItemIsSelectable|Qt::ItemIsEnabled|Qt::ItemIs Editable
|Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled);
return Qt::ItemFlags(v.toInt());
}

Coises
18th January 2010, 20:52
I already can change the checkbox state from checked to unchecked. But that still checks all of them(like you said it would..)
Do you think the code of QStandardItemModel would have the implementation to check/uncheck items independently?

I don’t think the code it has would be very useful, since it relies on instances of QStandardItem to keep the data, and you don’t have a model built on QStandardItems, you have a QFileSystemModel, the backing store of which is not accessible to programs that use the model.

Try something like this (off the top of my head, so errors/typos are not unlikely):


class CustomModel : public QFileSystemModel {
//...
QSet<QPersistentModelIndex> checklist;
//...
};

QVariant CustomModel::data(const QModelIndex& index, int role) const {
if (role == Qt::CheckStateRole) return checklist.contains(index) ? Qt::Checked : Qt::Unchecked;
return QFileSystemModel::data(index, role);
}

Qt::ItemFlags CustomModel::flags(const QModelIndex& index) const {
return QFileSystemModel::flags(index) | Qt::ItemIsUserCheckable;
}

bool CustomModel::setData(const QModelIndex& index, const QVariant& value, int role) {
if (role == Qt::CheckStateRole) {
if (value == Qt::Checked) checklist.insert(index);
else checklist.remove(index);
emit dataChanged(index, index);
return true;
}
return QFileSystemModel::setData(index, value, role);
}


Note that the QSet keeps QPersistentModelIndexes, not ordinary QModelIndexes.

been_1990
19th January 2010, 15:48
Seems pretty simple. I dont know why I thought it would be complicated. Should be kicking myself now...:mad:

the_jinx
11th March 2011, 15:28
This works like a charm, only down-side is that it gives me a checkbox per field.

I'd love to have a checkbox per row. I tried with index.column() == 0 but that only makes the first column checkbox checkable.
The other boxes are still shown.

What am I doing wrong?

totem
11th March 2011, 15:32
take a look at the CustomModel::flags() method, and add your column test here ?

the_jinx
11th March 2011, 16:01
That's what I did . . they are now non-checkable but still visible.

Added after 12 minutes:

Added column check to data method.

Now works like a charm!