QT入门记录1:自定义一个RangeSlider
前言
项目搭搭叠叠,历时一年半,终于出货,进入维护优化阶段,工作又开始缓松下来,有不少的时间去思考未来。
鉴于过去一年半开发的各种踩坑,老板在年终总结上明确跟我说明,后面开发新的项目GUI不再用AWTK了,和其他人一样用QT开发,预期几个月后可能就会接到QT的项目,因此今年开始计划重拾QT的学习。
虽然前一年基本没有接触QT,但是经过一年的AWTK GUI开发经历,很多相关知识都可以迁移到现在的学习上,因此倒不用担心从0开始,不过QT和AWTK开发仍旧有一些本质的不同,因此本次学习的目的就是弄熟相关的开发流程,记录踩坑,找出不同,并予以记录。
个人认为学习GUI,除了通过源码学习思路和解决方案,最综合的一种实践检验方法就是去做该GUI框架下的自定义控件,因此这次就将以自定义一个范围滑动条来开头(Range Slider)。
实战
在一些区间类的设置场景下经常能看到这样一类的滑动条,和一般的slider不同的是它有两个滑块用来控制。
实现这个控件的主要难点在于两个滑块的业务逻辑,首先是按键状态机的控制,RangeSlider是少见的有多个焦点的控件之一,为了两个滑块都能控制移动,两个滑块都要设置焦点;其次,滑块的正常,按下,聚焦状态都有不同的样式,因此滑块的业务处理会占控件业务很大一部分。
自然而然的想法就是用QCheckButton作为子控件来做滑块的逻辑了(QRadioButton虽然也能做,但是选中有些情况下没法取消,就没用),QCheckButton本身就有正常,按下,聚焦三种状态,最适合用来做滑块,我在AWTK就是这么干的。
不过QT上我一开始并没有直接上子控件来学习,而是用的自定义class+QPainter的形式来做,原因是我当时对QT如何处理子控件并不太熟悉,再者就是AWTK试过了子控件法,我想看看如果用原生方法来做可以达到什么样的水平。
原生绘制法
初级
myrangeslider.h
#ifndef MYRANGESLIDER_H
#define MYRANGESLIDER_H
#include <QWidget>
#include <QRect>
namespace Ui {
class MyRangeSlider;
}
class MyRangeSlider : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor bgColor READ bgColor WRITE setBgColor)
Q_PROPERTY(QColor fgColor READ fgColor WRITE setFgColor)
Q_PROPERTY(QColor draggerColorNormal READ draggerColorNormal WRITE setDraggerColorNormal)
Q_PROPERTY(QColor draggerColorPressed READ draggerColorPressed WRITE setDraggerColorPressed)
Q_PROPERTY(double value1 READ value1 WRITE setValue1)
Q_PROPERTY(double value2 READ value2 WRITE setValue2)
Q_PROPERTY(double valueMin READ valueMin WRITE setValueMin)
Q_PROPERTY(double valueMax READ valueMax WRITE setValueMax)
Q_PROPERTY(int barWidth READ barWidth WRITE setBarWidth)
Q_PROPERTY(int draggerWidth READ draggerWidth WRITE setDraggerWidth)
Q_PROPERTY(int draggerMininumValueDiff READ draggerMininumValueDiff WRITE setDraggerMininumValueDiff)
public:
explicit MyRangeSlider(QWidget *parent = nullptr);
~MyRangeSlider();
void setValue1(double value);
void setValue2(double value);
void setBgColor(const QColor &color);
void setFgColor(const QColor &color);
void setDraggerColorNormal(const QColor &color);
void setDraggerColorPressed(const QColor &color);
void setValueMin(double value);
void setValueMax(double value);
void setBarWidth(int width);
void setDraggerWidth(int width);
void setDraggerMininumValueDiff(int diff);
QColor bgColor() const { return mBgColor; }
QColor fgColor() const { return mFgColor; }
QColor draggerColorNormal() const { return mDraggerColorNormal; }
QColor draggerColorPressed() const { return mDraggerColorPressed; }
double value1() const { return mValue1; }
double value2() const { return mValue2; }
double valueMin() const { return mValueMin; }
double valueMax() const { return mValueMax; }
int barWidth() const { return mBarWidth; }
int draggerWidth() const { return mDraggerWidth; }
int draggerMininumValueDiff() const { return mDraggerMininumValueDiff; }
void paintEvent(QPaintEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
// Settable
QColor mBgColor;
QColor mFgColor;
QColor mDraggerColorNormal;
QColor mDraggerColorPressed;
double mValue1, mValue2, mValueMin, mValueMax;
int mBarWidth;
int mDraggerWidth;
int mDraggerMininumValueDiff;
private:
// Compute
bool mDragger1Pressed;
bool mDragger2Pressed;
QRectF mDraggerRect1;
QRectF mDraggerRect2;
Ui::MyRangeSlider *ui;
};
#endif // MYRANGESLIDER_H
myrangeslider.cpp
#include "myrangeslider.h"
#include "ui_myrangeslider.h"
#include <QPainter>
#include <QDebug>
#include <QMouseEvent>
MyRangeSlider::MyRangeSlider(QWidget *parent) :
QWidget(parent),
ui(new Ui::MyRangeSlider)
{
ui->setupUi(this);
mValueMin = 0;
mValueMax = 100;
mValue1 = 40;
mValue2 = 50;
mBarWidth = 10;
mDraggerWidth = 10;
mFgColor = QColor("#00c2ff");
mBgColor = QColor("#8b8b8b");
mDraggerColorNormal = QColor("#639dff");
mDraggerColorPressed = QColor("#00c2ff");
mDragger1Pressed = false;
mDragger2Pressed = false;
mDraggerMininumValueDiff = 1;
this->setAttribute(Qt::WA_StyledBackground);
}
MyRangeSlider::~MyRangeSlider()
{
delete ui;
}
void MyRangeSlider::setValue1(double value)
{
double value2 = (mValue2 - mDraggerMininumValueDiff > 0) ? mValue2 - mDraggerMininumValueDiff : 0;
value = qBound(static_cast<double>(0), value, value2);
mValue1 = value;
repaint();
}
void MyRangeSlider::setValue2(double value)
{
double value1 = (mValue1 + mDraggerMininumValueDiff <= mValueMax) ? mValue1 + mDraggerMininumValueDiff : mValueMax;
value = qBound(value1, value, mValueMax);
mValue2 = value;
repaint();
}
void MyRangeSlider::setBgColor(const QColor &color)
{
mBgColor = color;
repaint();
}
void MyRangeSlider::setFgColor(const QColor &color)
{
mFgColor = color;
repaint();
}
void MyRangeSlider::setDraggerColorNormal(const QColor &color)
{
mDraggerColorNormal = color;
repaint();
}
void MyRangeSlider::setDraggerColorPressed(const QColor &color)
{
mDraggerColorPressed = color;
repaint();
}
void MyRangeSlider::setValueMin(double value)
{
mValueMin = value;
repaint();
}
void MyRangeSlider::setValueMax(double value)
{
mValueMax = value;
repaint();
}
void MyRangeSlider::setBarWidth(int width)
{
mBarWidth = width;
repaint();
}
void MyRangeSlider::setDraggerWidth(int width)
{
mDraggerWidth = width;
repaint();
}
void MyRangeSlider::setDraggerMininumValueDiff(int diff)
{
mDraggerMininumValueDiff = diff;
repaint();
}
void MyRangeSlider::paintEvent(QPaintEvent *event)
{
// draw bar
QPainter painter(this);
painter.setPen(Qt::NoPen);
painter.setBrush(mBgColor);
QRectF barRect(0, (height() - mBarWidth) / 2.0, width(), mBarWidth);
painter.drawRect(barRect);
double pxValue1 = width() * mValue1 / (mValueMax - mValueMin);
double pxValue2 = width() * mValue2 / (mValueMax - mValueMin);
QRectF dragRect1(pxValue1 - mDraggerWidth / 2.0, 0, mDraggerWidth, height());
QRectF dragRect2(pxValue2 - mDraggerWidth / 2.0, 0, mDraggerWidth, height());
painter.setBrush(mFgColor);
int fgWidth = (dragRect2.x() + dragRect2.width() / 2) - (dragRect1.x() + dragRect1.width() / 2);
QRectF fgRect(dragRect1.x() + dragRect1.width() / 2, barRect.y(), fgWidth, mBarWidth);
painter.drawRect(fgRect);
// draw dragger1, dragger2
painter.setBrush(mDragger1Pressed ? mDraggerColorPressed : mDraggerColorNormal);
painter.drawRect(dragRect1);
painter.setBrush(mDragger2Pressed ? mDraggerColorPressed : mDraggerColorNormal);
painter.drawRect(dragRect2);
mDraggerRect1 = dragRect1;
mDraggerRect2 = dragRect2;
}
void MyRangeSlider::mousePressEvent(QMouseEvent *event){
QPoint clickPos = event->pos();
printf("event press: (%d %d)\r\n", clickPos.x(), clickPos.y());
bool needRepaint = false;
if(mDraggerRect1.contains(clickPos)
&& mDraggerRect2.contains(clickPos)){
if(mValue1 == mValueMin){
mDragger1Pressed = false;
mDragger2Pressed = true;
needRepaint = true;
}
else if(mValue2 == mValueMax){
mDragger1Pressed = true;
mDragger2Pressed = false;
needRepaint = true;
}
else{
// 选择距离中心最近的dragger
QPointF p1 = mDraggerRect1.center();
QPointF p2 = mDraggerRect2.center();
double dist1 = QLineF(p1, clickPos).length();
double dist2 = QLineF(p2, clickPos).length();
if(dist1 > dist2){
mDragger1Pressed = false;
mDragger2Pressed = true;
needRepaint = true;
}
else{
mDragger1Pressed = true;
mDragger2Pressed = false;
needRepaint = true;
}
}
}
else if(mDraggerRect1.contains(clickPos)){
mDragger1Pressed = true;
mDragger2Pressed = false;
needRepaint = true;
}
else if(mDraggerRect2.contains(clickPos)){
mDragger1Pressed = false;
mDragger2Pressed = true;
needRepaint = true;
}
printf("mDragger1Pressed: %d mDragger1Pressed: %d\r\n", mDragger1Pressed, mDragger2Pressed);
if(needRepaint){
repaint();
}
}
void MyRangeSlider::mouseReleaseEvent(QMouseEvent *event){
mDragger1Pressed = false;
mDragger2Pressed = false;
repaint();
}
void MyRangeSlider::mouseMoveEvent(QMouseEvent *event){
QPoint clickPos = event->pos();
double newValue1 = (mValueMax - mValueMin) * clickPos.x() / width();
double newValue2 = (mValueMax - mValueMin) * clickPos.x() / width();
if(mDragger1Pressed){
setValue1(newValue1);
}
else if(mDragger2Pressed){
setValue2(newValue2);
}
}
第一版练手的版本,只考虑了控件的鼠标事件,滑块就是简单绘制矩形,样式只有mDraggerColorNormal和mDraggerColorPressed两个滑块不同状态下背景色的绘制,两个颜色通过设置成qProperty的形式在design上设置,qProperty的作用类似于AWTK的属性注释,被声明的属性经过MOC解析之后在designer上可以显示属性并编辑,这种做法的优点就是能够自定义控件属性和易于理解。
这版功能上已经很完善了,毕竟滑动条绘制和值计算逻辑都比较简单,qProperty也可以接入qss做一些诸如主题切换的场景:
theme_light.qss
MyRangeSlider {
qProperty-draggerColorNormal: #ffffff;
qProperty-draggerColorPressed: #ff0000;
}
theme_dark.qss
MyRangeSlider {
qProperty-draggerColorNormal: #000000;
qProperty-draggerColorPressed: #00ff00;
}
效果如下:

