(Last updated: September 24, 2017)
HTML/Rich Text in a QComboBox

One thing which Qt doesn't natively support is the use of HTML/Rich Text inside QComboBoxes, QCheckBoxes, and QRadioButtons. Of the issues I've come across while diving more deeply into Qt, this is the one which vexes me the most. I really don't know why this isn't a standard, default part of these widgets. IMO this shouldn't even require setting a boolean or anything -- just auto-detect that I'm putting in tags like you do with any QLabel, yeah?

Compounding that problem is that the solution for adding this in is a hell of a lot more work than it really should be. For QComboBox in particular, you've even got to have a completely separate solution for displaying the selected item versus displaying the selection list when changing the value.

Complete Solution

Rather than go into all the details first, here's a pyqt script demonstrating HTML text inside all three widget types: htmlwidgets.py

Note that this solution is rather limited because I'd only gone so far as to support the subset of functionality I've needed for an application. Namely:

Main Widget Details (QProxyStyle/HTMLStyle)

The QProxyStyle-derived class HTMLStyle in the code is the main solution. Just call setStyle(HTMLStyle()) on the widget and the base widget itself will support HTML text. In the case of QComboBox, keep in mind that it only affects the main currently-selected text. The selection list when changing the value will show the raw HTML inline unless you do some other stuff, below.

Specifically, the method to override from QProxyStyle is drawItemText, which is what's called when the widget wants to display text. There's other methods you could override instead, but I like this one because it works for multiple widgets and just feels like the correct one to overload. Some of the specifics of how I coded drawItemText was taken from the C++ code for QStyle. A QTextDocument is what I'm using to do the actual text rendering. See the full file, above, for details.

QComboBox widget width

One of the issues which I encountered after that was that the QComboBox would be rendered far too wide, because its width was being computed as if the text were plaintext, rather than the rendered HTML. The way I ended up solving this may or may not be the best way to do it, but it works well for me.

Basically I subclassed QComboBox and provided my own sizeHint and minimumSizeHint functions which loop through all the items in the QComboBox's model, rendering them through a QTextDocument. The implementation I put together caches that sizing information once and will never regenerate it, so if you're dealing with QComboBoxes whose contents change dynamically, you'll have to edit that behavior to be a bit more smart. The Qt sourcecode for QComboBoxPrivate::recomputeSizeHint may prove useful if you're looking into that: qcombobox.cpp.

QCheckBox/QRadioButton widget width

QCheckBox and QRadioButton have the same width issue that QComboBox did, and the way I solved that was exactly the same: subclass and provide my own sizeHint and minimumSizeHint implementations which take into account rendering HTML text. As with QComboBox, my solution caches the size once and then never recomputes, so if you need to change the display text, there's more work to do.

The example file I have up there actually abuses/uses Python's multiple inheritance to use the same base class for both widgets.

QComboBox Item Selection

The next problem is that the item selection for the QComboBox still renders using plaintext, so the HTML tags are there to see. To fix this issue, we need to create a subclass of QStyledItemDelegate and pass an instance in to QComboBox.setItemDelegate. The Delegate will have to implement its own paint function.

Figuring out an implementation for this is a bit tricky, not least because your paint function is also responsible for rendering the mouseover color changes as you move between items. In the end, I did find a solution to this at the bottom of this thread at qtcentre.org. There's a few details in there that I'm still not 100% sure of, but it seems to do the trick just fine. The version of it which I've included above is edited somewhat - there's some extraneous processing which takes place in the qtcentre.org version which I've stripped out, and I've commented it a bit better as well.

QComboBox Item Selection Width

The final problem (which I'm aware of, anyway) is that the item selection dropdown area which we implemented in the QStyledItemDelegate subclass wil render too wide. The main QComboBox widget itself will be showing up at the proper width because of our fixes above, but the selection area will be too wide.

I suspect that the way I fixed this isn't really proper. What I did is to go back to the QProxyStyle implementation (where I reimplemented drawItemText) and added an override for sizeFromContents. When that function sees that it's dealing with my custom HTMLComboBox class, it pulls the sizing information out of that class and uses that rather than using the stock information.

As I say, I expect that that's not really the best way to do it, but it happens to work pretty well for me.

Conclusion

That's it, really. It's a pretty labyrinthine collection of classes and functions to work through, just to implement something which should really be a standard, easy feature. And my implementation of this isn't even complete! Ah well, perhaps in Qt 6.

Changelog

September 24, 2017
  • Added explicit info about QRadioButton
  • Resize QCheckBox and QRadioButton properly
September 20, 2017
  • Initial post