In my Qt 5.7.1 application I\'ve some buttons, I want to align button\'s icons to left and centre text, but there is no option in designer to do this.
I can
Simply specialize QPushButton
and override paintEvent
and sizeHint
, as proposed by cbuchart here. Then use it as a regular QPushButton
.
MyButton
declaration and implementation:mybutton.h:
#pragma once
#include <QPushButton>
class MyButton : public QPushButton
{
public:
explicit MyButton(QWidget* parent = nullptr);
virtual ~MyButton();
void setPixmap(const QPixmap& pixmap);
virtual QSize sizeHint() const override;
protected:
virtual void paintEvent(QPaintEvent* e) override;
private:
QPixmap m_pixmap;
};
mybutton.cpp:
#include "mybutton.h"
#include <QPainter>
MyButton::MyButton(QWidget* parent) : QPushButton(parent)
{
}
MyButton::~MyButton()
{
}
QSize MyButton::sizeHint() const
{
const auto parentHint = QPushButton::sizeHint();
// add margins here if needed
return QSize(parentHint.width() + m_pixmap.width(), std::max(parentHint.height(), m_pixmap.height()));
}
void MyButton::setPixmap(const QPixmap& pixmap)
{
m_pixmap = pixmap;
}
void MyButton::paintEvent(QPaintEvent* e)
{
QPushButton::paintEvent(e);
if (!m_pixmap.isNull())
{
const int y = (height() - m_pixmap.height()) / 2; // add margin if needed
QPainter painter(this);
painter.drawPixmap(5, y, m_pixmap); // hardcoded horizontal margin
}
}
Here is an example where "Promote widget" feature in Qt Designer was used to create MyButton
from .ui files:
mainframe.ui:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="MyButton" name="button1">
<property name="text">
<string>Button</string>
</property>
</widget>
</item>
<item>
<widget class="MyButton" name="button2">
<property name="text">
<string>Other button</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>20</height>
</rect>
</property>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<widget class="QStatusBar" name="statusBar"/>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>MyButton</class>
<extends>QPushButton</extends>
<header>mybutton.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
mainwindow.h:
#pragma once
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
};
mainwindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QStyle>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QStyle* style = qApp->style();
// set buttons pixmaps:
ui->button1->setPixmap( style->standardPixmap(QStyle::SP_ComputerIcon) );
ui->button2->setPixmap( style->standardPixmap(QStyle::SP_TrashIcon) );
}
MainWindow::~MainWindow()
{
delete ui;
}
main.cpp:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Results in:
QPushButton
(using QPushButton::setText
), you don't need to keep a reference to a mockup QLabel
to change button's text.To get this level of control you will need to write some code to override the style for your platform. This is best done using a QProxyStyle. In this case we are looking for when the style is asked to draw a CE_PushButtonLabel (the label includes the icon, and they are hard coded in Qt to be aligned together).
You need to implement a QProxyStyle and override the drawControl() method. The code for the bulk of it is copied directly from the Qt source code for the default drawcontrol method (in qcommonstyle.cpp) - so although it looks lengthy, it is mostly doing what Qt already does. I put extra /****/ markers around the sections I modified. This won't show up in Qt Designer, but will work at runtime.
Final result (shown on mac, should match platform you're on)
main.cpp:
QApplication a(argc, argv);
a.setStyle(new MyStyle);
...
mystyle.h
class MyStyle : public QProxyStyle
{
public:
virtual void drawControl(ControlElement element, const QStyleOption *opt,
QPainter *p, const QWidget *widget = Q_NULLPTR) const;
};
mystyle.cpp
// Copied from Qt source code..
static QWindow *qt_getWindow(const QWidget *widget)
{
return widget ? widget->window()->windowHandle() : 0;
}
void MyStyle::drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *widget) const
{
if(element==CE_PushButtonLabel)
{
if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(opt))
{
QRect textRect = button->rect;
uint tf = Qt::AlignVCenter | Qt::TextShowMnemonic;
if (!proxy()->styleHint(SH_UnderlineShortcut, button, widget))
tf |= Qt::TextHideMnemonic;
if (!button->icon.isNull()) {
QRect iconRect;
QIcon::Mode mode = button->state & State_Enabled ? QIcon::Normal : QIcon::Disabled;
if (mode == QIcon::Normal && button->state & State_HasFocus)
mode = QIcon::Active;
QIcon::State state = QIcon::Off;
if (button->state & State_On)
state = QIcon::On;
QPixmap pixmap = button->icon.pixmap(qt_getWindow(widget), button->iconSize, mode, state);
int pixmapWidth = pixmap.width() / pixmap.devicePixelRatio();
int pixmapHeight = pixmap.height() / pixmap.devicePixelRatio();
int labelWidth = pixmapWidth;
int labelHeight = pixmapHeight;
int iconSpacing = 4;//### 4 is currently hardcoded in QPushButton::sizeHint()
int textWidth = button->fontMetrics.boundingRect(opt->rect, tf, button->text).width();
if (!button->text.isEmpty())
labelWidth += (textWidth + iconSpacing);
/*************************************************************/
// Make the icon rectangle always be 10px in from the left edge
/*************************************************************/
iconRect = QRect(10,
textRect.y() + (textRect.height() - labelHeight) / 2,
pixmapWidth, pixmapHeight);
iconRect = visualRect(button->direction, textRect, iconRect);
/***********************************/
// Always horizontal align the text
/***********************************/
tf |= Qt::AlignHCenter;
if (button->state & (State_On | State_Sunken))
iconRect.translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, opt, widget),
proxy()->pixelMetric(PM_ButtonShiftVertical, opt, widget));
p->drawPixmap(iconRect, pixmap);
} else {
tf |= Qt::AlignHCenter;
}
if (button->state & (State_On | State_Sunken))
textRect.translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, opt, widget),
proxy()->pixelMetric(PM_ButtonShiftVertical, opt, widget));
if (button->features & QStyleOptionButton::HasMenu) {
int indicatorSize = proxy()->pixelMetric(PM_MenuButtonIndicator, button, widget);
if (button->direction == Qt::LeftToRight)
textRect = textRect.adjusted(0, 0, -indicatorSize, 0);
else
textRect = textRect.adjusted(indicatorSize, 0, 0, 0);
}
proxy()->drawItemText(p, textRect, tf, button->palette, (button->state & State_Enabled),
button->text, QPalette::ButtonText);
}
return;
}
// For all other controls, draw the default
QProxyStyle::drawControl(element, opt, p, widget);
}
Less code way without breaking UI style
pushButton->setIcon(QApplication::style()->standardIcon(QStyle::SP_MessageBoxQuestion));
pushButton->setStyleSheet("text-align:left;");
pushButton->setLayout(new QGridLayout);
QLabel* textLabel = new QLabel("Hello world!");
textLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); // or center
textLabel->setAttribute(Qt::WA_TransparentForMouseEvents, true);
pushButton->layout()->addWidget(textLabel);
Remember to send setText signals to textLabel instead of pushButton