✍Qt自定义带图标按钮

news2024/11/25 2:24:00

✍Qt自定义带图标按钮

📝问题引入

近段时间的工作中,有遇到这样一个需求 📝:

一个按钮,有normal、hover、pressed三种状态的样式,并且normal和hover样式下,字体颜色和按钮图标不一样。

分析这个需求,背景色和文字颜色容易实现,复杂的点在于图标和隐藏的一个点——文字和图标的间距以及文字的换行。常规的样式表只能实现背景色、文字颜色,无法设置hover和normal状态下,不同的icon。当然,我们有很多种方式来实现这个需求:例如创建一个继承自QWidget的类,并在里面添加文字label和图标label。但这种方式需要处理按钮的点击事件(毕竟一个按钮不可能光秃秃的放在那里,肯定会有点击操作)。

我采用的方案是:创建一个QPushButton,并创建一个布局,在布局里塞入文字label和图标label,最后在将这个布局设置给创建的QPushButton。代码可能如下:

当然,还要处理一下按钮的hover事件:

// ... 假设你为这个按钮安装了事件过滤器
bool eventFilter(QObject* watched, QEvent* e)
{
    if (e->type() == QEvent::HoverEnter) {
        iconLabel->setProperty("LabelStatus", "Hover");
        textLabel->setProperty("LabelStatus", "Hover");
        this->style()->unpolish(iconLabel);
        this->style()->polish(iconLabel);
        this->style()->unpolish(textLabel);
        this->style()->polish(textLabel);
        
    } else if (e->type() == QEvent::HoverLeave) {
        iconLabel->setProperty("LabelStatus", "Normal");
        textLabel->setProperty("LabelStatus", "Normal");
        this->style()->unpolish(iconLabel);
        this->style()->polish(iconLabel);
        this->style()->unpolish(textLabel);
        this->style()->polish(textLabel);
    }

    return QWidget::eventFilter(watched, e);
}

你可以通过样式表来设置图标和文字样式:

QLabel#iconLabel[LabelStatus=Normal]
{
  border-image: url("xxx");
}

QLabel#iconLabel[LabelStatus=Hover]
{
  border-image: url("xxx_hover");
}

QLabel#textLabel[LabelStatus=Normal]
{
  color: #FFFFFF;
}

QLabel#textLabel[LabelStatus=Hover]
{
  color: #FF0000;
}

这样做一个好处就是你就不需要去单独处理点击事件(虽然都已经处理了HoverEnter和HoverLeave😓),还有一个好处是:在按钮过多时,你可以将按钮加入到QButtonGroup里,统一对按钮的点击事件进行处理

当然,代码还可以优化,你可以将这些内容封装成类:

class MyButton : public QPushButton
{
    Q_OBJECT

public:
    MyButton(QWidget* parent)
        : QPushButton(parent)
        , m_pIconLabel{nullptr}
        , m_pTextLabel{nullptr}
    {
        auto hLayout = new QHBoxLayout();
        hLayout->setContentMargins(4, 4, 4, 4);
        hLayout->setSpacing(8);	// 设置文字和图标的间距
        
        m_pTextLabel = new QLabel(this);
        m_pTextLabel->setObjectName("m_pTextLabel");
        
        m_pIconLabel = new QLabel(this);
        m_pIconLabel->setObjectName("m_pIconLabel");
        
        hLayout->addWidget(m_pTextLabel);
        hLayout->addWidget(m_pIconLabel);
        
        this->setLayout(hLayout);
        this->installEventFilter(this);

        this->setStyleSheet(R"(
            QLabel#iconLabel[LabelStatus=Normal]
            {
              border-image: url("xxx");
            }
            
            QLabel#iconLabel[LabelStatus=Hover]
            {
              border-image: url("xxx_hover");
            }
            
            QLabel#textLabel[LabelStatus=Normal]
            {
              color: #FFFFFF;
            }
            
            QLabel#textLabel[LabelStatus=Hover]
            {
              color: #FF0000;
            }
        )");
    }

private:
    bool eventFilter(QObject* watched, QEvent* e) override
    {
        if (e->type() == QEvent::HoverEnter) {
            m_pIconLabel->setProperty("LabelStatus", "Hover");
            m_pTextLabel->setProperty("LabelStatus", "Hover");
            
            this->style()->unpolish(m_pIconLabel);
            this->style()->polish(m_pIconLabel);
            this->style()->unpolish(m_pTextLabel);
            this->style()->polish(m_pTextLabel);
            
        } else if (e->type() == QEvent::HoverLeave) {
            m_pIconLabel->setProperty("LabelStatus", "Normal");
            m_pTextLabel->setProperty("LabelStatus", "Normal");
            
            this->style()->unpolish(m_pIconLabel);
            this->style()->polish(m_pIconLabel);
            this->style()->unpolish(m_pTextLabel);
            this->style()->polish(m_pTextLabel);
        }
    
        return QPushButton::eventFilter(watched, e);
    }

private:
    QLabel* m_pIconLabel;
    QLabel* m_pTextLabel;
};