不过还是比较简陋,没有按键控制,滑块样式单一只有背景色且不支持图标,状态只支持normal和pressed两种状态,如果想新增样式/状态的话就又得增加property,且不管样式还是状态,随着变量的增多势必得封装起来。
如果只想增加状态而不想新增property setter和getter,比如新增一个滑块hover时的样式,要怎么办?
踩坑1:伪状态无法重置QSS
这里就踩了一个坑,开始以为QWidget不同伪状态时能够触发不同的样式属性,写成了这样:
MyRangeSlider {
qProperty-draggerColorNormal: #ff0000;
}
MyRangeSlider::hover {
qProperty-draggerColorNormal: #00ff00;
}
结果发现鼠标光标移到滑块上没有作用,于是问AI, 回答如下:
qproperty-xxx 在 Qt 样式表里通常只在 polish 时赋值一次,不会随着 :hover 这种伪状态自动重算。所以你会看到“无状态生效,hover 不生效
好吧,看起来还是得走QT属性系统,好在AI又给出了解决方案:
- QSS 改成属性选择器
MyRangeSlider[hovered="false"] {
qproperty-draggerColorNormal: #ff0000;
}
MyRangeSlider[hovered="true"] {
qproperty-draggerColorNormal: #00ff00;
}
- 在 enterEvent/leaveEvent 切换属性并 repolish
void MyRangeSlider::enterEvent(QEvent *event)
{
setProperty("hovered", true);
style()->unpolish(this);
style()->polish(this);
update();
QWidget::enterEvent(event);
}
void MyRangeSlider::leaveEvent(QEvent *event)
{
setProperty("hovered", false);
style()->unpolish(this);
style()->polish(this);
update();
QWidget::leaveEvent(event);
}
试了下有用,但是感觉太丑了,不符合qss的正规用法,而且每个事件都要重复走一段setProperty()+unpolish()+polish()函数,仍旧不太优雅。
状态样式探索一番后,我决定继续走状态封装+样式封装的思路。
进阶
myrangeslider.h
#ifndef MYRANGESLIDER_H
#define MYRANGESLIDER_H
#include <QWidget>
#include <QRadioButton>
#include <QRect>
#include <QPixmap>
namespace Ui {
class MyRangeSlider;
}
struct MyRangeSliderDraggerStyle {
QColor mDraggerColor;
QPixmap mDraggerIcon;
};
enum MyRangeSliderDraggerState{
kDraggerNormal = 0,
kDraggerPressed,
kDraggerFocused
};
enum MyRangeSliderState {
kRangerSliderNormal = 0,
kRangeSliderDragger1Pressed,
kRangeSliderDragger2Pressed,
kRangeSliderDragger1Focused,
kRangeSliderDragger2Focused
};
class MyRangeSliderDragger {
public:
void setState(MyRangeSliderDraggerState state) { mState = state; }
void setStyle(MyRangeSliderDraggerStyle *pstyle) { mStyle = pstyle; }
void setStateAndStyle(MyRangeSliderDraggerState state, MyRangeSliderDraggerStyle *pstyle) {
mState = state;
mStyle = pstyle;
}
bool pressed(){ return mState == kDraggerPressed || mState == kDraggerFocused; }
MyRangeSliderDraggerStyle *mStyle;
MyRangeSliderDraggerState mState;
QRectF mDraggerRect;
};
class MyRangeSlider : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor bgColor READ bgColor WRITE setBgColor)
Q_PROPERTY(QColor fgColor READ fgColor WRITE setFgColor)
Q_PROPERTY(QColor draggerColorNormal READ draggerColorNormal WRITE setDraggerColorNormal)
Q_PROPERTY(QColor draggerColorPressed READ draggerColorPressed WRITE setDraggerColorPressed)
Q_PROPERTY(QColor draggerColorFocused READ draggerColorFocused WRITE setDraggerColorPressed)
Q_PROPERTY(QPixmap draggerIconNormal READ draggerIconNormal WRITE setDraggerIconNormal)
Q_PROPERTY(QPixmap draggerIconPressed READ draggerIconPressed WRITE setDraggerIconPressed)
Q_PROPERTY(QPixmap draggerIconFocused READ draggerIconFocused WRITE setDraggerIconFocused)
Q_PROPERTY(double value1 READ value1 WRITE setValue1)
Q_PROPERTY(double value2 READ value2 WRITE setValue2)
Q_PROPERTY(double valueMin READ valueMin WRITE setValueMin)
Q_PROPERTY(double valueMax READ valueMax WRITE setValueMax)
Q_PROPERTY(int barWidth READ barWidth WRITE setBarWidth)
Q_PROPERTY(int barHeight READ barHeight WRITE setBarHeight)
Q_PROPERTY(int draggerWidth READ draggerWidth WRITE setDraggerWidth)
Q_PROPERTY(int draggerHeight READ draggerHeight WRITE setDraggerHeight)
Q_PROPERTY(int draggerMininumValueDiff READ draggerMininumValueDiff WRITE setDraggerMininumValueDiff)
Q_PROPERTY(bool draggerUseIcon READ draggerUseIcon WRITE setDraggerUseIcon)
Q_PROPERTY(int sliderStep READ sliderStep WRITE setSliderStep)
public:
explicit MyRangeSlider(QWidget *parent = nullptr);
~MyRangeSlider();
void initDraggerStyle();
void setValue1(double value);
void setValue2(double value);
void setBgColor(const QColor &color);
void setFgColor(const QColor &color);
void setDraggerColorNormal(const QColor &color);
void setDraggerColorPressed(const QColor &color);
void setDraggerColorFocused(const QColor &color);
void setDraggerIconNormal(const QPixmap &img);
void setDraggerIconPressed(const QPixmap &img);
void setDraggerIconFocused(const QPixmap &img);
void setValueMin(double value);
void setValueMax(double value);
void setBarWidth(int width);
void setBarHeight(int height);
void setDraggerWidth(int width);
void setDraggerHeight(int height);
void setDraggerMininumValueDiff(int diff);
void setDraggerUseIcon(bool flag);
void setSliderStep(int step);
QColor bgColor() const { return mBgColor; }
QColor fgColor() const { return mFgColor; }
QColor draggerColorNormal() const { return mDraggerStyleNormal.mDraggerColor; }
QColor draggerColorPressed() const { return mDraggerStylePressed.mDraggerColor; }
QColor draggerColorFocused() const { return mDraggerStyleFocused.mDraggerColor; }
QPixmap draggerIconNormal() const { return mDraggerStyleNormal.mDraggerIcon; }
QPixmap draggerIconPressed() const { return mDraggerStylePressed.mDraggerIcon; }
QPixmap draggerIconFocused() const { return mDraggerStyleFocused.mDraggerIcon; }
double value1() const { return mValue1; }
double value2() const { return mValue2; }
double valueMin() const { return mValueMin; }
double valueMax() const { return mValueMax; }
int barWidth() const { return mBarWidth; }
int barHeight() const { return mBarHeight; }
int draggerWidth() const { return mDraggerWidth; }
int draggerHeight() const { return mDraggerHeight; }
int draggerMininumValueDiff() const { return mDraggerMininumValueDiff; }
int draggerUseIcon() const { return mDraggerUseIcon; }
int sliderStep() const { return mSliderStep; }
void setState(MyRangeSliderState state);
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void enterEvent(QEvent *event) override;
void leaveEvent(QEvent *event) override;
void keyPressEvent(QKeyEvent *e) override;
void focusOutEvent(QFocusEvent* e) override;
void focusInEvent(QFocusEvent* e) override;
// Settable
bool mDraggerUseIcon;
QColor mBgColor;
QColor mFgColor;
double mValue1, mValue2, mValueMin, mValueMax;
int mBarWidth;
int mBarHeight;
int mDraggerWidth;
int mDraggerHeight;
int mDraggerMininumValueDiff;
int mSliderStep;
MyRangeSliderDraggerStyle mDraggerStyleNormal;
MyRangeSliderDraggerStyle mDraggerStylePressed;
MyRangeSliderDraggerStyle mDraggerStyleFocused;
private:
// Compute
MyRangeSliderDragger mDragger1;
MyRangeSliderDragger mDragger2;
MyRangeSliderState mState;
Ui::MyRangeSlider *ui;
};
#endif // MYRANGESLIDER_H
myrangeslider.cpp
#include "myrangeslider.h"
#include "ui_myrangeslider.h"
#include <QPainter>
#include <QDebug>
#include <QMouseEvent>
#include <QStyle>
#include <QAbstractButton>
MyRangeSlider::MyRangeSlider(QWidget *parent) :
QWidget(parent),
ui(new Ui::MyRangeSlider)
{
ui->setupUi(this);
mValueMin = 0;
mValueMax = 100;
mValue1 = 40;
mValue2 = 50;
mBarWidth = 5;
mDraggerWidth = 20;
mDraggerHeight = 20;
mFgColor = QColor("#00c2ff");
mBgColor = QColor("#8b8b8b");
mDraggerMininumValueDiff = 1;
mDraggerUseIcon = false;
mSliderStep = 1;
initDraggerStyle();
setState(kRangerSliderNormal);
this->setAttribute(Qt::WA_StyledBackground);
}
MyRangeSlider::~MyRangeSlider()
{
delete ui;
}
void MyRangeSlider::initDraggerStyle()
{
//normal
mDraggerStyleNormal.mDraggerColor = QColor("#ff0000");
mDraggerStyleNormal.mDraggerIcon = QPixmap(":/images/range_silder_dragger0.png");
//pressed
mDraggerStylePressed.mDraggerColor = QColor("#00ff00");
mDraggerStylePressed.mDraggerIcon = QPixmap(":/images/range_silder_dragger1.png");
//focused
mDraggerStyleFocused.mDraggerColor = QColor("#0000ff");
mDraggerStyleFocused.mDraggerIcon = QPixmap(":/images/range_silder_dragger1.png");
}
void MyRangeSlider::setValue1(double value)
{
double value2 = (mValue2 - mDraggerMininumValueDiff > 0) ? mValue2 - mDraggerMininumValueDiff : 0;
value = qBound(static_cast<double>(0), value, value2);
mValue1 = value;
repaint();
}
void MyRangeSlider::setValue2(double value)
{
double value1 = (mValue1 + mDraggerMininumValueDiff <= mValueMax) ? mValue1 + mDraggerMininumValueDiff : mValueMax;
value = qBound(value1, value, mValueMax);
mValue2 = value;
repaint();
}
void MyRangeSlider::setBgColor(const QColor &color)
{
mBgColor = color;
repaint();
}
void MyRangeSlider::setFgColor(const QColor &color)
{
mFgColor = color;
repaint();
}
void MyRangeSlider::setDraggerColorNormal(const QColor &color)
{
mDraggerStyleNormal.mDraggerColor = color;
repaint();
}
void MyRangeSlider::setDraggerColorPressed(const QColor &color)
{
mDraggerStylePressed.mDraggerColor = color;
repaint();
}
void MyRangeSlider::setDraggerColorFocused(const QColor &color)
{
mDraggerStylePressed.mDraggerColor = color;
repaint();
}
void MyRangeSlider::setDraggerIconNormal(const QPixmap &img)
{
mDraggerStyleNormal.mDraggerIcon = img;
repaint();
}
void MyRangeSlider::setDraggerIconPressed(const QPixmap &img)
{
mDraggerStylePressed.mDraggerIcon = img;
repaint();
}
void MyRangeSlider::setDraggerIconFocused(const QPixmap &img)
{
mDraggerStyleFocused.mDraggerIcon = img;
repaint();
}
void MyRangeSlider::setValueMin(double value)
{
mValueMin = value;
repaint();
}
void MyRangeSlider::setValueMax(double value)
{
mValueMax = value;
repaint();
}
void MyRangeSlider::setBarWidth(int width)
{
mBarWidth = width;
repaint();
}
void MyRangeSlider::setBarHeight(int height)
{
mBarHeight = height;
repaint();
}
void MyRangeSlider::setDraggerWidth(int width)
{
mDraggerWidth = width;
repaint();
}
void MyRangeSlider::setDraggerHeight(int height)
{
mDraggerHeight = height;
repaint();
}
void MyRangeSlider::setDraggerMininumValueDiff(int diff)
{
mDraggerMininumValueDiff = diff;
repaint();
}
void MyRangeSlider::setDraggerUseIcon(bool flag)
{
mDraggerUseIcon = flag;
repaint();
}
void MyRangeSlider::setSliderStep(int step)
{
mSliderStep = step;
}
void MyRangeSlider::setState(MyRangeSliderState state)
{
MyRangeSliderState oldState = mState;
mState = state;
switch(state){
case kRangerSliderNormal:{
mDragger1.setStateAndStyle(kDraggerNormal, &mDraggerStyleNormal);
mDragger2.setStateAndStyle(kDraggerNormal, &mDraggerStyleNormal);
break;
}
case kRangeSliderDragger1Focused:{
mDragger1.setStateAndStyle(kDraggerFocused, &mDraggerStyleFocused);
mDragger2.setStateAndStyle(kDraggerNormal, &mDraggerStyleNormal);
break;
}
case kRangeSliderDragger2Focused:{
mDragger1.setStateAndStyle(kDraggerNormal, &mDraggerStyleNormal);
mDragger2.setStateAndStyle(kDraggerFocused, &mDraggerStyleFocused);
break;
}
case kRangeSliderDragger1Pressed:{
mDragger1.setStateAndStyle(kDraggerPressed, &mDraggerStylePressed);
mDragger2.setStateAndStyle(kDraggerNormal, &mDraggerStyleNormal);
break;
}
case kRangeSliderDragger2Pressed:{
mDragger1.setStateAndStyle(kDraggerNormal, &mDraggerStyleNormal);
mDragger2.setStateAndStyle(kDraggerPressed, &mDraggerStylePressed);
break;
}
}
if(oldState != state){
repaint();
}
}
void MyRangeSlider::paintEvent(QPaintEvent *event)
{
printf("mState: %d\r\n", mState);
// draw bar
QPainter painter(this);
painter.save();
painter.setPen(Qt::NoPen);
painter.setBrush(mBgColor);
int padding = mDraggerWidth;
QRectF barRect(padding, (height() - mBarWidth) / 2.0, width() - padding * 2, mBarWidth);
painter.drawRect(barRect);
double pxValue1 = (barRect.width()) * mValue1 / (mValueMax - mValueMin) + padding / 2;
double pxValue2 = (barRect.width()) * mValue2 / (mValueMax - mValueMin) + padding / 2;
QRectF dragRect1(pxValue1, barRect.y() + barRect.height() / 2 - mDraggerHeight / 2, mDraggerWidth, mDraggerHeight);
QRectF dragRect2(pxValue2, barRect.y() + barRect.height() / 2 - mDraggerHeight / 2, mDraggerWidth, mDraggerHeight);
painter.setBrush(mFgColor);
int fgWidth = (dragRect2.x() + dragRect2.width() / 2) - (dragRect1.x() + dragRect1.width() / 2);
QRectF fgRect(dragRect1.x() + dragRect1.width() / 2, barRect.y(), fgWidth, mBarWidth);
painter.drawRect(fgRect);
painter.restore();
// draw dragger1, dragger2
if(!mDraggerUseIcon){
painter.setBrush(mDragger1.mStyle->mDraggerColor);
painter.drawRect(dragRect1);
painter.setBrush(mDragger2.mStyle->mDraggerColor);
painter.drawRect(dragRect2);
}
else{
int draggerWidth = mDragger1.mStyle->mDraggerIcon.rect().width();
int draggerHeight = mDragger1.mStyle->mDraggerIcon.rect().height();
dragRect1 = QRectF(pxValue1,
barRect.y() - draggerHeight / 2.0 + barRect.height() / 2,
draggerWidth,
draggerHeight
);
dragRect2 = QRectF(pxValue2,
barRect.y() - draggerHeight / 2.0 + barRect.height() / 2,
draggerWidth,
draggerHeight
);
painter.drawPixmap(dragRect1, mDragger1.mStyle->mDraggerIcon, mDragger1.mStyle->mDraggerIcon.rect());
painter.drawPixmap(dragRect2, mDragger2.mStyle->mDraggerIcon, mDragger2.mStyle->mDraggerIcon.rect());
}
mDragger1.mDraggerRect = dragRect1;
mDragger2.mDraggerRect = dragRect2;
}
void MyRangeSlider::mousePressEvent(QMouseEvent *event){
QPoint clickPos = event->pos();
printf("event press: (%d %d)\r\n", clickPos.x(), clickPos.y());
bool needRepaint = false;
QRectF mDraggerRect1 = mDragger1.mDraggerRect;
QRectF mDraggerRect2 = mDragger2.mDraggerRect;
if(mDraggerRect1.contains(clickPos)
&& mDraggerRect2.contains(clickPos)){
if(mValue1 == mValueMin){
setState(kRangeSliderDragger2Pressed);
needRepaint = true;
}
else if(mValue2 == mValueMax){
setState(kRangeSliderDragger1Pressed);
needRepaint = true;
}
else{
// 选择距离中心最近的dragger
QPointF p1 = mDraggerRect1.center();
QPointF p2 = mDraggerRect2.center();
double dist1 = QLineF(p1, clickPos).length();
double dist2 = QLineF(p2, clickPos).length();
if(dist1 > dist2){
setState(kRangeSliderDragger2Pressed);
needRepaint = true;
}
else{
setState(kRangeSliderDragger1Pressed);
needRepaint = true;
}
}
}
else if(mDraggerRect1.contains(clickPos)){
setState(kRangeSliderDragger1Pressed);
needRepaint = true;
}
else if(mDraggerRect2.contains(clickPos)){
setState(kRangeSliderDragger2Pressed);
needRepaint = true;
}
printf("mDragger1Pressed: %d mDragger2Pressed: %d\r\n", mDragger1.pressed(), mDragger2.pressed());
if(needRepaint){
repaint();
}
}
void MyRangeSlider::mouseReleaseEvent(QMouseEvent *event){
if(mDragger1.pressed()){
setState(kRangeSliderDragger1Focused);
}
else if(mDragger2.pressed()){
setState(kRangeSliderDragger2Focused);
}
else{
setState(kRangerSliderNormal);
}
repaint();
}
void MyRangeSlider::mouseMoveEvent(QMouseEvent *event){
QPoint clickPos = event->pos();
double newValue1 = (mValueMax - mValueMin) * clickPos.x() / width();
double newValue2 = (mValueMax - mValueMin) * clickPos.x() / width();
if(mDragger1.pressed()){
setValue1(newValue1);
}
else if(mDragger2.pressed()){
setValue2(newValue2);
}
}
void MyRangeSlider::enterEvent(QEvent *event){
repaint();
}
void MyRangeSlider::leaveEvent(QEvent *event){
repaint();
}
void MyRangeSlider::keyPressEvent(QKeyEvent *e){
QWidget::keyPressEvent(e);
switch (e->key()) {
case Qt::Key_Return:{
if(mState == kRangerSliderNormal){
setState(kRangeSliderDragger1Focused);
}
else if(mState == kRangeSliderDragger1Focused){
setState(kRangeSliderDragger1Pressed);
}
else if(mState == kRangeSliderDragger2Focused){
setState(kRangeSliderDragger2Pressed);
}
break;
}
case Qt::Key_Escape:{
if(mState == kRangeSliderDragger1Pressed){
setState(kRangeSliderDragger1Focused);
}
else if(mState == kRangeSliderDragger2Pressed){
setState(kRangeSliderDragger2Focused);
}
break;
}
case Qt::Key_Up:{
focusPreviousChild();
break;
}
case Qt::Key_Down:
{
focusNextChild();
break;
}
case Qt::Key_Left:
{
if(mState == kRangeSliderDragger2Focused){
setState(kRangeSliderDragger1Focused);
}
else if(mState == kRangeSliderDragger1Focused){
setState(kRangeSliderDragger2Focused);
}
else if(mState == kRangeSliderDragger1Pressed){
setValue1(mValue1 - mSliderStep);
}
else if(mState == kRangeSliderDragger2Pressed){
setValue2(mValue2 - mSliderStep);
}
break;
}
case Qt::Key_Right:
{
if(mState == kRangeSliderDragger1Focused){
setState(kRangeSliderDragger2Focused);
}
else if(mState == kRangeSliderDragger2Focused){
setState(kRangeSliderDragger1Focused);
}
else if(mState == kRangeSliderDragger1Pressed){
setValue1(mValue1 + mSliderStep);
}
else if(mState == kRangeSliderDragger2Pressed){
setValue2(mValue2 + mSliderStep);
}
break;
}
}
}
void MyRangeSlider::focusOutEvent(QFocusEvent* e){
setState(kRangerSliderNormal);
repaint();
}
void MyRangeSlider::focusInEvent(QFocusEvent* e){
setState(kRangerSliderNormal);
repaint();
}
最终的完成版本如上,把滑块脱离出来做成了一个独立的类MyRangeSliderDragger,并把滑块的样式独立建类为MyRangeSliderDraggerStyle,滑块状态建立枚举为MyRangeSliderDraggerState, 主控件也有自己的状态MyRangeSliderState,用于控制两个dragger类的状态,这样设计便于实现按键状态机。
设置滑块正常,聚焦, 按下状态下背景色分别为红,蓝,绿,效果如下:

