Qt自定义的ColorDialog--仿QColorDialog

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基本完成了。

测试代码。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

docker安装mysql8.0.33

1 从docker仓库中拉去mysql 8.0 docker pull mysql:8.0如果使用 docker pull mysql 默认拉取的是最新版本的mysql 上面我拉去的是8.0的版本&#xff0c;最后拉取过来的是8.0.33 如果有想要指定的版本&#xff0c;可以直接写指定版本&#xff0c;如&#xff1a; docker pull my…

pytorch:nn.ModuleList和nn.Sequential、list的用法以及区别

文章目录 在构建网络的时候&#xff0c;pytorch有一些基础概念很重要&#xff0c;比如nn.Module&#xff0c;nn.ModuleList&#xff0c;nn.Sequential&#xff0c;这些类我们称为为容器&#xff08;containers&#xff09;&#xff0c;可参考containers。本文中我们主要学习nn.…

【Python】正则表达式应用

知识目录 一、写在前面✨二、姓名检查三、解析电影排行榜四、总结撒花&#x1f60a; 一、写在前面✨ 大家好&#xff01;我是初心&#xff0c;希望我们一路走来能坚守初心&#xff01; 今天跟大家分享的文章是 正则表达式的应用 &#xff0c;希望能帮助到大家&#xff01;本篇…

把字节大佬花3个月时间整理的软件测试面经偷偷给室友,差点被他开除了···

写在前面 “这份软件测试面经看起来不错&#xff0c;等会一起发给他吧”&#xff0c;我看着面前的面试笔记自言自语道。 就在这时&#xff0c;背后传来了leder“阴森森”的声音&#xff1a;“不错吧&#xff0c;我可是足足花了三个月整理的” 始末 刚入职字节的我收到了大学室…

Windows 10 X64 内核对象句柄表解析

fweWindows 很多API函数都会创建和使用句柄(传入参数)&#xff0c;句柄代表一个内核对象的内存地址&#xff0c;每个进程都有一个句柄表&#xff0c;它保存着进程拥有的句柄&#xff0c;内核也有一个句柄表 PspCidTable&#xff0c;它保存着整个系统的句柄。 ExpLookupHandleTa…

DNS风险分析及安全防护研究(一):DNS自身风险分析(中科三方)

作为互联网上的一项基础服务&#xff0c;DNS在网站运行中起到了至关重要的作用&#xff0c;然而其安全性在很长一段时间内都没有得到足够的重视。DNS采用不可靠的UDP协议&#xff0c;安全性具有较大的漏洞&#xff0c;攻击者很容易利用这些漏洞发动攻击&#xff0c;从而引起一些…

华为设备这14个广域网命令,值得每位做广域网业务的网工收藏!

你好&#xff0c;这里是网络技术联盟站。 华为设备广域网命令是网络管理员在运维过程中常用的一类命令。该命令集涵盖了DCC配置命令、PPP配置命令、MP配置命令、PPPoE命令、ATM配置命令、帧中继配置命令、HDLC配置命令、LAPB配置命令、X.25配置命令、IP-Trunk配置命令、ISDN配…

Java 与数据结构(6):快速排序

ChatGPT 中文指南(大全) 内容包含&#xff1a;如何开通chatgpt、chatgpt的同类站点、prompts 、AI绘图、ChatGPT 工具、相关报告论文、ChatGPT应用项目等 链接&#xff1a;ChatGPT 中文指南(大全) 指令指南&#xff0c;精选资源清单&#xff0c;更好的使用 chatGPT 让你的生产力…

详解如何使用LAMP架构搭建论坛

文章目录 1.LAMP概述2.编译安装Apache httpd服务1.关闭防火墙&#xff0c;将安装Apache所需软件包传到/opt目录下2.安装环境依赖包 3.配置软件模块4.编译及安装5.优化配置文件路径&#xff0c;并把httpd服务的可执行程序文件放入路径环境变量的目录中便于系统识别6.添加httpd系…

复杂的C++继承

