Easy combo box in list under Qt
Qt is a powerful framework for building desktop application user interfaces. However, when you need information on how to achieve a particular goal, it is sometimes difficult to gather it. It can be frustrating to feel that the solution is close at hand, yet things don’t work properly.
The Goals
I recently wanted to put a combo box widget among the items of a table list. While the standard way of doing this is simple, the result was not exactly what I wanted. Seeking to improve the usual solution to make the experience more friendly for the user, I searched for ready-made solutions on the internet. While I found some ideas in the Qt wiki and on Stack Overflow, there was no complete solution that worked to my satisfaction.
Now, I want to share the solution that I finally hit upon. Here’s what I wanted to do, and why:
1. I wanted to put a combo box directly in a multi-column table list widget.
Why: To be able to quickly change a value for an item in the list. This is actually not hard; examples can be found right in the Qt documentation.
2. I wanted the combo box to always be visible.
Why: So the user would know the item was directly editable. By default, Qt hides the combo box, requiring the user to double-click the item to show the combo box. Yet the average user wouldn’t necessarily figure this out, and would remain unaware that an item is editable!
3. I wanted the combo box to display its contents on the first click.
Why: Normally, in Qt, the first click selects the item in the list; however, with a normal combo box, the first click just displays its contents. This discrepancy means that a combo box embedded in a list works differently than a combo box outside of it, which is off-putting for the user.
4. I wanted a click on a combo box to select the table row.
Why: Normally, a click on a combo box is absorbed by the combo box and the row is not selected, which could confuse the user.
5. I wanted the combo box to end an item’s editing as soon as the user selects the item.
Why: Normally, Qt leaves the list item in editing mode until the user moves away from the item either by using the keyboard or by clicking elsewhere. Again, this is inconsistent compared to how the combo box works outside a list view.
The Solution
Here is how I was able to achieve all of these goals:
1. Put the combo box directly in the multi-column list widget.
How: Using a QStyledItemDelegate
that creates a combo box as an editor. This is standard Qt code, as shown in the Qt wiki.
2. Make the combo box always visible.
How: This is also standard Qt code: override the paint
and the sizeHint
functions of the delegate. The problem is that the exact code required is not obvious from the documentation, especially when it comes to getting the standard style. See the code sample on GitHub for details.
3. Show the content of the combo box on the first click.
How: This requires three tricks. First, override the mousePressEvent
function of the table widget in order to enter editing mode on the first click. In the mousePressEvent
function, fire up a single-shot timer that calls the edit
function of the table widget with the selected item index (the timer is required so that the trick for selecting the row will work later on.) Second, within the setEditorData
function of the delegate, pre-select the current item in the combo box with a call to setCurrentIndex
. Third, again in the setEditorData
function, call the showPopup
function of the combo box so that it shows its contents immediately.
4. Select the table row by clicking on the combo box.
How: We need to simultaneously select the row and fire up the editor. That’s why we delayed showing the editor; otherwise, the row selection would interfere. So, in the mousePressEvent
function, call setCurrentIndex
on the table selection model.
5. End item editing as soon as the user selects the item.
How: In the createEditor
function of the delegate, when creating the combo box, immediately connect to its currentTextChanged
signal. The connected function commits the data and ends the editor. This is done by calling the commitData
and the closeEditor
functions of the delegate.
The Code
The C++ code to have a permanently displayed combo box in a table widget is available in this public repository on GitHub.
(The example is provided as a Visual Studio 2017 solution.)