但当把这个按钮加到到实际的界面时,问题出现了:

// ...创建界面
auto hLayout = new QHBoxLayout(this);

auto title = new QLabel(this);
title->setText("XXXXXX");

auto btn = new MyButton(this);

hLayout->addWidget(title);
hLayout->addStrect();
hLayout->addWidget(btn);

我想要的效果是:

在切换语言后,按钮的宽度能够跟随文字的长度变换而相应变宽或变窄。

但实际情况与预期有所不同😕。即使我将按钮或文字的sizePolicy设置为Expanding,效果依然不理想。而原生的QPushButton是可以根据文字宽度自动调整大小的。这是为什么呢🤔?按理说,我已经把自定义按钮内的文字标签等组件放入了一个水平布局中,它应该能够自动调整宽度,但实际上水平布局并未起作用。这种情况让我很好奇,想要去了解Qt的布局管理系统是如何工作的。

🚀Qt布局探秘

在Qt关于布局管理的[官方文档](https://doc.qt.io/qt-5/layout.html)中,有这样一句话:

:::success

Adding Widgets to a Layout

When you add widgets to a layout, the layout process works as follows:
  1. All the widgets will initially be allocated an amount of space in accordance with their QWidget::sizePolicy() and QWidget::sizeHint().

:::

大致意思就是:

所有界面的初始大小将根据其尺寸策略(sizePolicy)和尺寸建议(sizeHint)进行设定。

这让我突然有了一个灵感🌟,是不是因为QPushButton的sizeHint中,计算的文字控件与我所显示的文字控件不是同一个呢?而外层布局又是直接调用QPushButton的sizeHint函数来获取宽度的,进而导致按钮的大小不如人意。为了验证我的想法,我决定到QPushButton的源码中一探究竟。

QSize QPushButton::sizeHint() const
{
    Q_D(const QPushButton);
    // ...
    QString s(text());
    bool empty = s.isEmpty();
    if (empty)
        s = QStringLiteral("XXXX");
    QFontMetrics fm = fontMetrics();
    QSize sz = fm.size(Qt::TextShowMnemonic, s);
    if(!empty || !w)
        w += sz.width();
    if(!empty || !h)
        h = qMax(h, sz.height());
    opt.rect.setSize(QSize(w, h)); // PM_MenuButtonIndicator depends on the height
    
    // ...
    d->sizeHint = (style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(w, h), this).
                  expandedTo(QApplication::globalStrut()));
    return d->sizeHint;
}

事实确实是这样,QPushButton中计算的文本是通过text函数来获取的,而这个文本又是通过调用setText来设置的,我们绕过了这一步,那计算的大小就肯定不是我们想要的了。

🛠️问题的解决

看到这里,相信各位同学也都已经知道解决方法了:**实现自己的sizeHint。**

最终,我们的按钮类变成了:

class MyButton : public QPushButton
{
    Q_OBJECT

public:
    MyButton(QWidget* parent)
        : QPushButton(parent)
        , m_pIconLabel{nullptr}
        , m_pTextLabel{nullptr}
        , m_pLayout{nullptr}
    {
        m_pLayout = new QHBoxLayout();
        m_pLayout->setContentMargins(4, 4, 4, 4);
        m_pLayout->setSpacing(8);	// 设置文字和图标的间距
        
        m_pTextLabel = new QLabel(this);
        m_pTextLabel->setObjectName("m_pTextLabel");
        
        m_pIconLabel = new QLabel(this);
        m_pIconLabel->setObjectName("m_pIconLabel");
        
        m_pLayout->addWidget(m_pTextLabel);
        m_pLayout->addWidget(m_pIconLabel);
        
        this->setLayout(m_pLayout);
        this->installEventFilter(this);

        this->setStyleSheet(R"(
            QLabel#iconLabel[LabelStatus=Normal]
            {
              border-image: url("xxx");
            }
            
            QLabel#iconLabel[LabelStatus=Hover]
            {
              border-image: url("xxx_hover");
            }
            
            QLabel#textLabel[LabelStatus=Normal]
            {
              color: #FFFFFF;
            }
            
            QLabel#textLabel[LabelStatus=Hover]
            {
              color: #FF0000;
            }
        )");
    }

