`QStyle`自定义重绘`QSlider`控件
简介: 根据QStyle
的继承关系和重绘原理;通过实现一个继承QCommonStyle
类的实现,实现自己的自定义控件QSlider
控件。
文章目录
- 本博文的简述or解决问题?
- 系列博文:
- 运行效果:
- QSlider属性:
- 理解属性步长sigleStep、pageSteop:
- 重绘QSlider:
- 计算所需要的三个部分的矩形:
- 再对其中每一个矩形(一共3个)进行重绘:
- 若刻度异常情况(非bug):
- 解决方法:
- 思考总结:
- 互联网分享:
- 系列地址:
本博文的简述or解决问题?
详情: 见简介
编程环境: deepin 15.11 x64 专业版
Kernel: x86_64 Linux 4.15.0-30deepin-generic
编程软件: Qt Creator 4.8.2 (Enterprise)
, Qt 5.9.8
系列博文:
QStyle
自定义重绘QSlider
控件- QStyle之PenStyle的CustomDashLine使用 【更新:更加精准的绘画滑槽】
- 重绘的QStyle中sizeFromContents()没有被调用
- QStyle自定义重绘QSlider控件二 (重要)
运行效果:
先上一张最终的重绘运行效果图
运行代码,在 main() 函数里面:
QApplication app(argc, argv);qApp->setStyle("chameleon");QWidget wTemp;wTemp.resize(800, 600);// NoTicks = 0,
// TicksAbove = 1,
// TicksLeft = TicksAbove,
// TicksBelow = 2,
// TicksRight = TicksBelow,
// TicksBothSides = 3QHBoxLayout *layout = new QHBoxLayout(&wTemp);QSlider *slider1 = new QSlider(&wTemp); //竖直刻度在右侧的Sliderslider1->setOrientation(Qt::Vertical);slider1->setTickPosition(QSlider::TicksRight);slider1->setTickInterval(10);slider1->resize(40, 300);QSlider *slider2 = new QSlider(&wTemp); //竖直刻度在左侧的Sliderslider2->setOrientation(Qt::Vertical);slider2->setTickPosition(QSlider::TicksLeft);slider2->setTickInterval(10);slider2->resize(40, 300);slider2->move(150, 0);QSlider *slider3 = new QSlider(&wTemp); //水平刻度在下侧的Sliderslider3->setOrientation(Qt::Horizontal);slider3->setTickPosition(QSlider::TicksBelow);slider3->resize(300, 40);slider3->setTickInterval(10);slider3->move(0, 400);QSlider *slider4 = new QSlider(&wTemp); //水平刻度在上侧的Sliderslider4->setOrientation(Qt::Horizontal);slider4->setTickPosition(QSlider::TicksAbove);slider4->resize(300, 40);slider4->setTickInterval(10);slider4->move(400, 400);QSlider *slider5 = new QSlider(&wTemp); //竖直刻度在两侧的Sliderslider5->setOrientation(Qt::Vertical);slider5->setTickPosition(QSlider::TicksBothSides);slider5->resize(40, 300);slider5->setTickInterval(10);slider5->move(300, 0);QSlider *slider6 = new QSlider(&wTemp); //水平刻度在两侧的Sliderslider6->setOrientation(Qt::Horizontal);slider6->setTickPosition(QSlider::TicksBothSides);slider6->resize(300, 40);slider6->setTickInterval(10);slider6->move(0, 500);wTemp.show();return app.exec();
QSlider属性:
- Qt文档:
先看Qt官方文档介绍,我搽,就这么两个属性???然后大致浏览完了整篇。怎么给我一点,怎么只有这么一点讲解的内容啊!!!这可不行,谁受得了啊 ,然后继续看源码,看其继承的基类QAbstractSlider等之后。这还差不多。不然我怎么使用QStyle重绘这个控件
- tickInterval : int
此属性保存tickmarks之间的间隔这是一个值间隔,而不是像素间隔。如果为0,滑块将在singleStep和pageStep之间进行选择。 - tickPosition : TickPosition
此属性保存此滑块的tickmark位置,有效值由QSlider::TickPosition enum描述。默认值是QSlider:: notice。
刻度条绘画方向:
Constant | Description |
---|---|
QSlider::NoTicks | 不显示刻度 |
QSlider::TicksBothSides | 两边都显示刻度 |
QSlider::TicksAbove | 刻度显示在上边(水平) |
QSlider::TicksBelow | 刻度显示在下边(水平) |
QSlider::TicksLeft | 刻度显示在左边(竖直) |
QSlider::TicksRight | 刻度显示在右边(竖直) |
QSlider的摆放方向:
orientation : Qt::Orientation | Description |
---|---|
Qt::Vertical | 滑动条竖直绘画 |
Qt::Horizontal | 滑动条水平绘画 |
其他几个涉及QStyle重绘的重要属性:
val | 含义 |
---|---|
minimum | 滑动条最小值 |
maximum | 滑动条最大值 |
singleStep | 在min-max之间显示的步长,移动一步的改变数值 |
pageStep | 用于计算刻度的个数(有阈值限制,不完全按这个来,没详细研究,柑橘用来有点迷); |
value | 当前的显示数值(在min-max之间,也是本信号的槽的参数的数值) |
sliderPosition | ?? |
tickInterval | 两个刻度之间的间隔数值(重绘使用) |
tickPosition | 刻度的位置 |
注意: pageStep 这个用于计算刻度的个数(有阈值限制,不完全按这个来,没详细研究,柑橘用来有点迷); 刻度个数 - 1 = 刻度间隔的个数 = (span - 0) / pageStep; 关于0, span, min, max, val 的关系,参见sliderPositionFromValue()的实现 [此处特指qfusionstyle.cpp 里面的, 不知道qcommstyle.cpp实现原理是否相同?]
理解属性步长sigleStep、pageSteop:
因为QSlider = 滑块(句柄)+ 滑槽 + 刻度(矩形);
创建一个简单的小例子,核心代码如下:
void Widget::on_sliderHor_valueChanged(int value)
{int minimum = ui->sliderHor->minimum(); //滑动条最小值int maximum = ui->sliderHor->maximum(); //滑动条最大值int sigleStep = ui->sliderHor->singleStep(); //在min~max之间显示的步长,移动一步的改变数值int pageSteop = ui->sliderHor->pageStep(); //用于计算刻度的个数(有阈值限制,不完全按这个来)int val = ui->sliderHor->value(); //当前的显示数值(在min~max之间,也是本信号的槽的参数的数值)int sliderPosition = ui->sliderHor->sliderPosition(); //?? 不是很清楚int tickinterval = ui->sliderHor->tickInterval(); //两个刻度之间的间隔数值(重绘使用)int tickPosition = ui->sliderHor->tickPosition(); //刻度的位置QString str = QString("value:%1, minimum:%2, maximum:%3, sigleStep:%4, pageSteop:%5, val:%6, sliderPosition:%7, tickinterval:%8, tickPosition:%9").arg(value).arg(minimum).arg(maximum).arg(sigleStep).arg(pageSteop).arg(val).arg(sliderPosition).arg(tickinterval).arg(tickPosition);qDebug()<<str;
}
其效果如下:有qDebug可以查看每一个数值的含义,以及变化可以看出其含义(每次按下按下一个方向→或←按键);就会显示一行数据, 重点观察sigleStep、sigleStep、pageSteop的数值;
感觉其上面这些矩形,大多是使用时候,而非更底层的重绘该空间使用的;而sliderPosition、tickPosition才是重绘使用得。有了上面的基础之后,下面开始正经的重绘:
重绘QSlider:
重绘思路:
- 先预先计算好滑块(矩形)+ 滑槽(矩形) + 刻度(矩形)
- 对该部分矩形进行绘画
计算所需要的三个部分的矩形:
QRect CustomStyle::subControlRect(QStyle::ComplexControl cc, const QStyleOptionComplex *opt,QStyle::SubControl sc, const QWidget *w) const
{switch (cc) {case CC_Slider: {if (const QStyleOptionSlider *option = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {QRectF rect = option->rect; //Slider控件总的大小矩形int slider_size = proxy()->pixelMetric(PM_SliderControlThickness, opt, w); //滑块的高度
// int tick_size = proxy()->pixelMetric(PM_SliderTickmarkOffset, opt, w); //刻度的高度QRectF slider_handle_rect = rect; //滑块和滑漕的的最小公共矩形 (后面被用作临时且被改变的)if (option->orientation == Qt::Horizontal) {slider_handle_rect.setHeight(slider_size);if (option->tickPosition == QSlider::TicksAbove) slider_handle_rect.moveBottom(rect.bottom());if (option->tickPosition == QSlider::TicksBelow) slider_handle_rect.moveTop(rect.top());if (option->tickPosition == QSlider::TicksBothSides) slider_handle_rect.moveCenter(rect.center());} else {slider_handle_rect.setWidth(slider_size);if (option->tickPosition == QSlider::TicksRight) slider_handle_rect.moveLeft(rect.left());if (option->tickPosition == QSlider::TicksLeft) slider_handle_rect.moveRight(rect.right());if (option->tickPosition == QSlider::TicksBothSides) slider_handle_rect.moveCenter(rect.center());}QRectF rectStatic = slider_handle_rect; //rectStatic作为 滑块和滑漕的的最小公共矩形(不改变)switch (sc) {case SC_SliderGroove: { //滑漕qreal groove_size = slider_size / 4.0;QRectF groove_rect;if (option->orientation == Qt::Horizontal) {groove_rect.setWidth(slider_handle_rect.width());groove_rect.setHeight(groove_size);} else {groove_rect.setWidth(groove_size);groove_rect.setHeight(slider_handle_rect.height());}groove_rect.moveCenter(slider_handle_rect.center());return groove_rect.toRect();}case SC_SliderHandle: { //滑块int sliderPos = 0;int len = proxy()->pixelMetric(PM_SliderLength, option, w);bool horizontal = option->orientation == Qt::Horizontal;sliderPos = sliderPositionFromValue(option->minimum, option->maximum, option->sliderPosition,(horizontal ? slider_handle_rect.width() : slider_handle_rect.height()) - len, option->upsideDown);if (horizontal) {slider_handle_rect.moveLeft(slider_handle_rect.left() + sliderPos);slider_handle_rect.setWidth(len);slider_handle_rect.moveTop(rectStatic.top());} else {slider_handle_rect.moveTop(slider_handle_rect.top() + sliderPos);slider_handle_rect.setHeight(len);slider_handle_rect.moveLeft(rectStatic.left());}return slider_handle_rect.toRect();}case SC_SliderTickmarks: { //刻度的矩形QRectF tick_rect = rect;if (option->orientation == Qt::Horizontal) {tick_rect.setHeight(rect.height() - slider_handle_rect.height());if (option->tickPosition == QSlider::TicksAbove) {tick_rect.moveTop(rect.top());} else if (option->tickPosition == QSlider::TicksBelow) {tick_rect.moveBottom(rect.bottom());}} else {tick_rect.setWidth(rect.width() - slider_handle_rect.width());if (option->tickPosition == QSlider::TicksLeft) {tick_rect.moveLeft(rect.left());} else if (option->tickPosition == QSlider::TicksRight) {tick_rect.moveRight(rect.right());}}return tick_rect.toRect();}default:break;}}break;}default:break;}return QCommonStyle::subControlRect(cc, opt, sc, w);
}
再对其中每一个矩形(一共3个)进行重绘:
void ChameleonStyle::drawComplexControl(QStyle::ComplexControl cc, const QStyleOptionComplex *opt,QPainter *p, const QWidget *w) const
{switch (cc) {case CC_Slider : {if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(opt)) {//各个使用的矩形大小和位置QRectF rect = opt->rect; //Slider控件最大的矩形(包含如下三个)QRectF rectHandle = proxy()->subControlRect(CC_Slider, opt, SC_SliderHandle, w); //滑块矩形QRectF rectSliderTickmarks = proxy()->subControlRect(CC_Slider, opt, SC_SliderTickmarks, w); //刻度的矩形QRect rectGroove = proxy()->subControlRect(CC_Slider, opt, SC_SliderGroove, w); //滑槽的矩形// qDebug()<<"____04_____Slider控件最大的矩形(包含如下三个):"<<rect<<" 滑块矩形:"<<rectHandle<<" 刻度的矩形:"<<rectSliderTickmarks<<" 滑槽的矩形:"<<rectGroove<<endl;// //测试(保留不删)p->fillRect(rect, Qt::gray);p->fillRect(rectSliderTickmarks, Qt::blue);p->fillRect(rectGroove, Qt::red);p->fillRect(rectHandle, Qt::green);qDebug()<<"---rect:"<<rect<<" rectHandle:"<<rectHandle<<" rectSliderTickmarks:"<<rectSliderTickmarks<<" rectGroove:"<<rectGroove;QPen pen;//绘画 滑槽(线)if (opt->subControls & SC_SliderGroove) {pen.setStyle(Qt::CustomDashLine);QVector<qreal> dashes;qreal space = 1.3;dashes << 0.1 << space;pen.setDashPattern(dashes);pen.setWidthF(3);pen.setColor(getColor(opt, QPalette::Highlight));p->setPen(pen);p->setRenderHint(QPainter::Antialiasing);if (slider->orientation == Qt::Horizontal) {p->drawLine(QPointF(rectGroove.left(), rectHandle.center().y()), QPointF(rectHandle.left(), rectHandle.center().y()));pen.setColor(getColor(opt, QPalette::Foreground));p->setPen(pen);p->drawLine(QPointF(rectGroove.right(), rectHandle.center().y()), QPointF(rectHandle.right(), rectHandle.center().y()));} else {p->drawLine(QPointF(rectGroove.center().x(), rectGroove.bottom()), QPointF(rectGroove.center().x(), rectHandle.bottom()));pen.setColor(getColor(opt, QPalette::Foreground));p->setPen(pen);p->drawLine(QPointF(rectGroove.center().x(), rectGroove.top()), QPointF(rectGroove.center().x(), rectHandle.top()));}}//绘画 滑块if (opt->subControls & SC_SliderHandle) {pen.setStyle(Qt::SolidLine);p->setPen(Qt::NoPen);p->setBrush(getColor(opt, QPalette::Highlight));p->drawRoundedRect(rectHandle, DStyle::pixelMetric(DStyle::PM_FrameRadius), DStyle::pixelMetric(DStyle::PM_FrameRadius));}//绘画 刻度,绘画方式了参考qfusionstyle.cppif ((opt->subControls & SC_SliderTickmarks) && slider->tickInterval) { //需要绘画刻度p->setPen(opt->palette.foreground().color());int available = proxy()->pixelMetric(PM_SliderSpaceAvailable, slider, w); //可用空间int interval = slider->tickInterval; //标记间隔
// int tickSize = proxy()->pixelMetric(PM_SliderTickmarkOffset, opt, w); //标记偏移
// int ticks = slider->tickPosition; //标记位置int v = slider->minimum;int len = proxy()->pixelMetric(PM_SliderLength, slider, w);while (v <= slider->maximum + 1) { //此处不添加+1的话, 会少绘画一根线const int v_ = qMin(v, slider->maximum);int pos = sliderPositionFromValue(slider->minimum, slider->maximum, v_, available) + len / 2;if (slider->orientation == Qt::Horizontal) {if (slider->tickPosition == QSlider::TicksBothSides) { //两侧都会绘画, 总的矩形-中心滑槽滑块最小公共矩形p->drawLine(pos, rect.top(), pos, rectHandle.top());p->drawLine(pos, rect.bottom(), pos, rectHandle.bottom());} else {p->drawLine(pos, rectSliderTickmarks.top(), pos, rectSliderTickmarks.bottom());}} else {if (slider->tickPosition == QSlider::TicksBothSides) {p->drawLine(rect.left(), pos, rectHandle.left(), pos);p->drawLine(rect.right(), pos, rectHandle.right(), pos);} else {p->drawLine(rectSliderTickmarks.left(), pos, rectSliderTickmarks.right(), pos);}}// in the case where maximum is max intint nextInterval = v + interval;if (nextInterval < v)break;v = nextInterval;}}}break;}default:break;}DStyle::drawComplexControl(cc, opt, p, w);
}
若刻度异常情况(非bug):
注意: 下图中是使用pen.setStyle(Qt::DotLine);
来绘画的,上面的代码修改为了pen.setStyle(Qt::CustomDashLine);
来绘画,所以会**(看到的滑槽)**略有不一样;
当显示区域比较小的时候,而刻度条的个数又比较多的时候(密密麻麻的那种),当超过某一阈值时候,系统会自动压缩显示,一个变成**“胖瘦相间隔”**,此时如果将该粗窗口放大,则会被重绘画显示正常,刻度条均匀相间隔。
原图效果:
本来还以为是自己重新绘画的效果造成的,后面确定是过密形成的显示异常情况.就想着有没有能够的解决方法,能够重新绘画显示.想着参考QFusion
风格的实现,于是翻看源码,只看绘画刻度部分的Qt源码:
if (option->subControls & SC_SliderTickmarks) {painter->setPen(outline);int tickSize = proxy()->pixelMetric(PM_SliderTickmarkOffset, option, widget);int available = proxy()->pixelMetric(PM_SliderSpaceAvailable, slider, widget);int interval = slider->tickInterval;if (interval <= 0) {interval = slider->singleStep;if (QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, interval,available)- QStyle::sliderPositionFromValue(slider->minimum, slider->maximum,0, available) < 3)interval = slider->pageStep;}if (interval <= 0)interval = 1;int v = slider->minimum;int len = proxy()->pixelMetric(PM_SliderLength, slider, widget);while (v <= slider->maximum + 1) {if (v == slider->maximum + 1 && interval == 1)break;const int v_ = qMin(v, slider->maximum);int pos = sliderPositionFromValue(slider->minimum, slider->maximum,v_, (horizontal? slider->rect.width(): slider->rect.height()) - len,slider->upsideDown) + len / 2;int extra = 2 - ((v_ == slider->minimum || v_ == slider->maximum) ? 1 : 0);if (horizontal) {if (ticksAbove) {painter->drawLine(pos, slider->rect.top() + extra,pos, slider->rect.top() + tickSize);}if (ticksBelow) {painter->drawLine(pos, slider->rect.bottom() - extra,pos, slider->rect.bottom() - tickSize);}} else {if (ticksAbove) {painter->drawLine(slider->rect.left() + extra, pos,slider->rect.left() + tickSize, pos);}if (ticksBelow) {painter->drawLine(slider->rect.right() - extra, pos,slider->rect.right() - tickSize, pos);}}// in the case where maximum is max intint nextInterval = v + interval;if (nextInterval < v)break;v = nextInterval;}}
等等, 这根本没有考虑这个问题,好不好。而且感觉绘画刻度的方法,是对一个矩形矩形多次精确的计算,eeemmmmmmmmmm,这样子是不是有点复杂了了。感觉没有我的将这一个超大矩形,分割成为三个小矩形这一思路简单,然后在对每一块小矩形进行相应的绘画。
解决方法:
决定尝试一下其原生**QFusion
风格()是怎么解决这个效果,当设置刻度间隔比较小的时候,显示宽度比较窄时候,这个东西出现了"胖瘦相互间隔",然后利用上面的一开始创建的小例子,改变步长和刻度间个和max值,看看效果**
甚至再次改变步长和刻度间个和max值,看看效果。过于密集,成了线
握草,握了个草,我握了个大草.hhhhhhhhhhhhhhhhhh,原来老哥你也没有设置这个问题啊,果断的出结论,这不是bug.果断不再继续修改了重绘画了.坏坏的笑了几下之后。
于是将一开始出现问题的地方,将该窗口最大化,然后局部拉大,看到这个效果(果然得到了验证):
最后总结:
这个不是bug,或者显示异常。冷静下来是思考:如此窄的矩形里面,显示如此多根刻度线,由于像素限制,只能后绘画的比较密集,当这个数值更大的时候,会发现,这个会变成一条直线(放大拉开显示,才会显示其实是均匀相间隔的).
思考总结:
- 首先检查代码,是否是相关部分的代码逻辑有问题
- 仍然觉得不应该之后,试一下Qt自带的是否会重现,排除是自己还是非自己原因
- 改变相关的值,写小例子验证,查看效果
- 发现经验:+1; 完美结束
互联网分享:
因为有着许许多多的热心网友的无私分享,从他们的博客中学习成长,学会很多,故也不辞辛苦也将自己的项目或经验整理成博客的形式,也提供给一起大家学习探讨与交流
系列地址:
QtExamples
欢迎 star
和 fork
这个系列的 QT / DTK 学习,附学习由浅入深的目录。
`QStyle`自定义重绘`QSlider`控件相关推荐
- QStyle自定义重绘QRubberBand控件
简介: 根据QStyle的继承关系和重绘原理:通过实现一个继承QCommonStyle类的实现,实现自己的自定义控件QRubberBand控件. 文章目录 本博文的简述or解决问题? 运行效果: QR ...
- QStyle自定义重绘QScrollBar
简介: QStyle自定义重绘QScrollBar(滚动条)样式. 文章目录 本博文的简述or解决问题? 运行效果: QScrollbar官方教程: QScrollbar属性理解: 系列地址: 本博文 ...
- c#-winform重绘Tabcontrol控件,标签带Logo图标
模仿网页浏览器标签重绘Tabcontrol控件,每个标签页左上角的Logo图标可以自定义,当然图标也可以挪到右边,直接上图.
- 重绘MenuStrip 控件
重绘MenuStrip控件 效果如图: 首先添加 CustomProfessionalRenderer类 用于重绘控件菜单样式 /// <summary>/// 自定义MenuStrip ...
- android 父控件的背景_android控件拖动,移动、解决父布局重绘时控件回到原点
/** * * @description 设置意见反馈,用以灰度发布 * @author zhongwr * @params * @update 2016年1月12日 下午5:36:07 */ pri ...
- android控件拖动,移动、解决父布局重绘时控件回到原点
最近公司做小游戏开发,写倒计时加进度条 拖动问题private boolean isIntercept = false; /** 按下时的位置控件相对屏幕左上角的位置X */ private int ...
- 重绘CButton控件
1,创建一个类 继承自:CButtonl; #pragma once// CMyButtonclass CMyButton : public CButton {DECLARE_DYNAMIC(CMyB ...
- C#重绘TabControl控件的源码(转)
代码 1using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using ...
- 继承QWidget使用QPainter自定义二维图形控件【Qt学习】
继承QWidget使用QPainter自定义二维图形控件[Qt学习] 通过阅读该文章,将了解本文所说的二维图形控件的基本概念.为何要自定义二维图形控件.如何自定义二维图形控件. 该文章将首先进行一些书 ...
最新文章
- MatConvnet工具箱文档翻译理解(3)
- multinorm r语言_与心理学数据分析相关的R工具包
- Pseudoprime numbers POJ - 3641(快速幂+判素数)
- 如何正确运用计算机技术,如何正确认识与运用计算机网络技术.doc
- 前端学习(1705):前端系列javascript之原型中的this
- 铁路系统的在线检测系统讲座
- mysql mmm vip_MySQL MMM架构看不到vip地址
- 实用的摩斯编码(二)
- tomcat7不支持html5吗,HTTP状态404说明请求的资源()不可用。的Apache Tomcat/7.0.27
- 数学建模——01规划 / 线性规划 (工具:matlab + lingo,算法:Folyd)
- 我的世界可以在服务器用修改器,我的世界TMI修改器使用教程
- 一般描绘性形容词_英语中描绘性形容词是指哪种形容词?
- Unity中下载图片、音频和视频
- 团体程序设计天梯赛——L1-039 古风排版
- iocomp ActiveX/VCL各版本大比拼之Crack
- 青岛新媒体运营教程:短视频没脚本怎么行,镜头脚本模板直接套用
- 计算机诞生历史小故事
- 城市隧道水位监测与预警系统
- Python遍历多个列表:ValueError: too many values to unpack (expected 2)
- Spring Cloud教程(十二)加密和解密