当前位置: 首页 > news >正文

告别Qt Slider!用纯C++打造更轻量的TpSlider组件

组件运行效果展示

TpSlider

组件概述

TpSlider组件简介

TpSlider是PiXSingleGUI库中的可拖动滑块组件,支持水平和垂直两种方向的滑动操作。TpSlider.h:13-17该组件提供了完整的用户交互功能,包括鼠标拖拽、数值范围设置和实时反馈机制。

核心特性

  • 双向支持:支持水平(Horizon)和垂直(Vertical)两种滑动方向
  • 精确控制:提供数值范围设置和当前值获取功能TpSlider.h:24-37
  • 事件驱动:基于信号槽机制的松耦合通信TpSlider.h:40-48
  • 高性能渲染:优化的绘制流程,适用于嵌入式环境

核心架构设计

继承关系与基类依赖

TpSlider继承自tpChildWidget基类,这种设计遵循了PiXSingleGUI库的统一架构模式。TpSlider.h:10基类提供了以下核心能力:

  • 事件处理框架:统一的鼠标、键盘事件分发机制
  • 渲染管理:标准化的绘制接口和更新调度
  • 内存管理:自动的生命周期管理和资源释放

数据结构设计

组件采用PIMPL(PointertoImplementation)设计模式,将所有实现细节封装在TpSliderData结构中:TpSlider.cpp:7-25

struct TpSliderData
{int32_t maxValue = 100;int32_t minValue = 0;double value = 0;TpSlider::SliderDirect direct = TpSlider::Horizon;// 顶点矩形区域bool isPressVertex = false;ItpRect vertexRect;ItpPoint pressPoint;// 一个间隔值对应的像素double valuePx = 0;// 是否正在拖拽调整进度,拖拽过程不响应setValue事件bool isDrag = false;
};

数据结构的关键设计:

  • 数值管理:minValuemaxValuevalue三元组管理滑块状态
  • 交互状态:isPressVertexisDrag标志位控制拖拽逻辑
  • 性能优化:valuePx预计算像素比例,vertexRect缓存顶点区域

功能实现详解

数值范围管理

滑块的数值管理通过setRange()setValue()方法实现:TpSlider.cpp:48-68

void TpSlider::setRange(const int32_t &min, const int32_t &max)
{TpSliderData *sliderData = static_cast<TpSliderData *>(data_);sliderData->minValue = min;sliderData->maxValue = max;if (sliderData->maxValue < sliderData->minValue)sliderData->maxValue = sliderData->minValue + 1;if (sliderData->value < sliderData->minValue)sliderData->value = sliderData->minValue;else if (sliderData->value > sliderData->maxValue)sliderData->value = sliderData->maxValue;else{}rangeChanged.emit(sliderData->minValue, sliderData->maxValue);update();
}

边界处理逻辑:

  • 自动修正无效范围(最大值小于最小值)
  • 当前值超出范围时自动调整到边界值
  • 范围变化时触发rangeChanged信号通知

鼠标事件处理机制

按下事件处理

鼠标按下事件的核心是区域检测和状态初始化:TpSlider.cpp:104-129

bool TpSlider::onMousePressEvent(TpMouseEvent *event)
{TpSliderData *sliderData = static_cast<TpSliderData *>(data_);sliderData->isPressVertex = false;sliderData->isDrag = false;if (event->button() != BUTTON_LEFT)return true;ItpPoint mousePoint = event->pos();if (sliderData->vertexRect.contains(mousePoint)){if (sliderData->direct == TpSlider::Horizon)sliderData->valuePx = 1.0 * (sliderData->maxValue - sliderData->minValue) / width();elsesliderData->valuePx = 1.0 * (sliderData->maxValue - sliderData->minValue) / height();sliderData->pressPoint = mousePoint;sliderData->isPressVertex = true;sliderData->isDrag = true;}return true;
}