    QSize sizeHint()
    {
        return m_pLayout->sizeHint();
    }

private:
    bool eventFilter(QObject* watched, QEvent* e) override
    {
        if (e->type() == QEvent::HoverEnter) {
            m_pIconLabel->setProperty("LabelStatus", "Hover");
            m_pTextLabel->setProperty("LabelStatus", "Hover");
            
            this->style()->unpolish(m_pIconLabel);
            this->style()->polish(m_pIconLabel);
            this->style()->unpolish(m_pTextLabel);
            this->style()->polish(m_pTextLabel);
            
        } else if (e->type() == QEvent::HoverLeave) {
            m_pIconLabel->setProperty("LabelStatus", "Normal");
            m_pTextLabel->setProperty("LabelStatus", "Normal");
            
            this->style()->unpolish(m_pIconLabel);
            this->style()->polish(m_pIconLabel);
            this->style()->unpolish(m_pTextLabel);
            this->style()->polish(m_pTextLabel);
        }
    
        return QPushButton::eventFilter(watched, e);
    }

private:
    QLabel* m_pIconLabel;
    QLabel* m_pTextLabel;
    QHBoxLayout* m_pLayout;
};

通过返回内部布局的尺寸,来控制按钮在布局中的尺寸。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2236808.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenGL学习笔记(三) 绘制图形

glFrontFace(GL_CCW); // 设置CCW方向为“正面”&#xff0c;CCW即CounterClockWise&#xff0c;逆时针 glFrontFace(GL_CW); // 设置CW方向为“正面”&#xff0c;CW即ClockWise&#xff0c;顺时针#include <GL/glut.h>#include <math.h> void myDisplay(voi…

通过代码复习回忆 DiffusionDet: DiffusionTracker

DiffusionDet : DiffusionTracker复习回顾 之前的一段时间学习了基于扩散模型的检测于跟踪算法&#xff0c;最近在忙别的事情就导致了这里存在了很多和细节上的遗忘在这里进行一定的回顾&#xff0c;之后在试图看看可以进一步学习基于点集的扩散过程吗&#xff1f; Diffusion…

单体架构的 IM 系统设计

先直接抛出业务背景&#xff01; 有一款游戏&#xff0c;日活跃量&#xff08;DAU&#xff09;在两千左右&#xff0c;虽然 DAU 不高&#xff0c;但这两千用户的忠诚度非常高&#xff0c;而且会持续为游戏充值&#xff1b;为了进一步提高用户体验&#xff0c;继续增强用户的忠…

Java-字符串常量池

在Java程序中&#xff0c;类似于&#xff1a;1&#xff0c; 2&#xff0c; 3&#xff0c;3.14&#xff0c;“hello”等字面类型的常量经常频繁使用&#xff0c;为了使程序的运行速度更快、 更节省内存&#xff0c;Java为8种基本数据类型和String类都提供了常量池。 1.为什么要…

Wot Design Uni高颜值、轻量化的uni-app组件库 快速入门

一、简介 Wot Design Uni是一个基于Vue3和TypeScript开发的高颜值、轻量化的uni-app组件库。它提供了超过70个高质量组件&#xff0c;这些组件覆盖了移动端的主流场景&#xff0c;使得开发者能够更加高效地进行移动应用的开发。 以下是Wot Design Uni的一些主要特点&#xff…

maven依赖无法导入爆红问题

1、属于公司内部依赖&#xff0c;当前项目没有连接到公司Maven私服 2、之前本地已经下载过&#xff0c;但是下载中途失败了&#xff0c;产生了一个xxx.jar.lastUpdated文件&#xff0c;此时Maven不会对该依赖再下载 引入本地仓库依赖

MyBatis xml 文件中 SQL 语句的小于号未转义导致报错

问题现象 在 MyBatis 的 xml 文件中添加了一个 SQL 语句 <select id"countXxx" resultType"int">select count(*) from t1 where count < 3 </select>启动 Spring Boot 应用程序后报错&#xff1a; Caused by: org.apache.ibatis.builde…

前端学习之ES6+

1.ES6是什么 ES6&#xff0c;全称是ECMAScript 6&#xff0c;是JavaScript语言的下一代标准&#xff0c;由ECMA国际组织在2015年6月正式发布。ES6也被称作ECMAScript 2015&#xff0c;从这个版本开始&#xff0c;ECMA组织决定每年发布一个新的ECMAScript版本&#xff0c;以使J…

学习笔记:黑马程序员JavaWeb开发教程(2024.11.8)

5.10 分层解耦-分层解耦&#xff08;IOC-DI&#xff09; 在之前写的代码中&#xff0c;Controller层中new了一个Service层中的对象&#xff0c;在Service层中类名改变&#xff0c;则Controller层中也需要变化&#xff0c;这就是两个层之中耦合较重&#xff0c;需要减少耦…

