QPushButton icon aligned left with text centered

一世执手 提交于 2019-12-01 03:01:41

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
    }
}

Exemple of usage:

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:

  • Compared to docsteer answer, this solution makes it possible to apply the new style to only some buttons of your project. And the code is also smaller.
  • Compared to IGHOR answer, you can still use the button as a regular 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!