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
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).
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::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
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.