QTreeView with Radio Button
May. 8th, 2011 09:27 am![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Qt's QTreeView widget is a versatile tool to display information such as the content of a filesystem or the DOM tree of an XML file. Using the Model-View-Control (MVC) pattern, you can greatly control what, where, and how information is visualized. You can add icons or checkboxes to items, change color, font, or other aspects of the paint operation. What is missing is support for radio buttons in a QTreeView, but this post will show how to do it yourself.
QTreeView allows you to use checkboxes in items quite easily. First, you re-implement Qt::ItemFlags flags(const QModelIndex &index) const
in your model and return a flag value that is or-ed with Qt::ItemIsUserCheckable if you want to have index checkable. In QVariant data(const QModelIndex& index, int role) const
and bool setData(const QModelIndex& index, const QVariant& value, int role)
you have to handle role Qt::CheckStateRole. Here, value has to be interpreted as an int which may be either equal to Qt::Checked or Qt::Unchecked. Of course, your model has to memorize if an item is checkable and if so, if it is checked or unchecked. I guess you will find some good examples on the net on this topic explaining all the details I just missed.
The nice thing about checkboxes is that each checkbox is independent of any other checkbox regarding state. In contrast, two or more radio controls (a. k. a. radio buttons) form a group and only one radio control in a group can be “checked” and all others are “unchecked”. Whenever a user selects a radio control, all radio controls in the group have to be updated accordingly. This makes things tricky and I suspect this is the reason why radio controls are not supported out-of-the-box in Qt.
If you search the Internet on this problem, you will most certainly find some more or less informative/helpful postings on how to do it yourself. It is the intention of this posting to explain it in more detail. This explanation will follow my own implementation as coded for KBibTeX. You can look at the code by browsing the SVN repository (radiobuttontreeview.cpp, radiobuttontreeview.h). This class is used in findduplicatesui.cpp, if you are looking for a practical example.
Let's start with
RadioButtonTreeView
descending from QTreeView widget. It re-implements mouseReleaseEvent(QMouseEvent*)
and keyReleaseEvent(QKeyEvent*)
to catch events where the user attempts to set the radio control on the current index. The actual change process is done in switchRadioFlag(QModelIndex&)
where it is assumed that all siblings of the current index in the same column are radio controls as well (makes sense, doesn't it?). All radio controls except for the selected index are cleared by calling setData with a true/false value and the RadioSelectedRole role. Your model has to handle and process this call (details see below).
The paint job for radio controls is done in RadioButtonItemDelegate
which descends from QStyledItemDelegate. The paint
method falls back to the default implementation, unless the index is a radio control (determined via role IsRadioRole). In this case, all “normal” painting (including text, background highlighting) is shifted to the left. The new gap is used to draw a radio control button, which is either selected or unselected based on the data returned from the model when requested via the RadioSelectedRole role (your model has to implement this role).
As all drawing uses Qt's internal drawing methods, it should look native on all platforms (only tested it on KDE with the Oxygen theme).
The RadioButtonTreeView
class will behave like the original QTreeView
, except for indexes where you implement roles IsRadioRole and RadioSelectedRole. Role IsRadioRole is used to tell RadioButtonTreeView
than an index should contain a radio button, mouse and keyboard events for this case have to be handled, and the drawing of this index should be changed accordingly. Return QVariant()
or QVariant::fromValue(false)
if you do not want a radio button for an index, and return QVariant::fromValue(true)
to have a radio button. Role RadioSelectedRole is used to communicate the selection status (on/off) of the radio button. RadioButtonTreeView
will take care of telling your model if the state of any radio button changes, but your model has to store this change and return the stored change if requested. Settings the state is done by calling bool setData(const QModelIndex& index, const QVariant& value, int role)
where the value can be converted into a bool
via toBool()
.
In QVariant data(const QModelIndex& index, int role) const
, for role RadioSelectedRole return QVariant::fromValue(false)
if the button is to be unchecked and QVariant::fromValue(true)
if the button is to checked, respectively.
Happy coding!