Qt自定义的ColorDialog--仿QColorDialog

news2024/11/28 4:49:53

Qt已经有了色板选择,但是它使用QDialog形成的,每次调用基本上都成了点一个按钮,谈一个模态框,选择好颜色之后再关掉模态框。

但是,如果想将颜色选择板放在窗口上,并不会有模态的功能就会比较麻烦,所以为了这个目的,就再造一次轮子。

先看效果,下面的效果分别是自定义的colorWidgetQColorDialog的运行对比。
在这里插入图片描述

在这里插入图片描述

刚开始时,确实有种无处下手的感觉,后来突然想想,当你不会的时候,如果手边刚好有现成的差不多的东西,不仿去抄一抄,所以,我就看了下QColorDialog的运行方式,甚至去翻了下它的源码。

所以,编写一个程序将QColorDialog调出来,运行一下,看看他的运行过程,看能不能从中找到一点蛛丝马迹。

研究了一会,从它的运行过程中,我们能够很明显的得到以下几个结论:

  1. 鼠标在颜色幕布上滑动的时候,只改变它的 Hue和Sat的值,其Val值由右边的slider改变;
  2. 当鼠标在颜色幕布左上角时,Hue和Sat最大,对应右下角时最小;
  3. Hue的最大值为359,最小值为0,其他的范围都是0-255;
  4. 手动改变对应数值输入框的值,鼠标对应的十字线相应改变。

首先,我们知道color的HSV空间的数值就是0-360的区间,如下图所示:
在这里插入图片描述

那么,这个鼠标选择颜色的幕布刚好就是将这个空间从0和359这个点剪开之后展平了。

我们今天仿生的部分就是QColorDialog界面的右边部分,左边部分相对来说是比较简单的。

从这半部分界面来看,我们需要克服的难题,如何画出从左到右并且从下到上的渐变色。因为我们都知道,Qt是有一个QGradient类来绘制渐变色的,并且在这个类下面派生了三个已经现成的渐变方案。QConicalGradient, QLinearGradient, and QRadialGradient。而QLinearGradient刚好能够满足我们的需求。

所以,第一遍,我想到的就是用两个QWidget来叠放,然后设置两个QWidget的背景色为对应的渐变色。这样,这两个背景色的叠加显示效果就能够满足我们的视觉效果了。也就是下面这样。
在这里插入图片描述

background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0, stop:0 rgba(255, 0, 1, 255), stop:0.167 rgba(255, 0, 255, 255), stop:0.333 rgba(0, 0, 255, 255), stop:0.5 rgba(0, 255, 255, 255), stop:0.667 rgba(0, 255, 0, 255), stop:0.833 rgba(255, 255, 60, 255), stop:1 rgba(255, 0, 0, 255));

background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(0, 0, 0, 0), stop:1 rgba(255, 255, 255, 255));

这样来看,效果是满足的,但是要实现后面的鼠标拖动绘制十字线的话,还是要重新一个QWidget来做绘制类,那何不一次性全用这个呢?

所以,就有了下面这个类。

class ColorCoustomWidget : public QWidget
{
    Q_OBJECT

public:
    explicit ColorCoustomWidget(QWidget *parent = nullptr);
    ~ColorCoustomWidget();
    void setColor(const QColor& color);
protected:
    void paintEvent(QPaintEvent *event) override;
    void showEvent(QShowEvent *event) override;

private:
	void initPage();
    void drawBrush(QMouseEvent *event);
signals:
    void signal_color(const QVariant& data);
private:
    QPointF m_ptPointer;
    int m_val;
};

这个类的主要作用是用来实现颜色幕布的绘制、鼠标拖动时的十字线绘制及传递出去QColor HSV空间的两个数值。

有一个成员变量,用来表示十字线的原点,另一个成员变量用来表示HSV空间的Value值。

首先通过paintEvent函数来绘制幕布。

