PDA

View Full Version : Radio buttons in a tree view



notwithstanding
23rd October 2008, 00:03
I want to provide radio buttons inside a tree view, using them in place of check boxes. This post (http://www.qtcentre.org/forum/p-qtreewidgetitem-radio-button-post54032/postcount4.html) describes these steps:

Telling the view to use a radio button icon instead of a check box one.
Providing mutually exclusive semantics underneath a tree model.


I have a subclass of QAbstractItemModel (let's call it MyModel) which works over an internal representation. To add radio buttons to it I've:

Reimplemented QItemDelegate::drawCheck to use a radio button primitive element.
Used an internal representation which only allows one internal object to have an active state.
Defined MyModel::data for CheckStateRole that is Qt::Checked or Qt::Unchecked dependent on whether the internal object for the button is active.
Defined MyModel::setData for CheckStateRole which modifies which internal object is active.
Defined MyModel::flags


There are two observable problems. The first is that selecting the button requires two clicks, one to select the cell, and the next to select the button itself. This is similar to the behavior for editing an editable cell: you have to click on the cell to select, and then click again to edit, but it's not an acceptable behavior for radio buttons. What I want is the single-click behavior I can get from a simple demo involving QTreeView and QStandardItem::setCheckable. I thought it might something to do with my combination of flags, but it doesn't appear to. I also presume I can change it by managing mouse events manually but I'd prefer not to.

The second problem is more serious. Selecting one radio button works fine. Selecting a second button shows that button as selected, but leaves the first button selected as well. Selecting the second button another time clears the first button. That seems like an obvious model problem, except that I've verified the internal state by printing it every time setData is called and only one object is active. What's more I've verified that when data is called its not only got only one object active, but that the appropriate Qt.Checked or Qt.Unchecked is being returned. It's not the model. The view seems to have a refresh problem. The obvious issue is emitting dataChanged(QModelIndex, QModelIndex) but that happens after every successful setData.

This is PyQt 4.4.2 over Qt 4.4.1. (While it's not impossible that this is a PyQt problem, my experience is that PyQt is a pretty faithful binding. When I have problems in PyQt with Qt's behavior which aren't fatal or clearly Python specific, rewriting the code in C++ as a test always produces the same behavior, so I'm strongly inclined to believe it's my misunderstanding Qt over it being a bindings issue.)

Relevant code samples below.

notwithstanding
23rd October 2008, 00:16
Relevant code snippets. These aren't the complete methods, just those parts involving the check state (that's probably obvious):

The tree model:



class TreeModel (QAbstractItemModel):

def flags(self, index):
flags = Qt.ItemIsEnabled
item = self.get_item(index)
# ...
if index.column() is TreeModel.COLUMN_X:
if item.is_active():
flags |= Qt.ItemIsSelectable | Qt.ItemIsUserCheckable
return flags

def data(self, index, role):
data = QVariant()
item = self.get_item(index)
# ...
if role == Qt.CheckStateRole:
if index.column() is TreeModel.COLUMN_X:
if item.is_active():
data = QVariant(Qt.Checked)
else:
data = QVariant(Qt.Unchecked)
return data

def setData(self, index, value, role):
status = False
item = self.get_item(index)
# ...
if role == Qt.CheckStateRole:
if index.column() is TreeModel.COLUMN_X:
if not item.is_active():
item.set_active(True)
else:
item.set_active(False)
status = True
self.emit(SIGNAL('dataChanged(QModelIndex,QModelIn dex)', index, index)
return status


The item delegate. Compare to the implementation of QItemDelegate.drawCheck. It differs only in two places:

The last line uses QStyle.PE_IndicatorRadioButton instead of QStyle.PE_IndicatorViewItemCheck
In the last two lines Instead of asking the private implementation for the owning widget (which can't be done), assume it's the tree view.





class TreeRenderer(QItemDelegate):

def drawCheck( self, painter, option, rect, state ):
'''Draw a radio button instead of a check button for mutually exclusive fields.'''
if not rect.isValid(): return
option.rect = rect
option.state &= ~QStyle.State_HasFocus
option.state |= {
Qt.Unchecked: QStyle.State_Off,
Qt.PartiallyChecked: QStyle.State_NoChange,
Qt.Checked: QStyle.State_On
}[ state ]
style = self.view.style() if self.view else QApplication.style()
style.drawPrimitive( QStyle.PE_IndicatorRadioButton, option, painter, self.view )

nifei
31st October 2008, 07:01
Why not implement QItemDelegate::createEditor() .etc to create a radioButton and open it persistent in treeView? in my opinion, Qt::CheckRole behaves like QCheckBox, and if the checked button need to be exclusive, other buttons have to be setChecked(false), every time one button was clicked. i had did similar work before, which needed several exclusive buttonGroups, and QButtonGroup works well.

spirit
31st October 2008, 08:31
Why not implement QItemDelegate::createEditor() .etc to create a radioButton and open it persistent in treeView?

imho, it is bad idea, because performance of your application will decrease, because a lot of descendants of QRadioButton are opend. I think the best way is implement your own model/delegate which will allow to use radio-items approach.

nifei
3rd November 2008, 02:23
imho, it is bad idea, because performance of your application will decrease, because a lot of descendants of QRadioButton are opend. I think the best way is implement your own model/delegate which will allow to use radio-items approach.

thank you. so, as "Delegate Class" part in Qt's doc says, "Delegates are expected to be able to render their contents themselves by implementing the paint() and sizeHint() functions.", it is not expected to keep a real editor widget persistently? if i need a radio button in some cells all the time, what should i do? should i implement CustomDelegate::paint(), and paint pixmaps of radio_on and radio_off in the cells? I used to do this like this:

CustomDelegate::createEditor()
{
return new QRadioButton;
}

and calling QItemView::openPersistentEditor()for each index, or else the editor will be destroyed the editing finishes. i knew it's not a good way i really need a radiobutton in my view.

spirit
3rd November 2008, 08:33
1) (quick method) you can use add method setExclusive to a view or to a delegate and then reimplement this method QItemDelegate::drawCheck. according to exclusivity you can draw checkbox (just call drawCheck of paren, i.e. QItemDelegate::drawCheck) or radiobox.
2) subclass a needed model, then subclass a needed item, e.g. QStandartItem, add to it mehtod setExclusive and extend methods setChecked for supporting exclusivity (:)) and then reimplement a method QItemDelegate::drawCheck.
I think that's all. :) I hope, I helped you.

nifei
3rd November 2008, 23:32
1) (quick method) you can use add method setExclusive to a view or to a delegate and then reimplement this method QItemDelegate::drawCheck. according to exclusivity you can draw checkbox (just call drawCheck of paren, i.e. QItemDelegate::drawCheck) or radiobox.
2) subclass a needed model, then subclass a needed item, e.g. QStandartItem, add to it mehtod setExclusive and extend methods setChecked for supporting exclusivity (:)) and then reimplement a method QItemDelegate::drawCheck.
I think that's all. :) I hope, I helped you.

thanks a lot. i'll try.