Python常见并行化方法及性能对比

Python代码中通常有三种实现并行化的方法 multiprocessing的同步方法&#xff0c;mapmultiprocessing的异步方法&#xff0c;apply_asyncRay提供的并行或分布式能力 Ray 和 Python 的 multiprocessing 模块都是用于并行和分布式计算的工具&#xff0c;但它们在设计目标、功能…

【软考】系统分析师第二版 新增章节 第20章微服务系统分析与设计

微服务系统是一类基于微服务架构风格的分布式系统&#xff0c;它将应用程序拆分成多个独立的小型服务&#xff0c;每个服务都运行在独立的进程中&#xff0c;并采用轻量级通信协议进行通信。这些服务可以由不同的团队开发、不同的编程语言编写&#xff0c;并且可以按需部署。微…

【WRF理论第七期】WPS预处理

【WRF理论第七期】WPS预处理 运行WPS&#xff08;Running the WPS&#xff09;步骤1&#xff1a;Define model domains with geogrid步骤2&#xff1a;Extracting meteorological fields from GRIB files with ungrib步骤3&#xff1a;Horizontally interpolating meteorologic…

【设计模式】行为型模式(一):模板方法模式、观察者模式

行为型模式&#xff08;一&#xff09;&#xff1a;模板方法模式、观察者模式 1.模板方法模式&#xff08;Template&#xff09;1.1 主要特点1.2 适用场景1.3 示例1.3.1 抽象类定义模板方法1.3.2 子类实现具体步骤1.3.3 客户端1.3.4 结果输出 2.观察者模式&#xff08;Observer…

【ESP32】ESP-IDF开发 | 低功耗管理+RTC唤醒和按键唤醒例程

1. 简介 ESP32支持5种低功耗模式&#xff0c;低功耗管理单元包括调压器、功耗控制器、电源开关单元、电源域隔离单元 (Isolation Cell) 等部分。 1.1 RTC单元 RTC单元是ESP32低功耗管理的核心&#xff0c;可用于管理低功耗模式的进入和退出&#xff0c;控制时钟源、PLL、电源开…

国家电投“电投云”平台,浪潮信息助力其登顶IDC大奖

近日&#xff0c;国际权威咨询机构IDC揭晓了“2024 IDC中国未来企业大奖”优秀奖榜单。国家电力投资集团有限公司倾力打造的“电投云”平台&#xff0c;凭借其卓越的大规模云计算能力、高效的应用迁移设计&#xff0c;成功支撑了集团的产业数字化与管理数字化应用&#xff0c;为…

下载mysql的jar,添加至jmeter中,编写jdbc协议脚本1106

下载jar包&#xff1a; 步骤1&#xff1a;进入maven仓库官网https://mvnrepository.com/ 步骤2&#xff1a;搜索实际的数据库 步骤3&#xff1a;点击 Mysql connnector/J 步骤5、查看数据库的版本号&#xff0c;选择具体版本&#xff0c;我的是mysql 8.0.16,下图&#xff0c;…

【分布式事务】二、NET8分布式事务实践: DotNetCore.CAP 框架 、 消息队列(RabbitMQ)、 数据库(MySql、MongoDB)

介绍 [CAP]是一个用来解决微服务或者分布式系统中分布式事务问题的一个开源项目解决方案, 同样可以用来作为 EventBus 使用 github地址:https://github.com/dotnetcore/CAP官网地址: https://cap.dotnetcore.xyz/官网文档:https://cap.dotnetcore.xyz/userguide/zh/cap/id…

嘉吉连续第七年亮相进博会

以“新质绿动&#xff0c;共赢未来”为主题&#xff0c;嘉吉连续第七年亮相进博会舞台。嘉吉带来了超过120款产品与解决方案&#xff0c;展示嘉吉在农业、食品、金融和工业等领域以客户为中心的创新成果。这些产品融合了嘉吉在相关领域的前瞻性思考&#xff0c;以及对本土市场的…

低代码工作流平台概述-自研

讲解视频可看【【低代码】【企业级】【毕设】一键生成web应用&#xff0c;最强最便捷简单的低代码工作流平台-哔哩哔哩】 【低代码】【企业级】【毕设】一键生成web应用&#xff0c;最强最便捷简单的低代码工作流平台_哔哩哔哩_bilibili 1.在线设计数据库 2.表单设计 3.流程设…

vue3+vite 前端打包不缓存配置

最近遇到前端部署后浏览器得清缓存才能出现最新页面效果得问题 所以…按以下方式配置完打包就没啥问题了&#xff0c;原理很简单就是加个时间戳 /* eslint-disable no-undef */ import {defineConfig, loadEnv} from vite import path from path import createVitePlugins from…