void ColorCoustomWidget::paintEvent(QPaintEvent *event)
{
    QImage back(size(), QImage::Format_ARGB32);
    back.fill(Qt::transparent);

    QPainter painter;
    painter.begin(&back);
    QPen pen = QPen(Qt::black, 2, Qt::SolidLine, Qt::RoundCap);
    painter.setPen(pen);

    QLineF line1(m_ptPointer.x() - 5, m_ptPointer.y(), m_ptPointer.x() + 5, m_ptPointer.y());
    QLineF line2(m_ptPointer.x(), m_ptPointer.y() - 5, m_ptPointer.x(), m_ptPointer.y() + 5);
    painter.drawLine(line1);
    painter.drawLine(line2);

    painter.end();

    painter.begin(this);
    painter.setCompositionMode(QPainter::CompositionMode_SourceOver);

    QLinearGradient linearGradientH(this->rect().topLeft(), this->rect().topRight());
    linearGradientH.setSpread(QGradient::PadSpread);

    linearGradientH.setColorAt((qreal)0, QColor(255, 0, 1, 255));
    linearGradientH.setColorAt((qreal)1 / 6, QColor(255, 0, 255, 255));
    linearGradientH.setColorAt((qreal)1 / 3, QColor(0, 0, 255, 255));
    linearGradientH.setColorAt((qreal)1 / 2, QColor(0, 255, 255, 255));
    linearGradientH.setColorAt((qreal)2 / 3, QColor(0, 255, 0, 255));
    linearGradientH.setColorAt((qreal)5 / 6, QColor(255, 255, 0, 255));
    linearGradientH.setColorAt(1, QColor(255, 0, 0, 255));
    painter.fillRect(this->rect(), linearGradientH);

    QLinearGradient linearGradientV(this->rect().topLeft(), this->rect().bottomLeft());
    linearGradientV.setColorAt(0, QColor(0, 0, 0, 0));
    linearGradientV.setColorAt(1, QColor(255, 255, 255, 255));
    linearGradientV.setSpread(QGradient::PadSpread);
    painter.fillRect(this->rect(), linearGradientV);
    painter.drawImage(0, 0, back);
    painter.end();
}

绘制完的效果跟前面叠加两个QWidget的效果是一样的。

鼠标事件的最终效果是用来确定成员变量的m_ptPointer的坐标。然后通过坐标绘制两条相交的长度为10的黑色线段。然后向上级emit一个改变颜色的信号。

void ColorCoustomWidget::drawBrush(QMouseEvent *event)
{
    if (event->type() == QEvent::MouseButtonPress)
    {
        m_ptPointer = event->pos();
    }
    else if (event->type() == QEvent::MouseMove)
    {
        m_ptPointer = event->pos();
    }
    else if (event->type() == QEvent::MouseButtonRelease)
    {
    }

    m_ptPointer.setX(qMax(0, qMin((int)m_ptPointer.rx(), size().width())));
    m_ptPointer.setY(qMax(0, qMin((int)m_ptPointer.ry(), size().height())));
    update();
    QColor t;
    t.setHsv(359* (1 - (qreal)m_ptPointer.rx() / size().width()), 255 * (1 - (qreal)m_ptPointer.ry() / size().height()), m_val);

    emit signal_color(t);
}

设置颜色值的时候需要进行一次转换,因为这个HSV空间的最大值是359和255,所以要根据界面的大小转换成在HSV空间对应的数值。

上级界面就是模仿QColorDialog,通过一个QSlider来模仿一个滑动块,通过留个QSpinBox分别表示颜色的各个数值。

接收ColorCoustomWidget类传上来的信号之后,设置界面的数值。

connect(ui->wdgCoustom, &ColorCoustomWidget::signal_color, this, [this](const QVariant& data)
{
    updateColor(data.value<QColor>());
});
void ColorWidget::updateColor(const QColor &color)
{
    ui->spinRed->setValue(color.red());
    ui->spinGreen->setValue(color.green());
    ui->spinBlue->setValue(color.blue());

    ui->spinHue->setValue(color.hsvHue());
    ui->spinSat->setValue(color.hsvSaturation());
    ui->spinVal->setValue(color.value());

    ui->lineEdit->setText(color.name(QColor::HexRgb));
    ui->wdgColor->setStyleSheet(QString("background:%1;").arg(ui->lineEdit->text()));
	
	QColor t;
	t.setHsv(color.hsvHue(), color.hsvSaturation(), 255);
    QString qss(QString("QSlider::groove {top:6px;bottom:6px;right: 6px;background: qlineargradient(x1:0, y1:0, x2:0, y2:1,stop:0 %1, stop:1#000000);}"
                        "QSlider::handle:vertical{border-image: url(:/resource/slider-handler.png);margin:-6px;}").arg(t.name(QColor::HexRgb)));

    ui->sliderVal->setStyleSheet(qss);
}

