PDA

View Full Version : QTreeView, Delegates and Persistent Widgets



chezifresh
19th November 2008, 00:32
I've created a QTreeView with a custom model and would like to display custom widgets for manipulating the data.

To do this Qt tells us to use the QItemDelegate (or the abstract one...) but this only provides an editor for the EditRole. In other words it only shows the delegate widget when a cell/index is in edit mode.

I want to ALWAYS show the widget not just display it after an EditTrigger. (Please for the love of god dont ask why, thats just what this GUI requires)

My model has some values that are boolean values. I would like to display (ie. in the default DisplayRole) these values in a QCheckBox. I dont want the user to have to double click to get to the check box. There's nothing worse than having to triple click to change the value.

I could probably do a trick like paint a fake checkbox and change the EditTriggers to allow for a single click to toggle the value but that's a pretty ugly hack. It seems like Qt should support this behavior if it doesnt already. Any thoughts? I know that many people have asked this question before and as far as I can tell there are no satisfactory answers out there. How about we come up with a real solution.

This may be the way to do it but its SUCKS and I wasn't able to actually get it to work. But calling
QTreeView.openPersistentEditor(index) should work. I tried it with the following code but nothing happened. But even if that did work... its besides the point, it pretty lame that you have to call that on every cell/index in your view. I have a pretty big model so it would be nice if I could do that on the entire treeview or at least on a given row/column.

This seems like a pretty big oversight if its not natively supported. And it probably could use some better documentation

Python:

checkboxColumn = 2
root = self._view.rootIndex()
for i in range(self._view.model().rowCount(root)):
self._view.openPersistentEditor(root.child(i, checkboxColumn))

Also to clarify... this is not just for check boxes... I might want to display a custom widget, or a combo box persistently.

wysota
19th November 2008, 10:00
Why not use Qt::CheckStateRole and display the checkbox not as an editor but as a regular feature of the delegate?

chezifresh
19th November 2008, 18:55
That works for check boxes but how about comboboxes or custom widgets?

Is there an easy way to override the delegate paint event to paint a real widget? One that responds to mouse events, not just a pixmap.

I found that the persistent editor nearly does the job. I've modified my QTreeView derivation to implement an openPersistentEditorForColumn method. This will require quite a bit of tweaking to make it look right since the persistent editor does not recognize the parent's colors (which makes it look especially weird if you have alternating row colors on)... but thats a problem I'm bound to have.

I suppose to make my life easier I can use the CheckStateRole when dealing with boolean values so I dont have to muck with the QCheckBox to make it paint right.

I'll continue with the persistent editor and try to report back if it works out well (without having to hack it together)

It still seems like this should be easier to do

wysota
19th November 2008, 20:30
The problem with inserting real widgets is that they slow down the view significantly when the number of items grows to more than 10 :) That's why you should avoid things like persistant editors. If you want comboboxes, they are easy to render, so by using QStyle you should be able to mimic them while the item is inactive. But for this particular situation I would suggest to open a single editor when you need it (you can react on clicking the view). Another solution is to have the editor as a totally external widget and update it when the item is changed (you can use QDataWidgetMapper for that).

chezifresh
19th November 2008, 22:08
Thanks for the replies. I'll have to consider if its worth the extra work/processing time to use persistent editors. For the time being at least I'll use the builtin checkboxes for boolean values and non-persistent editors for drop downs.

jwieland
22nd April 2009, 21:47
Why not use Qt::CheckStateRole and display the checkbox not as an editor but as a regular feature of the delegate?

Hi Wysota,

Pardon my ignorance, but I've been trying to do the same thing: implement a treeview with checkboxes and so far cannot get it to work. Could you elaborate more on how to use the Qt::ChectStateRole to achieve this?

Thank you

chezifresh
22nd April 2009, 23:08
I found that it kind of sucks using an QAbstractItemModel. So I'm now using a QStandardItemModel which is AWESOME.


class StandardAssetItem(QtGui.QStandardItem):
'''The StandardAssetItem represents a row/column cell in the Qt model.
'''
SUPPORTED_ROLES = [ Qt.EditRole, Qt.DisplayRole, Qt.CheckStateRole ]

def __init__(self, mydata):
QtGui.QStandardItem.__init__(self)

self._mydata = mydata
if type(mydata) == types.BooleanType:
checkstate = Qt.Unchecked
if mydata == True:
checkstate = Qt.Checked

self.setCheckState(checkstate)
self.setCheckable(True)
self.setEditable(False)

elif type(mydata) == types.StringType:
self.setText(mydata)

else:
self.setEditable(False)

def setData(self, variant, role):
if role not in self.SUPPORTED_ROLES:
return QtGui.QStandardItem.setData(self, variant, role)

value = None
if role in [ Qt.EditRole, Qt.DisplayRole ]:
value = variant.toPyObject()
if type(value) == PyQt4.QtCore.QString:
# convert it to a std string.
value = str(value)
elif role == Qt.CheckStateRole:
checkstate, okay = variant.toInt()
value = checkstate == Qt.Checked

if self._mydata != value:
self._mydata = value
# and tell the world the value changed

QtGui.QStandardItem.setData(self, variant, role)


Then in your subclass of the standard item model you use this item instead of QModelIndexes (you dont need to subclass StandardItem, but it might be useful to do)

wysota
23rd April 2009, 19:49
Pardon my ignorance, but I've been trying to do the same thing: implement a treeview with checkboxes and so far cannot get it to work. Could you elaborate more on how to use the Qt::ChectStateRole to achieve this?

You have to make your model aware of that role. So you have to make it store it somewhere and return data for it when asked from within the data() method. You also need to modify the flags returned by the model for items you wish checkable so that they return ItemIsUserCheckable.

