Friday, February 26, 2010

Flat QComboBox

The idea of having a flat looking QComboBox has aesthetic reasons behind it. A regular combo box when inserted into a QTableWidget (or another view widget) will not produce the same look and feel as a flat combo box, for example. Well let me keep it short; if you want your table cells look like a real cell but act like a combo (e.g. when you double click it opens a drop-down list), then you need a flat combo box (though other solutions might also be found). Look at the picture below:


OK, now when you get the idea, I'll present you the code. Basically, you'll need to have a moc-able header file and a corresponding source file (I'm sure you have!) In the header file:

#include <QComboBox>

class QFlatComboBox : public QComboBox
{
typedef QComboBox Super;

private:
Q_OBJECT;

public:
QFlatComboBox(QWidget *parent = 0);
bool flat() const { return flat_; }
void setFlat(bool flat);

Qt::Alignment arrowAlignment() const { return arrowAlignment_; }
void setArrowAlignment(Qt::Alignment a);

protected:
virtual void paintEvent(QPaintEvent *e);
virtual void mousePressEvent(QMouseEvent *e);

signals:
void aboutToPullDown();

private:
bool flat_;
Qt::Alignment arrowAlignment_;
};

Here we derive our QFlatComboBox from QComboBox to have all its functionalities with us, and override paintEvent and mousePressEvent methods for our own needs. Next is to see how the source file looks:

#include <QStylePainter>
#include <QLineEdit>
#include <QMouseEvent>
#include <QPalette>

QFlatComboBox::QFlatComboBox(QWidget *parent)
: Super(parent)
, arrowAlignment_(Qt::AlignRight)
, flat_(true)
{
setFlat(true);
setAutoFillBackground(true);
QPalette plt(palette());
plt.setColor(QPalette::Background, Qt::white);
setPalette(plt);
}

void QFlatComboBox::paintEvent(QPaintEvent *e)
{
if (flat()) {
QStylePainter painter(this);
painter.setPen(palette().color(QPalette::Text));
QStyleOptionComboBox opt;
initStyleOption(&opt);
QString displayText(opt.currentText);
opt.currentText = "";
painter.drawItemText(rect(), Qt::AlignCenter, palette(), true, displayText);
const QRect rcOld(opt.rect);
opt.rect = QStyle::alignedRect(Qt::LeftToRight, arrowAlignment(), QSize(16, rcOld.height()), rcOld);
painter.drawPrimitive(QStyle::PE_IndicatorArrowDown, opt);
opt.rect = rcOld;
painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
} else {
Super::paintEvent(e);
}
}

void QFlatComboBox::mousePressEvent(QMouseEvent *e)
{
if (!isEditable() || !lineEdit()->rect().contains(e->pos())) {
emit aboutToPullDown();
}
Super::mousePressEvent(e);
}

void QFlatComboBox::setFlat(bool flat)
{
flat_ = flat;
}
void QFlatComboBox::setArrowAlignment(Qt::Alignment a)
{
arrowAlignment_ = a;
}

Note that by default the arrow of the combo box will appear to the right (as it's in QComboBox) and our class is set to be flat. Also note that we can switch the flat property by using QFlatComboBox::setFlat. Also we can make the arrow appear to the e.g. left by QFlatComboBox::setArrowOrientation.
The important part however is the overridden paintEvent method which draws the combo box itself by using Qt's QStyleOptionComboBox class.

Now our QFlatComboBox can be used like a regular QComboBox. For your convenience there is a signal aboutToPull down which informs the client side that the combo's drop-down list box is about to appear.

Well, this source code can be used with no limitations.