上面设置界面的时候,是要通过模拟的方式设置QSlide的样式,这个样式是根据界面的颜色来设置的。因为从底层传上来的QColor是HSV空间的,并且value值是固定的255;通过QSlider的滑动来改变value值。

所以就能很方便的确定渐变色的两个点。然后通过qss的方式设置QSlider的样式表。可以看见的QSlider右侧的那个三角形是手绘的一个22*9的矩形,设置之后与QSlider本体重合的部分是透明的,右半部分是一个三角形。

为了能够显示在右侧,需要设置right:6px;如果不设置这个属性,单纯地设置margin-right:6px;是不生效的。

这是一个新的知识点,毕竟尝试了很久才达到的效果。

接下来,为了逼真一点,抄的更像一点,我们需要在手动修改QSpinBox的数值时希望能够修改界面的颜色,并且十字线也能够跟随数值的变化而进行重绘。

所以我们需要,connect QSpinBoxvalueChanged 信号,并且重新设置界面。

auto slot_hsv = [this](int val)
{
    QColor color;
    color.setHsv(ui->spinHue->value(), ui->spinSat->value(), ui->spinVal->value());
    updateColor(color);
};

auto slot_rgb = [this](int val)
{
    QColor color;
    color.setRgb(ui->spinRed->value(), ui->spinGreen->value(), ui->spinBlue->value());
    updateColor(color);
};

connect(ui->spinHue, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, slot_hsv);
connect(ui->spinSat, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, slot_hsv);
connect(ui->spinVal, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, slot_hsv);
connect(ui->spinVal, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this](int val)
{
    ui->sliderVal->setValue(val);
});

connect(ui->spinRed, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, slot_rgb);
connect(ui->spinGreen, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, slot_rgb);
connect(ui->spinBlue, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, slot_rgb);

这样设置之后,按照预想应该是正常的,没想到的是,运行起来之后,直接堆栈溢出了。因为设置之后,其他的也改变了value,改变了就会发信号,发了信号我们就又设置了,设置了就又发信号,所以就一直在这样的循环中重复了起来。

后面查了setKeyBoardTracking(false);可以预防这种情况,所以堆每一个QSpinBox都进行了这样的设置。

设置完之后发现,如果connect的槽函数是同一个,是生效的,但是我们的六个QSpinBoxconnect了两个不同的槽函数,所以导致还是会发生上面一样的堆栈溢出的情况发生。

最后使用了一招暴力的解决方式,在两个槽函数中,在设置界面数据之前,对另外三个QSpinBox进行信号屏蔽,设置完界面之后再取消信号屏蔽。

uto slot_hsv = [this](int val)
{
	ui->spinRed->blockSignals(true);
	...
    QColor color;
    color.setHsv(ui->spinHue->value(), ui->spinSat->value(), ui->spinVal->value());
    updateColor(color);
    ui->spinRed->blockSignals(false);
    ...
};

上级界面通过手动修改颜色之后,需要更新底层颜色幕布的显示,所以就有了下面的这个函数:

void ColorCoustomWidget::setColor(const QColor& color)
{
    int hue = color.hsvHue();
    int sat = color.hsvSaturation();
    m_val = color.value();
    int rx = size().width() * (1 - (qreal)hue / 359);
    int ry = size().height() * (1 - (qreal)sat / 255);

    m_ptPointer.setX(rx);
    m_ptPointer.setY(ry);

    update();
}

设置之后也要进行一次反转换,才能将代表颜色的十字线准确的绘制在界面上。

至此,一个照猫画虎的colorWidget基本完成了。

