3

I've got a Qt/C++ app that contains a number of (non-editable) QComboBox widgets in a crowded window. The user can click on these combo-box to choose from various types of units. To keep the window width manageable, the QComboBoxes need to be as skinny as possible.

Conceptually, the available unit types in the combobox are these:

feet
meters
milliseconds
[etc]

... but to allow the QComboBoxes to be as skinny as possible, they are represented on-screen by abbreviations, e.g.:

f
m
mS
[etc]

... so far, so good, but what management would like now is to have the non-abbreviated strings ("feet", "meters", "milliseconds", etc) show up in the popup-menu that appears when the user clicks on the QComboBox... while retaining the abbreviated form in the box itself. This seems logically doable (since the popup-menu appears only briefly, in front of the rest of the GUI, there's no fundamental reason it cannot be made wider), but it's not clear to me how to implement this using QComboBox.

Is there a "right way" to do this (short of hacking the Qt combobox code)?

Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234
  • Probably a duplicate of http://stackoverflow.com/questions/16080431/qt-set-display-text-of-non-editable-qcombobox I think you will need to overide the completer as well. – Nathaniel Johnson Jun 09 '16 at 18:16
  • 1
    I didn't test it, but just a thought : maybe you can set a custom `QStyledItemDelegate` on your `QComboBox` where you reimplement the `displayText()` method to show the text you want. It may be simpler than your current answer... – IAmInPLS Jun 10 '16 at 12:22

2 Answers2

3

A delegate would be simpler:

class Delegate : public QStyledItemDelegate {
public:
    QString displayText(const QVariant & value, const QLocale & locale) const override {
        // value contains one of your short labels {"mS", "f", "m"}
        return /* Just return the corresponding long label */;
    }
};

And

yourComboBox->setItemDelegate(new Delegate);
O'Neil
  • 3,790
  • 4
  • 16
  • 30
2

Lacking a nice way to do this, I found an ugly way to do it... the following code seems to work well enough for me under MacOS/X and Windows, at least. (I haven't tried it under other OS's but I expect it would work elsewhere as well):

#include <QComboBox>
#include <QIdentityProxyModel>
#include <QStandardItemModel>
#include <QProxyStyle>

static const char * shortLabels[] = {"mS",           "f",    "m",      [... and so on...] };
static const char * longLabels[]  = {"milliseconds", "feet", "meters", [... and so on...] };

// A wrapper-facade data-model object that will provide the long label strings, but only when appropriate
class LongLabelsProxyModel : public QIdentityProxyModel
{
public:
    LongLabelsProxyModel(QAbstractItemModel * source, QObject * parent = 0) : QIdentityProxyModel(parent), forceShortLabelsCounter(0)
    {
       setSourceModel(source);
    }

    virtual QVariant data(const QModelIndex &index, int role) const
    {
        return ((forceShortLabelsCounter == 0)&&((role == Qt::DisplayRole)||(role == Qt::EditRole)))
               ? QVariant(QString(longLabels[index.row()])) :  
                 QIdentityProxyModel::data(index, role);
    }

    void BeginForceShortNames() {forceShortLabelsCounter++;}
    void EndForceShortNames()   {forceShortLabelsCounter--;}

private:
    int forceShortLabelsCounter;
};

#ifndef __APPLE__
// Under MacOS/X, this class isn't necessary, because OS/X uses a native/non-Qt popup widget.
// For other OS's, however, we need to make the Qt popup widget wider so the long labels can be fully visible
class WiderPopupProxyStyle : public QProxyStyle
{
public:
    WiderPopupProxyStyle(QStyle * baseStyle) : QProxyStyle(baseStyle) {/* empty */}

    virtual QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *option, SubControl sc, const QWidget *widget) const
    {
        QRect r = QProxyStyle::subControlRect(cc, option, sc, widget);
        if ((cc == QStyle::CC_ComboBox)&&(sc == QStyle::SC_ComboBoxListBoxPopup)) r.setWidth(r.width()*2);
        return r;
    }
};
#endif

// My customized combo-box class that will show long strings in the popup menu
class DelayUnitsComboBox : public EnumComboBoxComponent
{
public:
    DelayUnitsComboBox(QWidget * parent = NULL ) : QComboBox(parent)
    {
#ifndef __APPLE__
       // otherwise the popup list is the same width as the combo box
       // itself, which isn't wide enough to see the long strings
       setStyle(new WiderPopupProxyStyle(style()));
#endif

       setModel(new LongLabelsProxyModel(new QStandardItemModel(0, 1, this), this));

       // Officially populate the QComboBox with the short labels
       for (int i=0; i<((sizeof(shortLabels)/sizeof(shortLabels[0])); i++) addItem(shortLabels[i]);
    }

    // overridden so that when drawing the actual QComboBox, we still use the shorter names
    virtual void paintEvent(QPaintEvent * p)
    {
        LongLabelsProxyModel * llpm = static_cast<LongLabelsProxyModel*>(model());

        llpm->BeginForceShortNames();
        EnumComboBoxComponent::paintEvent(p);
        llpm->EndForceShortNames();
    }
};
Jeremy Friesner
  • 70,199
  • 15
  • 131
  • 234