QPushButton 图标左对齐,文本居中
QPushButton icon aligned left with text centered
在我的 Qt 5.7.1 应用程序中,我有一些按钮,我想将按钮的图标与左文本和居中文本对齐,但设计器中没有这样做的选项。
我可以通过将以下代码添加到按钮样式表来对齐图标和文本:
text-align:left;
但这不是我想要达到的。
那么,你能告诉我,是否有任何选项可以将图标左对齐,并使文本居中对齐?谢谢你的帮助。
要获得这种级别的控制,您需要编写一些代码来覆盖您平台的样式。最好使用 QProxyStyle 来完成。在这种情况下,我们正在寻找何时要求样式绘制 CE_PushButtonLabel(标签包括图标,并且它们在 Qt 中硬编码以对齐在一起)。
您需要实现 QProxyStyle 并覆盖 drawControl() 方法。它的大部分代码是直接从默认 drawcontrol 方法的 Qt 源代码中复制的(在 qcommonstyle.cpp 中)——所以虽然它看起来很长,但它主要是在做什么Qt 已经做到了。我在我修改的部分周围放置了额外的 /****/ 标记。这不会显示在 Qt Designer 中,但会在运行时工作。
最终结果(显示在 mac 上,应与您所在的平台相匹配)
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);
}
不破坏UI风格的更少代码方式
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);
记得将 setText 信号发送到 textLabel 而不是 pushButton
按照 cbuchart 的建议,简单地专门化 QPushButton
并覆盖 paintEvent
和 sizeHint
。然后将其用作常规 QPushButton
.
MyButton
声明与实现:
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
}
}
用法示例:
这是一个示例,其中使用 Qt Designer 中的 "Promote widget" 功能从 .ui 文件创建 MyButton
:
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();
}
结果:
- 与 docsteer answer 相比,此解决方案可以将新样式仅应用于项目的某些按钮。而且代码也更小了。
- 与 IGHOR 答案相比,您仍然可以将按钮用作常规
QPushButton
(使用 QPushButton::setText
),您不需要保留对模型的引用 QLabel
更改按钮的文本。
在我的 Qt 5.7.1 应用程序中,我有一些按钮,我想将按钮的图标与左文本和居中文本对齐,但设计器中没有这样做的选项。
我可以通过将以下代码添加到按钮样式表来对齐图标和文本:
text-align:left;
但这不是我想要达到的。 那么,你能告诉我,是否有任何选项可以将图标左对齐,并使文本居中对齐?谢谢你的帮助。
要获得这种级别的控制,您需要编写一些代码来覆盖您平台的样式。最好使用 QProxyStyle 来完成。在这种情况下,我们正在寻找何时要求样式绘制 CE_PushButtonLabel(标签包括图标,并且它们在 Qt 中硬编码以对齐在一起)。
您需要实现 QProxyStyle 并覆盖 drawControl() 方法。它的大部分代码是直接从默认 drawcontrol 方法的 Qt 源代码中复制的(在 qcommonstyle.cpp 中)——所以虽然它看起来很长,但它主要是在做什么Qt 已经做到了。我在我修改的部分周围放置了额外的 /****/ 标记。这不会显示在 Qt Designer 中,但会在运行时工作。
最终结果(显示在 mac 上,应与您所在的平台相匹配)
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);
}
不破坏UI风格的更少代码方式
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);
记得将 setText 信号发送到 textLabel 而不是 pushButton
按照 cbuchart QPushButton
并覆盖 paintEvent
和 sizeHint
。然后将其用作常规 QPushButton
.
MyButton
声明与实现:
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
}
}
用法示例:
这是一个示例,其中使用 Qt Designer 中的 "Promote widget" 功能从 .ui 文件创建 MyButton
:
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();
}
结果:
- 与 docsteer answer 相比,此解决方案可以将新样式仅应用于项目的某些按钮。而且代码也更小了。
- 与 IGHOR 答案相比,您仍然可以将按钮用作常规
QPushButton
(使用QPushButton::setText
),您不需要保留对模型的引用QLabel
更改按钮的文本。