测试代码。

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

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

相关文章

【Python GUI编程】零基础也能轻松掌握的学习路线与参考资料

Python GUI编程是指使用Python语言及其相关的GUI框架来开发图形用户界面的程序。学习Python GUI编程需要具备一定的基础知识&#xff0c;如Python语言基础、面向对象编程、GUI编程等。下面给出详细的学习路线和参考资料。 一、Python基础 学习Python GUI编程的第一步需要具备…

代理模式 静态代理 动态代理

代理对象可以在客户端和目标对象之间起到中介的作用&#xff0c;并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。 代理模式中的角色&#xff1a; 代理类目标类代理类和目标类的公共接口&#xff1a;客户端在使用代理类时就像在使用目标类&a…

接口加密解决方案:Python的各种加密实现

前言 在现代软件开发中&#xff0c;接口测试已经成为了不可或缺的一部分。随着互联网的普及&#xff0c;越来越多的应用程序都采用了接口作为数据传输的方式。接口测试的目的是确保接口的正确性、稳定性和安全性&#xff0c;从而保障系统的正常运行。 在接口测试中&#xff0…

【C++项目】负载均衡oj

前言&#xff1a; 本篇记录负载均衡oj项目设计的整体思路和部分代码。 负载均衡oj项目基于http网络请求&#xff0c;通过简单的前后端交互&#xff1a;前端的页面编辑已经提交代码&#xff0c;后端控制模块和编译运行的模块分离整合&#xff08;负载均衡式的选择后端编译运行服…

【2024最新】Spring面试题

✅✅作者主页:🔗请你喝杯Java的博客 🔥🔥精选专栏:🔗Java求职一条龙(持续更新中) 💞💞觉得文章还不错的话欢迎大家点赞👍➕收藏⭐️➕评论💬支持博主🤞 👉 👉你的一键三连是我更新的最大动力❤️❤️ 【2024最新】Spring面试题 一.Spring中@Resource…

Unity包围盒

序 比如&#xff0c;目前导入了一个obj文件&#xff0c;想知道它的AABB包围盒是什么。 官方文档 Unity - Scripting API: Bounds (unity3d.com) 可以看到&#xff0c;包围盒有三个类别的&#xff1a; Mesh.bounds Unity - Scripting API: Mesh.bounds (unity3d.com) 不随…

【万字解析、学习参考资料】MySQL数据库常见面试题

version&#xff1a;1.0 文章目录 基础篇&#x1f64e;‍♂️面试官&#xff1a; 非关系型数据库和关系型数据库的区别&#xff1f;&#x1f64e;‍♂️面试官&#xff1a; MySQL 数据库两种存储引擎的区别? 事务篇&#x1f64e;‍♂️面试官&#xff1a; 事务的四大特性了解…

C语言之网络编程(必背知识点)

一、认识网络 1、网络发展史 网络的来历_百度知道 ARPnetA--Internet--移动互联网--物联网 2、局域网和广域网 局域网&#xff08;LAN&#xff09; 局域网的缩写是LAN&#xff0c;local area network&#xff0c;顾名思义&#xff0c;是个本地的网络&#xff0c;只能实现小范围…

【KVM虚拟化】· 虚拟机的冷迁移和热迁移

目录 &#x1f34e;静态迁移(冷迁移) &#x1f34e;动态迁移&#xff08;热迁移&#xff09; &#x1f34e;迁移注意事项 &#x1f352;静态迁移 &#x1f352;动态迁移 &#x1f352;迁移帮助命令 &#x1f34e;迁移实例 &#x1f353;冷迁移 &#x1f353;热迁移 &#x1f35…

ChatGPT让我变成了“超人”-如何提升团队30%效能质量提高100%的阶段性总结报告

创作背景 CHATGPT刚出现时我的内心有一万匹“马”在奔腾&#xff0c;我是排斥的、BS的、甚至关掉屏敝掉相关新闻、连家里电视机的插线都拨掉。因为它的表现真的伤到了我的自尊。 这样的情绪源至我自己的“不自信”&#xff0c;不自信的前提是因为听到的东西太过于有“冲击性”了…

