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

测试代码。

Qt自定义的ColorDialog--仿QColorDialog相关推荐

  1. Qt 自定义信号与槽

    注 对象与槽理解 //第一个参数lineEdit是激发事件对象,信号中的方法必须在对象中存在,并在对象类头文件signals下定义,//第二个参数信号,//第三个参数this是槽方法所属类的对象,且必 ...

  2. QT自定义饼图的外观

    QT自定义饼图的外观 项目简介 项目技术 项目展示 主要源码片段解析 获取完整项目源码传送门 项目简介 自定义饼图的外观. 项目技术 qt5.12,qt charts模块,C++ 项目展示

  3. QT自定义图表上不同元素的外观

    QT自定义图表上不同元素的外观 项目简介 项目技术 项目展示 主要源码片段解析 获取完整项目源码传送门 项目简介 自定义图表上不同元素的外观. 项目技术 qt5.12,qt charts模块,C++ ...

  4. android 自定义取色器,【Android自定义View】仿Photoshop取色器ColorPicker(二)

    ColorPicker 一款仿Photoshop取色器的Android版取色器. 前言 上一篇已经简单介绍了ColorPicker的项目结构以及两种颜色空间,接下来我们详细解析一下ColorPicke ...

  5. 自定义xy组 android,Android自定义view之仿支付宝芝麻信用仪表盘示例

    自定义view练习 仿支付宝芝麻信用的仪表盘 对比图: 首先是自定义一些属性,可自己再添加,挺基础的,上代码 接着在构造方法里初始化自定义属性和画笔: private void initAttr(At ...

  6. 名片夹android布局代码,Android自定义布局实现仿qq侧滑部分代码

    自定义布局实现仿qq侧滑部分Android代码,供大家参考,具体内容如下 实现说明: 通过自定义布局实现: SlidingLayout继承于 HorizontalScrollView /** * Cr ...

  7. Android自定义View之仿QQ运动步数进度效果

    文章目录 前言 先看效果图 ![在这里插入图片描述](https://img-blog.csdnimg.cn/6e4ddec17933496ea4830fa08d8ffbe5.png?x-oss-pr ...

  8. Qt 自定义标题栏,最小化、最大化、关闭窗口,双击最大化,鼠标拖动等效果实现

    文章目录 前言 效果 代码 .pro文件 widget.h widget.cpp widget.ui title.h title.cpp title.ui 前言 本次实验内容为Qt自定义标题栏,最小化 ...

  9. Android显示九宫图(自定义圆角,仿微信九宫格图)

    详细解析Android显示九宫图(自定义圆角,仿微信九宫格图) 这是一个自定义九宫格图片框架,里面有设置圆角大小,还有当图片一张的时候控件自定义的大小,图片的间隔,四张图片的时候图片自定义为两行两列等 ...

最新文章

  1. 【 HihoCoder】1082 The Marshtomp has seen it all before (暴力 或 脑力)
  2. mysql索引详细介绍简书_MySql索引详解
  3. DiracNetV2
  4. OSI七层模型具体解释
  5. 如何设置使windows(dos)命令中目录和文件可以自动完成和补齐
  6. Exchange 2007 申请多域名证书
  7. JAVA 框架-Spring
  8. 改变定时器获取传感器频度_广东梅州梅县压力传感器*校对
  9. panda python_12个很棒的Pandas和NumPy函数,让分析事半功倍
  10. Linux 日志系统
  11. android 获取软件签名,获取Android应用签名
  12. android谷歌卫星地图,高德地图安卓端实现卫星地图路网功能
  13. 拿下多家主机厂数百万前装定点,禾赛科技激光雷达量产进程加速
  14. 复杂电路简化经典例题_复杂电路的简化策略
  15. [HEOI2013] 钙铁锌硒维生素
  16. 从零搭建飞冰微前端项目《第三篇:搭建微应用》❤️
  17. 给华为服务器RH2288V3(hm23-03)安装驱动
  18. stm32 + ESP8266 wifi获取网络时间和天气 stm32天气预报
  19. 【优化求解】基于遗传算法优化PARSEC 方法的翼型形状附matlab代码
  20. 移动互联网创业方向的思考(绕过腾讯和华为等大公司的战场,打造新型的餐饮平台)

热门文章

  1. 网络营销技术之——QQ空间留言
  2. CF821 E. Okabe and El Psy Kongroo 矩阵快速幂
  3. JS解构的5种有趣用法
  4. python uiautomator2 环境搭建和基本使用
  5. 前端整体阶段个人总结
  6. 设备、系统云监控平台
  7. 计算机课程免修申请书,课程免修申请书
  8. java程序设计基础_陈国君版第五版_第十一章习题
  9. Android Transition过渡动画
  10. Go学习笔记:flag库的使用