文章目录 什么是继承继承方式赋值规则继承中的作用域&#xff08;隐藏&#xff09;子类中的默认成员函数需要自己写默认成员函数的情况 继承与友元及静态成员多继承菱形继承菱形继承的问题菱形虚拟继承 继承和组合 面向对象三大特性&#xff1a;封装继承和多态。封装在类和对象…

(四)调整PID控制器参数的指南

一、控制系统设计快速入门和环境 首先确定一下控制任务。快速、精准地控制&#xff0c;必要的稳定性&#xff0c;时域&#xff08;上升时间、超调等&#xff09;&#xff0c;频域&#xff08;带宽、阻尼比&#xff09;然后明白控制系统特点。类积分器&#xff1f;开环稳定性、高…

注解实现自动装配

要使用注解须知&#xff1a; 1.导入约束 context约束 2.配置注解的支持 官方配置文件 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/…

详解知识蒸馏原理和代码

目录 知识蒸馏原理概念技巧举例说明KL 散度及损失 KD训练代码导入包网络架构teacher网络student网络 teacher网络训练定义基本函数训练主函数 student网络训练&#xff08;重点&#xff09;理论部分定义kd的loss定义基本函数训练主函数 绘制结果teacher网络的暗知识softmax_t推…

C4d Octane渲染器内存满、卡顿、崩溃、缓慢、updating解决办法

最近碰到Octane渲染动画序列&#xff0c;总是会渲染一段时间后卡在某一张图片上&#xff0c;图片查看器左下角一直显示updating。 偶然发现在C4D界面点击octane工具栏的设置&#xff0c;它又会开始渲染&#xff0c;但渲染一些序列帧后又会卡在一张图上显示updating 点击octane工…

【Netty】 工作原理详解(十一)

文章目录 前言一、Netty 模型二、代码示例2.1、引入Maven依赖2.2、服务端的管道处理器2.3、服务端主程序2.4、客户端管道处理器2.5、客户端主程序2.6、测试运行 总结 前言 回顾Netty系列文章&#xff1a; Netty 概述&#xff08;一&#xff09;Netty 架构设计&#xff08;二&…

【Python]】地图热力图如何绘制?(含源代码)

文章目录 一、问题引入 & 使用地图的说明1.1 问题的引入1.2 使用地图的说明 二、方法1三、方法2 一、问题引入 & 使用地图的说明 1.1 问题的引入 我们有一个中国各省份的数据集&#xff0c;要求绘制地图热力图&#xff0c;该怎么实现呢&#xff1f; 部分数据集如下&…

tcp套接字的应用

tcp服务端流程 tcp客户端流程 客户端代码 tcpClient.hpp #include<iostream> #include<string> #include<cstring> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in…

2172. 最大公约数

Powered by:NEFU AB-IN Link 文章目录 2172. 最大公约数题意思路代码 2022年第十三届决赛真题 2172. 最大公约数 题意 给定一个数组, 每次操作可以选择数组中任意两个相邻的元素 x , y x, yx,y 并将其 中的一个元素替换为 gcd ⁡ ( x , y ) \operatorname{gcd}(x, y)gcd(x,y),…

117.【微信小程序】

微信小程序 (一)、微信小程序概括1.微信小程序简介(1).小程序与普通网页开发的区别 2.注册微信小程序账号(1).注册小程序账号(2).获取小程序的AppID 3.安装微信开发者工具(1).微信开发者工具的简介:(2).微信开发者工具的下载 4.创建第一个小程序(1).创建小程序步骤(2).开发者工…

新入职一个00后卷王,每天加班到2点,太让人崩溃了····

在程序员职场上&#xff0c;什么样的人最让人反感呢? 是技术不好的人吗?并不是。技术不好的同事&#xff0c;我们可以帮他。 是技术太强的人吗?也不是。技术很强的同事&#xff0c;可遇不可求&#xff0c;向他学习还来不及呢。 真正让人反感的&#xff0c;是技术平平&…