更适合电音的蓝牙耳机,设计真的很潮,哈氪零度青春版上手

现在低价位的耳机&#xff0c;音质都没什么特点&#xff0c;设计也是马马虎虎吧&#xff0c;想找一款好看好听的耳机还真不容易。最近我用的是一款哈氪零度青春版&#xff0c;这款耳机设计就很不错&#xff0c;上面加入了冰雪的元素&#xff0c;而且这款耳机音频素质也很不错&a…

ESP32-S3在VSCODE上编译烧录

1.准备 安装好ESP-IDF和VSCODE上的扩展插件 参考安装步骤1 参考按照步骤2 2.编译和烧录 &#xff08;1&#xff09;显示所有例程 &#xff08;2&#xff09;在get-started处选择hello_world&#xff0c;然后创建项目目录 &#xff08;3&#xff09;选择芯片类型&#xff0c…

【网络协议详解】——DNS系统协议(学习笔记)

目录 &#x1f552; 1. DNS的作用&#x1f552; 2. 域名结构&#x1f552; 3. 域名分类&#x1f552; 4. 域名空间&#x1f552; 5. 域名服务器类型&#x1f558; 5.1 根域名服务器&#x1f558; 5.2 顶级域名服务器&#x1f558; 5.3 权限域名服务器&#x1f558; 5.4 本地域名…

Java-软考总结

软考总结目录 宏观  学习感受  阶段划分 微观  1.自己看书和看视频&#xff1a;  2.学习的知识点和课后题进行结合  3.做往年的软考真题  4.提炼出相对来说难以攻克的问题组织分享和讨论  5.小组讨论做错的题并进行结构化 总结学习时间上学习方法上学习形式上 宏…

【Linux入门】Linux权限及管理

【Linux入门】Linux权限及管理 目录 【Linux入门】Linux权限及管理Linux权限管理文件访问者的分类文件类型和访问权限&#xff08;事物属性&#xff09; 文件权限值的表示方法文件访问权限的相关设置方法目录的权限实现共享目录粘滞位目录权限总结 作者&#xff1a;爱写代码的刚…

【iOS开发-多线程【四】pthreadNSThread

前言 多线程的最后一篇&#xff0c;从GCD的API到GCD的实现&#xff0c;学到了NSOperation和NSOperationQueue 慢慢了解了多线程的使用场景和众多原理&#xff0c;其中不乏涉及到了其他的知识&#xff0c;锁等。 这篇博客学习iOS常用的NSThread&#xff0c;了解pthread&#x…

DBeaver安装与使用教程

—仅供学习 侵权请联系删除– 一、DBeaver介绍 DBeaver是免费和开源&#xff08;GPL&#xff09;为开发人员和数据库管理员通用数据库工具。 1.它支持任何具有一个JDBC驱动程序数据库&#xff0c;也可以处理任何的外部数据源。 DBeaver 通过 JDBC 连接到数据库&#xff0c;可以…

在AgilePLM项目中使用积木报表

前言 目前市面上有很多比较好的报表工具&#xff0c;但很多收费都比较昂贵&#xff0c;这次找到一个开源免费的报表工具。推荐企业内部开发使用 积木报表虽然没有FineReport那么功能强大&#xff0c;但是目前测试下来也可以满足大部分报表功能。也是能缩短开发周期降低开发成…

PCDViewer的常用操作

PCDViewer是一款功能强大但操作极为简单的点云可视化和编辑软件&#xff0c;支持对点云的渲染显示、查询、量测、建图拼接、编辑、格式转换等功能&#xff0c;同时支持了pose文件、矢量文件等的显示。PCDViewer目前提供了Windows、Ubuntu18.04、Ubuntu20.04等版本。 本页面总结…

Mysql之高可用方案浅析

在工程项目中&#xff0c;系统应用的高可用性越来越重要&#xff0c;业主越来越重视。其实高可用可以分为应用层高可用和数据层高可用&#xff0c;数据层高可用中常见的有关系型数据库mysql的高可用、非关系型NoSQl数据库redis的高可用等&#xff0c;下面聊聊典型的关系型数据库…