也可以改为图标,这里就不放了。
到了这里,我对于这个控件的要求就基本完善了,但是实现起来明显繁琐了,样式和状态定义都得自己去做,而且有qProperty覆盖状态x样式导致方法数爆炸的问题,给dragger的三个样式属性draggerIcon, draggerColor新增一个focused的状态就要新增2(属性数)x2(setter及getter)=4个方法,扩展效率很差。
而且,既然已经考虑封装滑块的样式和状态了,QT的那些现成的Widget类封装不能直接拿来用?
山重水复,终于又回来了。
子控件法
myrangeslider.h
#ifndef MYRANGESLIDER_H
#define MYRANGESLIDER_H
#include <QWidget>
#include <QRadioButton>
#include <QCheckBox>
#include <QRect>
#include <QPixmap>
#include <QLabel>
namespace Ui {
class MyRangeSlider;
}
class MyRangeSliderDragger : public QCheckBox{
public:
void setPressed(bool flag) { setDown(flag); }
bool pressed(){ return isDown(); }
};
class MyRangeSlider : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor bgColor READ bgColor WRITE setBgColor)
Q_PROPERTY(QColor fgColor READ fgColor WRITE setFgColor)
Q_PROPERTY(double value1 READ value1 WRITE setValue1)
Q_PROPERTY(double value2 READ value2 WRITE setValue2)
Q_PROPERTY(double valueMin READ valueMin WRITE setValueMin)
Q_PROPERTY(double valueMax READ valueMax WRITE setValueMax)
Q_PROPERTY(int barHeight READ barHeight WRITE setBarHeight)
Q_PROPERTY(int draggerWidth READ draggerWidth WRITE setDraggerWidth)
Q_PROPERTY(int draggerHeight READ draggerHeight WRITE setDraggerHeight)
Q_PROPERTY(int draggerMininumValueDiff READ draggerMininumValueDiff WRITE setDraggerMininumValueDiff)
Q_PROPERTY(bool draggerUseIcon READ draggerUseIcon WRITE setDraggerUseIcon)
Q_PROPERTY(int sliderStep READ sliderStep WRITE setSliderStep)
public:
explicit MyRangeSlider(QWidget *parent = nullptr);
~MyRangeSlider();
void initDraggerStyle();
void initLabelStyle();
void setValue1(double value);
void setValue2(double value);
void setBgColor(const QColor &color);
void setFgColor(const QColor &color);
void setValueMin(double value);
void setValueMax(double value);
void setBarHeight(int height);
void setDraggerWidth(int width);
void setDraggerHeight(int height);
void setDraggerMininumValueDiff(int diff);
void setDraggerUseIcon(bool flag);
void setSliderStep(int step);
void setValueLabelWidth(int width);
void setValueLabelHeight(int height);
void setValueLabelFontPointSize(int size);
QColor bgColor() const { return mBgColor; }
QColor fgColor() const { return mFgColor; }
double value1() const { return mValue1; }
double value2() const { return mValue2; }
double valueMin() const { return mValueMin; }
double valueMax() const { return mValueMax; }
int barHeight() const { return mBarHeight; }
int draggerWidth() const { return mDraggerWidth; }
int draggerHeight() const { return mDraggerHeight; }
int draggerMininumValueDiff() const { return mDraggerMininumValueDiff; }
int draggerUseIcon() const { return mDraggerUseIcon; }
int sliderStep() const { return mSliderStep; }
void clearAllDraggerFocus();
bool eventFilter(QObject *watched, QEvent *event);
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void enterEvent(QEvent *event) override;
void leaveEvent(QEvent *event) override;
void keyPressEvent(QKeyEvent *e) override;
void focusOutEvent(QFocusEvent* e) override;
void focusInEvent(QFocusEvent* e) override;
// Settable
bool mDraggerUseIcon;
QColor mBgColor;
QColor mFgColor;
double mValue1, mValue2, mValueMin, mValueMax;
int mBarHeight;
int mDraggerWidth;
int mDraggerHeight;
int mDraggerMininumValueDiff;
int mSliderStep;
private:
// Compute
QRectF mBarRect;
MyRangeSliderDragger mDragger1;
MyRangeSliderDragger mDragger2;
Ui::MyRangeSlider *ui;
};
#endif // MYRANGESLIDER_H
myrangeslider.cpp
#include "myrangeslider.h"
#include "ui_myrangeslider.h"
#include <QPainter>
#include <QDebug>
#include <QMouseEvent>
#include <QStyle>
#include <QAbstractButton>
#include <QFont>
MyRangeSlider::MyRangeSlider(QWidget *parent) :
QWidget(parent),
ui(new Ui::MyRangeSlider)
{
ui->setupUi(this);
mValueMin = 0;
mValueMax = 100;
mValue1 = 40;
mValue2 = 50;
mDraggerWidth = 30;
mDraggerHeight = 30;
mBarHeight = 5;
mFgColor = QColor("#00c2ff");
mBgColor = QColor("#8b8b8b");
mDraggerMininumValueDiff = 1;
mDraggerUseIcon = false;
mSliderStep = 1;
this->setFocusPolicy(Qt::NoFocus);
initDraggerStyle();
initLabelStyle();
}
MyRangeSlider::~MyRangeSlider()
{
delete ui;
}
void MyRangeSlider::initDraggerStyle()
{
mDragger1.setParent(this);
mDragger1.setObjectName("dragger1");
// 用于禁用子控件的鼠标事件
mDragger1.setAttribute(Qt::WA_TransparentForMouseEvents);
mDragger1.installEventFilter(this);
mDragger2.setParent(this);
mDragger2.setObjectName("dragger2");
// 用于禁用子控件的鼠标事件
mDragger2.setAttribute(Qt::WA_TransparentForMouseEvents);
mDragger2.installEventFilter(this);
}
void MyRangeSlider::setValue1(double value)
{
double value2 = (mValue2 - mDraggerMininumValueDiff > 0) ? mValue2 - mDraggerMininumValueDiff : 0;
value = qBound(static_cast<double>(0), value, value2);
mValue1 = value;
repaint();
}
void MyRangeSlider::setValue2(double value)
{
double value1 = (mValue1 + mDraggerMininumValueDiff <= mValueMax) ? mValue1 + mDraggerMininumValueDiff : mValueMax;
value = qBound(value1, value, mValueMax);
mValue2 = value;
repaint();
}
void MyRangeSlider::setBgColor(const QColor &color)
{
mBgColor = color;
repaint();
}
void MyRangeSlider::setFgColor(const QColor &color)
{
mFgColor = color;
repaint();
}
void MyRangeSlider::setValueMin(double value)
{
mValueMin = value;
repaint();
}
void MyRangeSlider::setValueMax(double value)
{
mValueMax = value;
repaint();
}
void MyRangeSlider::setBarHeight(int height)
{
mBarHeight = height;
repaint();
}
void MyRangeSlider::setDraggerWidth(int width)
{
mDraggerWidth = width;
repaint();
}
void MyRangeSlider::setDraggerHeight(int height)
{
mDraggerHeight = height;
repaint();
}
void MyRangeSlider::setDraggerMininumValueDiff(int diff)
{
mDraggerMininumValueDiff = diff;
repaint();
}
void MyRangeSlider::setDraggerUseIcon(bool flag)
{
mDraggerUseIcon = flag;
repaint();
}
void MyRangeSlider::setSliderStep(int step)
{
mSliderStep = step;
}
void MyRangeSlider::clearAllDraggerFocus()
{
mDragger1.setChecked(false);
mDragger2.setChecked(false);
repaint();
}
bool MyRangeSlider::eventFilter(QObject *watched, QEvent *event)
{
if(event->type() == QEvent::FocusOut){
bool isDragger = (watched == &mDragger1 || watched == &mDragger2);
if(isDragger){
MyRangeSliderDragger *dragger = static_cast<MyRangeSliderDragger*>(watched);
dragger->setChecked(false);
}
}
else if(event->type() == QEvent::KeyPress){
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
switch (ke->key()) {
case Qt::Key_Escape:{
clearAllDraggerFocus();
break;
}
case Qt::Key_Return:{
if(mDragger1.hasFocus()){
mDragger1.setChecked(!mDragger1.isChecked());
}
if(mDragger2.hasFocus()){
mDragger2.setChecked(!mDragger2.isChecked());
}
break;
}
case Qt::Key_Up:{
focusPreviousChild();
break;
}
case Qt::Key_Down:
{
focusNextChild();
break;
}
case Qt::Key_Left:
{
if(mDragger1.hasFocus() && !mDragger1.isChecked()){
focusNextChild();
}
else if(mDragger2.hasFocus() && !mDragger2.isChecked()){
focusPreviousChild();
}
else if(mDragger1.hasFocus() && mDragger1.isChecked()){
setValue1(mValue1 - mSliderStep);
}
else if(mDragger2.hasFocus() && mDragger2.isChecked()){
setValue2(mValue2 - mSliderStep);
}
break;
}
case Qt::Key_Right:
{
if(mDragger1.hasFocus() && !mDragger1.isChecked()){
focusNextChild();
}
else if(mDragger2.hasFocus() && !mDragger2.isChecked()){
focusPreviousChild();
}
if(mDragger1.hasFocus() && mDragger1.isChecked()){
setValue1(mValue1 + mSliderStep);
}
if(mDragger2.hasFocus() && mDragger2.isChecked()){
setValue2(mValue2 + mSliderStep);
}
break;
}
}
return true;
}
else if(event->type() == QEvent::FocusIn){
repaint();
}
return false;
}
void MyRangeSlider::paintEvent(QPaintEvent *event)
{
// draw bar
QPainter painter(this);
painter.save();
painter.setPen(Qt::NoPen);
painter.setBrush(mBgColor);
int padding = mDraggerWidth;
QRectF barRect(padding, (height() - mBarHeight) / 2.0, width() - padding * 2, mBarHeight);
painter.drawRect(barRect);
mBarRect = barRect;
double pxValue1 = (barRect.width()) * mValue1 / (mValueMax - mValueMin) + padding / 2;
double pxValue2 = (barRect.width()) * mValue2 / (mValueMax - mValueMin) + padding / 2;
QRectF dragRect1(pxValue1, barRect.y() + (barRect.height() - mDraggerHeight) / 2, mDraggerWidth, mDraggerHeight);
QRectF dragRect2(pxValue2, barRect.y() + (barRect.height() - mDraggerHeight) / 2, mDraggerWidth, mDraggerHeight);
painter.setBrush(mFgColor);
int fgWidth = (dragRect2.x() + dragRect2.width() / 2) - (dragRect1.x() + dragRect1.width() / 2);
QRectF fgRect(dragRect1.x() + dragRect1.width() / 2, barRect.y(), fgWidth, barRect.height());
painter.drawRect(fgRect);
painter.restore();
mDragger1.setGeometry(pxValue1, barRect.y() + barRect.height() / 2 - mDraggerHeight / 2, mDraggerWidth, mDraggerHeight);
mDragger2.setGeometry(pxValue2, barRect.y() + barRect.height() / 2 - mDraggerHeight / 2, mDraggerWidth, mDraggerHeight);
}
void MyRangeSlider::mousePressEvent(QMouseEvent *event){
QPoint clickPos = event->pos();
printf("event press: (%d %d)\r\n", clickPos.x(), clickPos.y());
bool needRepaint = false;
QRectF mDraggerRect1 = mDragger1.geometry();
QRectF mDraggerRect2 = mDragger2.geometry();
if(mDraggerRect1.contains(clickPos)
&& mDraggerRect2.contains(clickPos)){
if(mValue1 == mValueMin){
mDragger2.setPressed(false);
mDragger1.setPressed(true); mDragger1.setFocus();
needRepaint = true;
}
else if(mValue2 == mValueMax){
mDragger1.setPressed(false);
mDragger2.setPressed(true); mDragger2.setFocus();
needRepaint = true;
}
else{
// 选择距离中心最近的dragger
QPointF p1 = mDraggerRect1.center();
QPointF p2 = mDraggerRect2.center();
double dist1 = QLineF(p1, clickPos).length();
double dist2 = QLineF(p2, clickPos).length();
if(dist1 > dist2){
mDragger1.setPressed(false);
mDragger2.setPressed(true); mDragger2.setFocus();
needRepaint = true;
}
else{
mDragger2.setPressed(false);
mDragger1.setPressed(true); mDragger1.setFocus();
needRepaint = true;
}
}
}
else if(mDraggerRect1.contains(clickPos)){
mDragger2.setPressed(false);
mDragger1.setPressed(true); mDragger1.setFocus();
needRepaint = true;
}
else if(mDraggerRect2.contains(clickPos)){
mDragger1.setPressed(false);
mDragger2.setPressed(true); mDragger2.setFocus();
needRepaint = true;
}
printf("mDragger1Pressed: %d mDragger2Pressed: %d\r\n", mDragger1.pressed(), mDragger2.pressed());
if(needRepaint){
repaint();
}
}
void MyRangeSlider::mouseReleaseEvent(QMouseEvent *event){
mDragger1.setPressed(false);
mDragger2.setPressed(false);
repaint();
}
void MyRangeSlider::mouseMoveEvent(QMouseEvent *event){
QPoint clickPos = event->pos();
double newValue1 = (mValueMax - mValueMin) * (clickPos.x() - mBarRect.x()) / mBarRect.width();
double newValue2 = (mValueMax - mValueMin) * (clickPos.x() - mBarRect.x()) / mBarRect.width();
if(mDragger1.pressed()){
setValue1(newValue1);
}
if(mDragger2.pressed()){
setValue2(newValue2);
}
}
void MyRangeSlider::enterEvent(QEvent *event){
repaint();
}
void MyRangeSlider::leaveEvent(QEvent *event){
repaint();
}
void MyRangeSlider::keyPressEvent(QKeyEvent *e){
}
void MyRangeSlider::focusOutEvent(QFocusEvent* e){
qDebug()<<"Out="<<objectName(); //当失去焦点时输出失去焦点的部件名称
clearAllDraggerFocus();
repaint();
}
//必须调用 update()函数,否则焦点不会被更新
void MyRangeSlider::focusInEvent(QFocusEvent* e){
repaint();
}
最终版本,回到当时AWTK开发的思路,用QCheckButton来做滑块控件,由于QCheckButton自身就有状态和样式相关的API,因此之前的各种style和state封装都可以直接删掉了,整个cpp文件减少了一百多行。
而且设置qss样式也很容易,直接父控件后面指定子控件名称就行了,qss固有的属性正好满足要求,不需要再自定义属性然后搞paintEvent逻辑。
MyRangeSlider QCheckBox::focus{
outline: none;
}
MyRangeSlider QCheckBox::indicator {
width: 20px;
height: 20px;
border: 2px solid transparent;
image: url(:/images/range_silder_dragger0.png);
}
MyRangeSlider QCheckBox::indicator::focus {
width: 20px;
height: 20px;
border: 2px solid #00ffff;
outline: none;
}
MyRangeSlider QCheckBox::indicator::pressed, MyRangeSlider QCheckBox::indicator::checked {
width: 20px;
height: 20px;
image: url(:/images/range_silder_dragger1.png);
}
效果图(这一次加上了图标):