vieraci
21st June 2009, 15:31
I found that it kind of sucks using an QAbstractItemModel. So I'm now using a QStandardItemModel which is AWESOME.


...

Then in your subclass of the standard item model you use this item instead of QModelIndexes (you dont need to subclass StandardItem, but it might be useful to do)

Hey I don't follow you here, I don't know python and can't understand what your code does or which methods you're over-riding. I assume you got your table view to accept a single click ? that's what I want to do with my view too. Can you please expand on what you've done ?

chezifresh
22nd June 2009, 18:41
I'm just subclassing QStandardItem and passing in a value in the constructor. Then based on the type of the value it either builds a check box or a normal QTreeView cell.

Now of course you dont need to subclass QStandardItem, you can just check if your value is a boolean call:



QStandardItem * item = new QStandardItem();
// for boolean values
int checkstate = Qt::Unchecked;
if mydata == true
checkstate = Qt::Checked;

item->setCheckState(checkstate);
item->setCheckable(true);
item->setEditable(false);


(that code might not be absolutely correct, I just wanted to make sure you could understand the C++ version of what I'm doing_

In my code I'm actually passing in a pointer to my object, and when setData on the QStandardItem is called I also update my object.... that way my object and the model are sync'd up without having to write my own model from scratch using a QAbstractItemModel. This works pretty well for me since I'm not to worried about the hierarchy of my model changing or things getting reordered outside of the view. If you need to worry about that then you might rethink things or at the very least implement a subject-observer to detect changes

barnabyr
5th July 2009, 23:17
Hi ...

So, if we could go back to combo boxes for a second, how many clicks on a view item does your program need to open a combo box ? One or more .. ?

Thanks.

vieraci
6th July 2009, 11:09
Hi ...

So, if we could go back to combo boxes for a second, how many clicks on a view item does your program need to open a combo box ? One or more .. ?

Thanks.

You can do it with one click. I implemented my own method which uses a custom delegate and re-implements the editorEvent(). The following code is for a check box, but it wouldn't be much diferent for any other type of widget.


// Trap the mouse button in editorEvent and check/uncheck the checkbox by calling setData()
// which is a subclassed member of the model this delegate is attached to.
bool CheckBoxDelegate::editorEvent ( QEvent * event,
QAbstractItemModel * model,
const QStyleOptionViewItem & /* option */,
const QModelIndex & index )
{
if (event->type() == QEvent::MouseButtonRelease)
{
bool checkState = index.model()->data(index, Qt::CheckStateRole).toBool();
model->setData(index, !checkState);
}
return true; // Event has been handled here.
}

barnabyr
6th July 2009, 20:12
You can do it with one click. I implemented my own method which uses a custom delegate and re-implements the editorEvent(). The following code is for a check box, but it wouldn't be much diferent for any other type of widget.


Hmm. Well for a check box it's easy. You are just toggling the state based on the mouse release event. But for a combobox you have to populate the combo and then actually care about which one was selected. Which all needs to happen though more events. I guess, because of the complexity, that's why the standard way of implementing the delegate is through the createEditor(), setEditorData(), setModelData() paradigm.

I'd be interested to see an example if you have one ....

faldzip
6th July 2009, 21:25
for a combobox you can make a real QComboBox for each item, but set WA_DontShowOnScreen, and instead of this render it in delegate's paint method using QWidget::render() (you have to set the eventFilter on every widget so you can react on PaintEvents). Then enable mouseTracking in the view (or it's viewport - dont remember) and handle mouse movement and clicks in editorEvent().

vieraci
6th July 2009, 23:54
You don't have to make a real combo box, it is populated in createEditor() method and don't forget, it's data is already set. All that has to be done is in the editorEvent() method, just call showPopup(). (I haven't tested but it should be this easy).

QAlex
14th December 2009, 16:51
Hi...
I think that my question isn't out of argument from the others, so I post here.

I have to obtain a result similar to that in the attached photo.
I started from different Qt examples in order to test different ways to obtain some results and then put them together.
At the moment I'm a bit stuck on a problem (the first): how to present data from the model in a frame like the one on the right of the sample photo. I tried to modify the spinboxdelegate example to visualize/edit/modify the data from the table in a lateral frame, but with no succes, because I don't know how to place the widget created by
QWidget *SpinBoxDelegate::createEditor function. I tried to reimplement the paint method, but the problem is always the same... :p
The 2nd problem is: I'm reading a lot of documentation trying to comprehend if delegates are the best solution to achieve my goal. At the moment I think so... But if someone has got much experience in this kind of programming and can indicate me an eventually better way, I'll surely evaluate it.

Note: At the moment I'm stuck on the first problem... The second is a little bigger, but It is part of the evaluation process.

I really hope someone can help me!
Best regards and Thanks in advance for your help. :)
Alex

QAlex
16th December 2009, 09:30
Any help?
Now I tried another way to obtain my goal.
I started from a little and simple example of the treemodel and I created a model containing items from a file. As item I used QStandardItem, as model I used QStandardItemModel, and I visualized all in a QTreeView. I stored in the various Items different kind of data: an icon with DecorationRole, a string with DisplayRole and some custom data with UserRole. Now my problem is:
the data that I want to edit when I doubleclick an item are stored in UserRole because if I store them with EditRole they become the same as DisplayRole ('cause of the standard implementation of QStandardItem).
My questions are:
1. How can I edit data in UserRole?
2. If that is not possible, I suppose I have to subclass QStandardItem and reimplement data and setData methods... Is it possible to reimplement only this two methods to obtain a different behaviour for DisplayRole and EditRole? Could someone please post an example?
3. Any hints on how to edit data? I think a custom delegate would be the better way, do you?

Thanks,
Alex