关键算法:

  • 只响应左键点击且点击在顶点区域内
  • 根据滑块方向预计算像素-数值转换比例
  • 设置拖拽状态标志,防止外部setValue干扰

移动事件处理

拖拽过程中的数值计算是组件的核心算法:TpSlider.cpp:131-183

bool TpSlider::onMouseMoveEvent(TpMouseEvent *event)
{TpSliderData *sliderData = static_cast<TpSliderData *>(data_);if (sliderData->isPressVertex){ItpPoint curMotionPoint = event->pos();int32_t offsetPx = 0;if (sliderData->direct == TpSlider::Horizon){offsetPx = curMotionPoint.x - sliderData->pressPoint.x;}else{offsetPx = sliderData->pressPoint.y - curMotionPoint.y;}// std::cout << " offsetPx  " << offsetPx << std::endl;if (std::abs(offsetPx) >= sliderData->valuePx){int32_t oldValue = sliderData->value;sliderData->value += (offsetPx * sliderData->valuePx);if (sliderData->value > sliderData->maxValue){sliderData->value = sliderData->maxValue;}else if (sliderData->value < sliderData->minValue){sliderData->value = sliderData->minValue;}else{}// std::cout << " sliderData->value  " << sliderData->value << std::endl;int32_t newValue = sliderData->value;if (newValue != oldValue){valueChanged.emit(newValue);}}sliderData->pressPoint = curMotionPoint;update();}return true;
}

算法特点:

  • 方向适配:水平方向计算X轴偏移,垂直方向计算Y轴偏移(注意Y轴反向)
  • 增量更新:基于像素偏移量计算数值变化
  • 防抖处理:只有数值真正改变时才触发valueChanged信号

信号槽通信机制

组件提供两个核心信号用于外部通信:TpSlider.h:41-48

public 
signals:
/// @brief 值变化信号
/// @param int 当前值
declare_signal(valueChanged, int32_t);/// @brief 范围变化信号
/// @param int 当前最小值
/// @param int 当前最大值
declare_signal(rangeChanged, int32_t, int32_t);

PiXSingleGUI的信号槽系统基于模板实现,提供类型安全的事件通信:TpSignalSlot.h:91-130

渲染系统实现

绘制流程

滑块的绘制分为三个层次:背景轨道、进度填充和拖拽顶点。TpSlider.cpp:192-228

TpSliderData *sliderData = static_cast<TpSliderData *>(data_);// tpChildWidget::onPaintEvent(event);
TpShared<TpCssData> curCssData = currentStatusCss();TpCanvas *painter = event->canvas();// 整体高度、宽度;分成4份。进度条1份,顶点2份,浅色顶点4份
uint32_t bgWidth = width();
uint32_t bgHeight = height();
uint32_t bgX = 0;
uint32_t bgY = 0;// 不能用父类绘制,绘制背景色
ItpRect rect = event->rect();
if (objectType() == TP_FLOAT_OBJECT)
{if ((curCssData->backgroundColor() & 0xff) != 0xff){painter->erase();}
}if (sliderData->direct == TpSlider::Horizon)
{bgHeight = height() / 4.0;bgY = (height() - bgHeight) / 2.0;painter->roundedBox(0, bgY, rect.w, bgY + bgHeight, roundCorners(), curCssData->backgroundColor());
}
else
{bgWidth = width() / 4.0;bgX = (width() - bgWidth) / 2.0;painter->roundedBox(bgX, 0, bgX + bgWidth, rect.h, roundCorners(), curCssData->backgroundColor());
}

方向适配渲染

水平方向渲染:TpSlider.cpp:240-272

if (sliderData->direct == TpSlider::Horizon)
{circleRadius = height() / 4.0 * 2.0 / 2.0;valueWidth = valuePercent * width();if (valueWidth != 0)painter->roundedBox(0, bgY, valueWidth, bgY + bgHeight, roundCorners(), curCssData->subColor());int32_t circleX = valueWidth;if (circleX == 0){circleX = circleRadius;}else if (circleX == width()){circleX = width() - circleRadius;}else{}// 绘制淡色圆形顶点painter->filledCircle(circleX, height() / 2.0, height() / 2.0, lightSubColor);// 绘制圆形顶点painter->filledCircle(circleX, height() / 2.0, circleRadius, subColor);// 记录顶点区域sliderData->vertexRect.x = circleX - circleRadius;sliderData->vertexRect.y = height() / 2.0 - circleRadius;sliderData->vertexRect.w = circleRadius * 2;sliderData->vertexRect.h = circleRadius * 2;
}
  • 轨道高度为组件高度的1/4
  • 顶点位置根据数值百分比计算
  • 边界处理确保顶点不超出范围

垂直方向渲染:TpSlider.cpp:274-307

else
{circleRadius = width() / 4.0 * 2.0 / 2.0;valueWidth = valuePercent * height();if (valueWidth != 0)painter->roundedBox(bgX, height() - valueWidth, bgX + bgWidth, height(), roundCorners(), subColor);int32_t circleY = height() - valueWidth;if (circleY == 0){circleY = circleRadius;}else if (circleY == height()){circleY = height() - circleRadius;}else{}// 绘制淡色圆形顶点painter->filledCircle(width() / 2.0, circleY, width() / 2.0, lightSubColor);// 绘制圆形顶点painter->filledCircle(width() / 2.0, circleY, circleRadius, subColor);// 记录顶点区域sliderData->vertexRect.x = width() / 2.0 - circleRadius;sliderData->vertexRect.y = circleY - circleRadius;sliderData->vertexRect.w = circleRadius * 2;sliderData->vertexRect.h = circleRadius * 2;
}
  • 轨道宽度为组件宽度的1/4
  • 从底部向上填充(符合用户直觉)
  • Y坐标计算考虑垂直方向特殊性

样式系统集成

TpSlider组件集成了PiXSingleGUI的样式系统,支持通过CSS配置外观:TpSlider.cpp:197

// tpChildWidget::onPaintEvent(event);
TpShared<TpCssData> curCssData = currentStatusCss();

可配置的样式属性:

  • backgroundColor:轨道背景色
  • subColor:进度填充色和顶点颜色
  • roundCorners:圆角半径设置

使用示例程序

#include "TpApp.h"
#include "TpFixScreen.h"
#include "TpLabel.h"
#include "TpSlider.h"
#include "TpFont.h"int32_t main(int32_t argc, char *argv[])
{TpApp app(argc, argv);TpFixScreen *vScreen = new TpFixScreen();vScreen->setBackGroundColor(_RGBA(128, 128, 128, 255));vScreen->setVisible(true); // vScreen setvisible will be update displayapp.bindVScreen(vScreen);TpLabel *valueText = new TpLabel(vScreen);valueText->setText(TpString::number(50));valueText->setAlign(tinyPiX::AlignCenter);valueText->font()->setFontColor(_RGB(255, 255, 255),_RGB(255, 255, 255));valueText->font()->setFontSize(128);valueText->setWidth(600);valueText->setHeight(400);valueText->move(20, 150);TpSlider *slider = new TpSlider(vScreen);slider->setValue(50);slider->setSize(500, 10);slider->move(20, 20);TpSlider *vSlider = new TpSlider(vScreen);vSlider->setDirection(TpSlider::Vertical);vSlider->setValue(50);vSlider->setSize(10, 500);vSlider->move(650, 20);connect(slider, valueChanged, [=](int32_t value){ valueText->setText(TpString::number(value));vSlider->setValue(value); });connect(vSlider, valueChanged, [=](int32_t value){ valueText->setText(TpString::number(value));slider->setValue(value); });vScreen->update();return app.run();
}

TinyPiXOS开发者联盟

源码级支持 + 真实项目:TinyPiXOS开发者联盟招募中​,国产自主轻量级嵌入式设备桌面操作系统交流社群。
获取开发资料
官网网站
B 站视频

感谢支持和关注,如果对项目感兴趣,请点赞、收藏和转发!


文章转载自:

http://kAtcD7jx.mbmtn.cn
http://rwBYhCcI.mbmtn.cn
http://Hzqsglmn.mbmtn.cn
http://LzTRl2Ri.mbmtn.cn
http://fRYMj6DF.mbmtn.cn
http://XFOmY2OO.mbmtn.cn
http://JbHKZNvO.mbmtn.cn
http://332b4tUm.mbmtn.cn
http://Qn3ZrhdK.mbmtn.cn
http://ZcB6epXZ.mbmtn.cn
http://7U7SVSAW.mbmtn.cn
http://2UCUGTTk.mbmtn.cn
http://hMOaD97H.mbmtn.cn
http://nFWNRIin.mbmtn.cn
http://HIXr5b93.mbmtn.cn
http://7WiU2XsZ.mbmtn.cn
http://sVnZFyCL.mbmtn.cn
http://TQ4nPmSu.mbmtn.cn
http://vQsb4mqJ.mbmtn.cn
http://u6WpLfWG.mbmtn.cn
http://gil9YZjr.mbmtn.cn
http://ygkF6SGQ.mbmtn.cn
http://Nb27uKIU.mbmtn.cn
http://q5TPHD0O.mbmtn.cn
http://Xqp5BPUn.mbmtn.cn
http://3PUrPIeK.mbmtn.cn
http://kECfyxHa.mbmtn.cn
http://Rq2vOWuP.mbmtn.cn
http://63qHohlG.mbmtn.cn
http://ntHYeikO.mbmtn.cn
http://www.dtcms.com/a/369273.html

相关文章:

  • 数字孪生赋能:智能制造如何实现从“经验驱动”到“数据驱动”?
  • 穿越市场迷雾:如何在经济周期中保持理性与长期视角
  • Mac M4环境下基于VMware Fusion虚拟机安装Ubuntu24.04 LTS ARM版
  • Vue基础知识-脚手架开发-使用Axios发送异步请求+代理服务器解决前后端分离项目的跨域问题
  • 苍穹外卖 day03
  • 【学习笔记】解决 JWT 解析报错:Claims claims = JwtUtil.parseJWT(...) Error Code 401(token过期)
  • linux下快捷删除单词、行的命令
  • AI提示词增强丨用EARS语法进行产品原子化拆解
  • 概率论第三讲——多维随机变量及其分布
  • 重大更新Claude更新用户协议把中国列为敌对国家
  • 移植Qt4.8.7到ARM40-A5
  • C++语言编程规范-初始化和类型转换
  • Gartner发布2025年数据安全领域的先锋厂商:GenAI和量子计算时代的数据安全创造性技术、产品和服务
  • 微前端架构:解构前端巨石应用的艺术
  • uniapp开发前端静态视频界面+如何将本地视频转换成网络地址
  • EI会议:第三届大数据、计算智能与应用国际会议(BDCIA 2025)
  • 9.5C++作业
  • 数据库基础知识——聚合函数、分组查询
  • MySQL 综合练习
  • 基于cornerstone3D的dicom影像浏览器 第三章 拖拽seriesItem至displayer上显示第一张dicom
  • 用户眼中的VR自来水厂之旅
  • 数据安全成焦点:基于Hadoop+Spark的信用卡诈骗分析系统实战教程
  • 瑞芯微RV1126目标识别算法Yolov8的部署应用
  • 【深入理解Batch Normalization(1)】原理与作用
  • 【教程】快速入门golang
  • Day21_【机器学习—决策树(2)—ID3树 、C4.5树、CART树】
  • std::complex
  • 深度解读:PSPNet(Pyramid Scene Parsing Network) — 用金字塔池化把“场景理解”装进分割网络
  • 【WRF-Chem】SYNMAP 土地覆盖数据概述及处理(二进制转geotiff)
  • 怎么快速构建一个deep search模型呢