另外补充一段细节,我在两个dragger的样式控制上卡了很久,总结出来,QT给控件做样式有几种方法:QPainter, QPalette, QSS, QStyle, 几种是相互补充配合而不是替代的关系。其中最能深入QT本质的就是自己写控件的Style,继承CommonStyle,但是那个十分繁琐,需要对QWidget的元素绘制(PE, CE, CC三大类元素)以及QStyle继承体系有一定了解,要自己实现drawPrimitve/drawComplexControl, polish/unpolish, pixelMetric等等一系列方法, 本质还是封装QPainter干活,实践成本很高。
我看了下QPushButton和QSlider的paintEvent源码,看得云里雾里,深感复杂度非自己目前功能所能应对,最后只得放弃了事。
踩坑2:事件过滤器
一开始的时候,思路没有转变过来,想当然继续在MyRangeSlider::keyEvent方法上去控制dragger的相关事件:
void MyRangeSlider::keyPressEvent(QKeyEvent *e){
QKeyEvent *ke = static_cast<QKeyEvent *>(e);
switch (ke->key()) {
case Qt::Key_Escape:{
clearAllDraggerFocus();
break;
}
case Qt::Key_Return:{
if(mDragger1.hasFocus()){
mDragger1.setChecked(!mDragger1.isChecked());
}
if(mDragger2.hasFocus()){
mDragger2.setChecked(!mDragger2.isChecked());
}
break;
}
case Qt::Key_Up:{
focusPreviousChild();
break;
}
case Qt::Key_Down:
{
focusNextChild();
break;
}
case Qt::Key_Left:
{
if(mDragger1.hasFocus() && !mDragger1.isChecked()){
focusNextChild();
}
else if(mDragger2.hasFocus() && !mDragger2.isChecked()){
focusPreviousChild();
}
else if(mDragger1.hasFocus() && mDragger1.isChecked()){
setValue1(mValue1 - mSliderStep);
}
else if(mDragger2.hasFocus() && mDragger2.isChecked()){
setValue2(mValue2 - mSliderStep);
}
break;
}
case Qt::Key_Right:
{
if(mDragger1.hasFocus() && !mDragger1.isChecked()){
focusNextChild();
}
else if(mDragger2.hasFocus() && !mDragger2.isChecked()){
focusPreviousChild();
}
if(mDragger1.hasFocus() && mDragger1.isChecked()){
setValue1(mValue1 + mSliderStep);
}
if(mDragger2.hasFocus() && mDragger2.isChecked()){
setValue2(mValue2 + mSliderStep);
}
break;
}
}
}
结果发现:怎么按键按下了滑块之后按左右还是焦点切换?(预期应该移动滑块)
后面检查才发现MyRangeSliderDragger拿到 Left/Right 后,走了它还是自己的QCheckBox默认按键导航逻辑,导致焦点切换。
对于这种情况的一个默认思路就是重载MyRangeSliderDragger的keyEvent事件去自己实现逻辑,但是如果后面子控件一多,重载keyEvent的方法就显得繁琐了,有没有统一集中的方法?
很幸运, QT考虑到了此番场景,提供了一个叫事件过滤器的特性,事件过滤器允许事件在走控件相关处理逻辑时,先走设置事件过滤器的逻辑,再根据事件过滤器的结果来确定继不继续走控件相关处理逻辑。
在MyRangeSlider上重载eventFilter,把KeyEvent的事件迁移过去并指定判断watched对象为dragger时处理,问题解决。
bool MyRangeSlider::eventFilter(QObject *watched, QEvent *event)
{
if(event->type() == QEvent::FocusOut){
bool isDragger = (watched == &mDragger1 || watched == &mDragger2);
if(isDragger){
MyRangeSliderDragger *dragger = static_cast<MyRangeSliderDragger*>(watched);
dragger->setChecked(false);
}
}
else if(event->type() == QEvent::KeyPress){
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
switch (ke->key()) {
case Qt::Key_Escape:{
clearAllDraggerFocus();
break;
}
case Qt::Key_Return:{
if(mDragger1.hasFocus()){
mDragger1.setChecked(!mDragger1.isChecked());
}
if(mDragger2.hasFocus()){
mDragger2.setChecked(!mDragger2.isChecked());
}
break;
}
case Qt::Key_Up:{
focusPreviousChild();
break;
}
case Qt::Key_Down:
{
focusNextChild();
break;
}
case Qt::Key_Left:
{
if(mDragger1.hasFocus() && !mDragger1.isChecked()){
focusNextChild();
}
else if(mDragger2.hasFocus() && !mDragger2.isChecked()){
focusPreviousChild();
}
else if(mDragger1.hasFocus() && mDragger1.isChecked()){
setValue1(mValue1 - mSliderStep);
}
else if(mDragger2.hasFocus() && mDragger2.isChecked()){
setValue2(mValue2 - mSliderStep);
}
break;
}
case Qt::Key_Right:
{
if(mDragger1.hasFocus() && !mDragger1.isChecked()){
focusNextChild();
}
else if(mDragger2.hasFocus() && !mDragger2.isChecked()){
focusPreviousChild();
}
if(mDragger1.hasFocus() && mDragger1.isChecked()){
setValue1(mValue1 + mSliderStep);
}
if(mDragger2.hasFocus() && mDragger2.isChecked()){
setValue2(mValue2 + mSliderStep);
}
break;
}
}
return true; // true表示继续不传递控件给事件
}
else if(event->type() == QEvent::FocusIn){
repaint();
}
return false; // false表示继续传递控件给事件
}
事件过滤器也是QT的一个独特的特性了,一个控件可以安装多个事件过滤器,本质和Single/Slot一样都是基于观察者模式的实现方式:事件过滤器回调类似链表的方式组织,集体观察指定的控件,通过回调结果决定是否把事件继续传递给控件。
事件过滤器函数a------->事件过滤器函数b------->事件过滤器函数c
| | |
| | |
____________________________________________
|
|
V
控件
在AWTK上,没有类似事件过滤器的特性,只能用widget_on在控件上做事后处理,有点遗憾。
踩坑3:代码无法从QSS获取属性设置值
到了这里代码的功能和可扩展性都得到了很大提升,但是还没有做显示值的表框,一开始的想法是直接在代码上获取滑块focus状态下的聚焦框颜色,也就是border的设置,然后提取出颜色给画布绘制,于是这里又发现了一个大坑,上网搜索才知道QT的QSS并没有直接的像JSON那样能够获取属性key和value一类的API,唯一的方式就是通过styleSheet()方法获取设置样式然后正则暴力解析,效率不高,适用场景也很窄(比如一个控件一个qss),类似的能获取样式颜色的方法是QPalette,但是QPalette和QSS居然是两套独立的系统,实在令人无语,估计是qss脱胎于css, 太复杂不好解析的缘故?
这如果是在AWTK上就很好办了,AWTK的控件样式也是属性系统的一部分,而且样式属性类型定死了只有int, str, color等几种形式,要获取相关样式就有专门的API:
style_t* style = widget->astyle;
color_t trans = color_init(0, 0, 0, 0xd0);
color_t bg_color = style_get_color(style, STYLE_ID_BG_COLOR, trans);
color_t border_color = style_get_color(style, STYLE_ID_BORDER_COLOR, trans);
color_t text_color = style_get_color(style, STYLE_ID_TEXT_COLOR, trans);
int32_t font_size = style_get_int(style, STYLE_ID_FONT_SIZE, TK_DEFAULT_FONT_SIZE);
const char* font = style_get_str(style, STYLE_ID_FONT_NAME, NULL);
int32_t wox = style_get_int(style, STYLE_ID_X_OFFSET, 0);
int32_t woy = style_get_int(style, STYLE_ID_Y_OFFSET, 0);
int32_t round_r = style_get_int(style, STYLE_ID_ROUND_RADIUS, 0);
...
样式处理方面,AWTK略胜一筹。
看了下公司几个同事的QT项目,是直接自定义控件内setStyle定死字符串大法上去的,最后我也只好QLabel子控件+setStyleSheet大法了,没办法,到时候全局改样式时IDE能搜索到相关字符就行。
void MyRangeSlider::paintEvent(QPaintEvent *event)
{
...
mValueLabel1.setGeometry(mDragger1.x() + (mDraggerWidth - mValueLabelWidth) / 2,
mDragger1.y() + mDraggerHeight,
mValueLabelWidth,
mValueLabelHeight);
mValueLabel2.setGeometry(mDragger2.x() + (mDraggerWidth - mValueLabelWidth) / 2,
mDragger2.y() + mDraggerHeight,
mValueLabelWidth,
mValueLabelHeight);
mValueLabel1.setText(QString::number(mValue1, 'f', 0));
mValueLabel2.setText(QString::number(mValue2, 'f', 0));
const QString defaultLabelStyle = "QLabel{border: 2px solid #000000;}";
const QString focusedLabelStyle = "QLabel{border: 2px solid #00ffff;}";
mValueLabel1.setStyleSheet(mDragger1.hasFocus() ? focusedLabelStyle : defaultLabelStyle);
mValueLabel2.setStyleSheet(mDragger2.hasFocus() ? focusedLabelStyle : defaultLabelStyle);
}
综上,这个控件迭代三次,踩了3个坑,这些坑如果我没有动手去综合实践,就不会知道,就算有AI能够立刻解答,但是由于对QT框架本身的不熟悉,可能我会在当中卡几个小时才会发现问题的本源在哪,这也是我写此篇学习记录的意义。
参考
Cursor GPT-5.3 Codex [AI]
Qt5.10 GUI完全参考手册

浙公网安备 33010602011771号