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

python GUI之实现一个自定义的范围滑块控件:QRangeSlider

在图形用户界面(GUI)开发中,滑块控件是一种常用于选择数值范围的交互元素。然而,很多时候默认的滑块控件无法满足复杂的交互需求,例如同时选择一个范围的起始值和结束值。为此,实现了一个自定义的范围滑块控件——QRangeSlider,它允许用户通过拖动两个滑块来选择一个数值范围,并且支持动态显示当前值。

文章目录

  • 1. 功能概述
  • 2. 控件设计
    • 2.1 主要属性
    • 2.2 信号
    • 2.3 方法
  • 3. 核心代码解析
    • 3.1 绘制轨道和滑块
    • 3.2 滑块拖动逻辑
    • 3.3 值与位置的转换
  • 4. 使用示例
  • 5. 总结

1. 功能概述

QRangeSlider 是一个基于 PyQt5或PySide6 的自定义控件,它具有以下功能:

  • 双滑块选择范围:用户可以通过拖动两个滑块来分别设置范围的起始值和结束值。
  • 动态值显示:当滑块被拖动时,会动态显示当前滑块的值。
  • 限制滑块范围:起始值不能超过结束值,结束值不能小于起始值,并且滑块不能移出轨道。
  • 自定义外观:通过 PyQt 的绘图机制,可以轻松定制滑块和轨道的样式。

2. 控件设计

2.1 主要属性

  • start_valueend_value:分别表示范围的起始值和结束值。
  • min_valuemax_value:分别表示范围的最小值和最大值
  • total_range:表示整个滑块轨道的总范围,默认为 100。
  • handle_radius:滑块的半径,用于定义滑块的大小。
  • is_dragging:用于标记当前是否正在拖动滑块,以及拖动的是哪个滑块(startend)。

2.2 信号

  • startValueChanged:当起始值发生变化时发出的信号。
  • endValueChanged:当结束值发生变化时发出的信号。

2.3 方法

  • setRange(start, end, total_range=100):设置范围的起始值、结束值和总范围。
  • paintEvent(event):重写绘图事件,用于绘制轨道和滑块。
  • _value_to_position(value)_position_to_value(x):用于将值与像素位置相互转换。
  • mousePressEvent(event)mouseMoveEvent(event)mouseReleaseEvent(event):处理鼠标事件,实现滑块的拖动功能。
  • show_value_label(pos, value):动态显示滑块的值。

3. 核心代码解析

3.1 绘制轨道和滑块

def paintEvent(self, event):painter = QPainter(self)painter.setRenderHint(QPainter.Antialiasing)# 绘制轨道painter.setPen(QPen(Qt.gray, 1))painter.drawLine(self.handle_radius, self.height() // 2, self.width() - self.handle_radius, self.height() // 2)# 绘制滑块painter.setBrush(QBrush(QColor("#2AB8E5")))painter.setPen(QPen(Qt.white, 0.5))# 计算滑块位置start_x = self._value_to_position(self.start_value)end_x = self._value_to_position(self.end_value)# 绘制圆形滑块painter.drawEllipse(start_x - self.handle_radius, self.height() // 2 - self.handle_radius,self.handle_radius * 2, self.handle_radius * 2)painter.drawEllipse(end_x - self.handle_radius, self.height() // 2 - self.handle_radius,self.handle_radius * 2, self.handle_radius * 2)

paintEvent 方法中,我们使用 QPainter 绘制了轨道和滑块。轨道是一条水平线,滑块是两个圆形,分别表示起始值和结束值。

3.2 滑块拖动逻辑

    def mouseMoveEvent(self, event):if self.is_dragging == "start":new_value = self._position_to_value(event.position().toPoint().x())# 限制 start_value 不能超过 end_value,且不能移出轨道new_value = max(self.min_value, min(new_value, self.end_value))if new_value != self.start_value:self.start_value = new_valueself.update()self.startValueChanged.emit(self.start_value)self.show_value_label(event.position().toPoint(), self.start_value)  # 显示值elif self.is_dragging == "end":new_value = self._position_to_value(event.position().toPoint().x())# 限制 end_value 不能小于 start_value,且不能移出轨道new_value = max(self.start_value, min(new_value, self.max_value))if new_value != self.end_value:self.end_value = new_valueself.update()self.endValueChanged.emit(self.end_value)self.show_value_label(event.position().toPoint(), self.end_value)  # 显示值

mouseMoveEvent 方法中,我们根据鼠标的位置计算新的滑块值,并更新滑块的位置。同时,我们通过信号通知外部值的变化,并调用 show_value_label 方法动态显示当前值。

3.3 值与位置的转换

def _value_to_position(self, value):"""将值转换为像素位置"""return ((value - (-20)) / self.total_range) * (self.width() - 2 * self.handle_radius) + self.handle_radiusdef _position_to_value(self, x):"""将像素位置转换为值"""return int(((x - self.handle_radius) / (self.width() - 2 * self.handle_radius)) * self.total_range) - 20

这两个方法用于将滑块的值与像素位置相互转换,确保滑块的值与位置之间的映射关系是正确的。

4. 使用示例

简单的使用示例:

import sys
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout
from PySide6.QtCore import Qt, Signal, QRect, QPoint
from PySide6.QtGui import QPainter, QPen, QBrush,QColorclass QRangeSlider(QWidget):startValueChanged = Signal(int)endValueChanged = Signal(int)def __init__(self, parent=None):super(QRangeSlider, self).__init__(parent)self.setMinimumSize(200, 30)  # 设置最小尺寸self.start_value = 20self.end_value = 80self.min_value = -20self.max_value = 150self.total_range = 170self.handle_radius = 10  # 滑块的半径self.is_dragging = None  # 当前拖动的滑块# 创建 QLabel 用于显示值self.label = QLabel(self)self.label.setStyleSheet("color: white; font-weight: bold; background-color: rgba(0, 0, 0, 0.5); padding: 2px;")self.label.hide()  # 初始隐藏def setRange(self, start, end, total_range=100):self.start_value = startself.end_value = endself.total_range = total_rangeself.update()  # 更新绘制self.startValueChanged.emit(self.start_value)self.endValueChanged.emit(self.end_value)def paintEvent(self, event):painter = QPainter(self)painter.setRenderHint(QPainter.Antialiasing)# 绘制轨道painter.setPen(QPen(Qt.gray, 1))painter.drawLine(self.handle_radius, self.height() // 2, self.width() - self.handle_radius, self.height() // 2)# 绘制滑块painter.setBrush(QBrush(QColor("#2AB8E5")))painter.setPen(QPen(Qt.white, 0.5))# 计算滑块位置start_x = self._value_to_position(self.start_value)end_x = self._value_to_position(self.end_value)# 绘制圆形滑块painter.drawEllipse(start_x - self.handle_radius, self.height() // 2 - self.handle_radius,self.handle_radius * 2, self.handle_radius * 2)painter.drawEllipse(end_x - self.handle_radius, self.height() // 2 - self.handle_radius,self.handle_radius * 2, self.handle_radius * 2)def _value_to_position(self, value):"""将值转换为像素位置"""return ((value - (-20)) / self.total_range) * (self.width() - 2 * self.handle_radius) + self.handle_radiusdef _position_to_value(self, x):"""将像素位置转换为值"""return int(((x - self.handle_radius) / (self.width() - 2 * self.handle_radius)) * self.total_range) - 20def mousePressEvent(self, event):if event.button() == Qt.LeftButton:start_x = self._value_to_position(self.start_value)end_x = self._value_to_position(self.end_value)# 判断点击的是哪个滑块if abs(event.position().toPoint().x() - start_x) < self.handle_radius:self.is_dragging = "start"elif abs(event.position().toPoint().x() - end_x) < self.handle_radius:self.is_dragging = "end"else:self.is_dragging = Nonedef mouseMoveEvent(self, event):if self.is_dragging == "start":new_value = self._position_to_value(event.position().toPoint().x())# 限制 start_value 不能超过 end_value,且不能移出轨道new_value = max(self.min_value, min(new_value, self.end_value))if new_value != self.start_value:self.start_value = new_valueself.update()self.startValueChanged.emit(self.start_value)self.show_value_label(event.position().toPoint(), self.start_value)  # 显示值elif self.is_dragging == "end":new_value = self._position_to_value(event.position().toPoint().x())# 限制 end_value 不能小于 start_value,且不能移出轨道new_value = max(self.start_value, min(new_value, self.max_value))if new_value != self.end_value:self.end_value = new_valueself.update()self.endValueChanged.emit(self.end_value)self.show_value_label(event.position().toPoint(), self.end_value)  # 显示值def mouseReleaseEvent(self, event):self.is_dragging = Noneself.label.hide()  # 隐藏值标签def show_value_label(self, pos, value):"""显示值标签,跟随鼠标位置,但 Y 轴固定在滑块中心"""self.label.setText(str(value))self.label.adjustSize()  # 调整标签大小以适应内容# 设置标签位置:X 轴跟随鼠标,Y 轴固定在滑块中心label_x = pos.x() + self.handle_radiuslabel_y = (self.height() - self.label.height()) // 2  # 固定在滑块的垂直中心# 边界检查:确保 label 不超出控件的右边界label_x = min(label_x, self.width() - self.label.width())  # 留出一些间距self.label.move(label_x, label_y)self.label.show()class DemoApp(QWidget):def __init__(self):super().__init__()self.initUI()def initUI(self):layout = QVBoxLayout(self)self.range_slider = QRangeSlider(self)self.range_slider.setRange(20, 80, 170)self.range_slider.startValueChanged.connect(self.on_start_value_changed)self.range_slider.endValueChanged.connect(self.on_end_value_changed)layout.addWidget(self.range_slider)self.setWindowTitle("QRangeSlider Demo")def on_start_value_changed(self, value):print(f"Start value changed: {value}")def on_end_value_changed(self, value):print(f"End value changed: {value}")if __name__ == "__main__":app = QApplication([])demo = DemoApp()demo.show()app.exec()

在这个示例中,创建了一个 QRangeSlider 控件,并设置了初始范围。当滑块的值发生变化时,会通过信号通知到外部,并打印当前值。

5. 总结

QRangeSlider 是一个功能强大且易于使用的自定义范围滑块控件。它通过 PySide6 的绘图机制和事件处理机制实现了双滑块选择范围的功能,并支持动态显示当前值。可以根据自己的需求进一步扩展和定制这个控件,例如添加更多的样式选项或支持更多的交互功能。

相关文章:

python GUI之实现一个自定义的范围滑块控件:QRangeSlider

在图形用户界面&#xff08;GUI&#xff09;开发中&#xff0c;滑块控件是一种常用于选择数值范围的交互元素。然而&#xff0c;很多时候默认的滑块控件无法满足复杂的交互需求&#xff0c;例如同时选择一个范围的起始值和结束值。为此&#xff0c;实现了一个自定义的范围滑块控…...

测试用例总结

一、通用测试用例八要素   1、用例编号&#xff1b;    2、测试项目&#xff1b;   3、测试标题&#xff1b; 4、重要级别&#xff1b;    5、预置条件&#xff1b;    6、测试输入&#xff1b;    7、操作步骤&#xff1b;    8、预期输出 二、具体分析通…...

深度学习之图像学习知识点

数据增广&#xff1a; 数据增广是深度学习中常用的技巧之一&#xff0c;主要用于增加训练数据集&#xff0c;让数据集尽可能的多样化&#xff0c;使得训练的模型具有更强的泛化能力&#xff0c;目前数据增广主要包括&#xff1a;水平/垂直翻转&#xff0c;旋转&#xff0c;缩放…...

vulnhub靶场之【digitalworld.local系列】的development靶机

前言 靶机&#xff1a;digitalworld.local-devt-improved&#xff0c;IP地址为192.168.10.10 攻击&#xff1a;kali&#xff0c;IP地址为192.168.10.6 kali采用VMware虚拟机&#xff0c;靶机选择使用VMware打开文件&#xff0c;都选择桥接网络 这里官方给的有两种方式&…...

C语言---猜数字游戏

猜数字游戏代码 #include <stdio.h> #include <time.h> #include <stdlib.h>void meun() {printf("**********************\n");printf("******* 1.play *******\n");printf("******* 0.quit *******\n");printf("*****…...

C#中泛型的协变和逆变

协变&#xff1a; 在泛型接口中&#xff0c;使用out关键字可以声明协变。这意味着接口的泛型参数只能作为返回类型出现&#xff0c;而不能作为方法的参数类型。 示例&#xff1a;泛型接口中的协变 假设我们有一个基类Animal和一个派生类Dog&#xff1a; csharp复制 public…...

Select 下拉菜单选项分组

使用<select>元素创建下拉菜单&#xff0c;并使用 <optgroup> 元素对选项进行分组。<optgroup> 元素允许你将相关的 <option> 元素分组在一起&#xff0c;并为每个分组添加一个标签。 <form action"#" method"post"><la…...

文件上传漏洞详细利用流程

一、了解基本术语 1、后门 像房子一样&#xff0c;前门后门都可以进出房子&#xff0c;而较之前门&#xff0c;后门更具有隐蔽性。电脑技术中的后门是抽象概念&#xff0c;意指隐蔽性高或不常用的&#xff0c;区别于常规操作所使用的一种出入口。现金网络后门形形色色&#x…...

蓝桥与力扣刷题(蓝桥 旋转)

题目&#xff1a;图片旋转是对图片最简单的处理方式之一&#xff0c;在本题中&#xff0c;你需要对图片顺时针旋转 90 度。 我们用一个 nm的二维数组来表示一个图片&#xff0c;例如下面给出一个 34 的 图片的例子&#xff1a; 1 3 5 7 9 8 7 6 3 5 9 7 这个图片顺时针旋转…...

transformer架构解析{掩码,(自)注意力机制,多头(自)注意力机制}(含代码)-3

目录 前言 掩码张量 什么是掩码张量 掩码张量的作用 生成掩码张量实现 注意力机制 学习目标 注意力计算规则 注意力和自注意力 注意力机制 注意力机制计算规则的代码实现 多头注意力机制 学习目标 什么是多头注意力机制 多头注意力计算机制的作用 多头注意力机…...

使用DiskGenius工具来实现物理机多硬盘虚拟化迁移

使用DiskGenius工具来实现物理机多硬盘虚拟化迁移 概述准备工作注意事项实操过程记录1、Win7虚拟机&#xff0c;安装有两个硬盘&#xff08;硬盘0和硬盘1&#xff09;&#xff0c;各分了一个区&#xff0c;磁盘2是一块未使用的磁盘2、运行DiskGenius程序&#xff0c;记录现有各…...

iOS安全和逆向系列教程 第5篇 iOS基础开发知识速览 - 理解你要逆向的目标

iOS安全和逆向系列教程 第5篇 iOS基础开发知识速览 - 理解你要逆向的目标 正如上一篇文章结尾所预告的&#xff0c;在完成环境搭建后&#xff0c;我们需要了解iOS开发的基础知识。这不是要求你成为一名iOS开发者&#xff0c;而是为了让你在逆向分析过程中能够理解应用的代码结…...

计算机常用单词

文章目录 计算机单词1-100101-200201-300301-400401-500501-600601-700701-800801-900901-10001001-11001101-12001201-13001301-14001401-15001501-16001601-1695 计算机单词 参考 1-100 1. file [英faɪl 美faɪl] n. 文件&#xff1b;v. 保存文件 2. command [英kəˈmɑ…...

TS的接口 泛型 自定义类型 在接口中定义一个非必须的属性

TS的接口 泛型 自定义类型 接口 新建一个ts文件&#xff0c;在里面定义一个接口 export interface PersonInter{id:string,name:string,age:number }在vue文件中引入这个ts文件 <script lang"ts" setup name"Person">import {type PersonInter} …...

76.读取计时器运行时间 C#例子 WPF例子

TimerManager&#xff1a;一个增强的定时器类&#xff0c;带时间管理功能 在使用定时器时&#xff0c;我们常常需要知道定时器的运行状态&#xff0c;比如它已经运行了多久&#xff0c;或者还剩下多少时间。然而&#xff0c;.NET 的 System.Timers.Timer 类本身并没有直接提供…...

React封装通用Table组件,支持搜索(多条件)、筛选、自动序号、数据量统计等功能。未采用二次封装调整灵活,包含使用文档

封装通用组件 一、封装思想二、react代码三、css代码四、实现效果五、使用文档 BasicTableModal 表格模态框组件1.组件简介2.功能特点3.使用方法基础用法宽度控制示例带筛选功能搜索功能示例自定义单元格渲染 4.API 说明PropsColumn 配置项Filter 配置项 5.注意事项 一、封装思…...

【JavaEE】-- 多线程(初阶)4

文章目录 8.多线程案例8.1 单例模式8.1.1 饿汉模式8.1.2 懒汉模式 8.2 阻塞队列8.2.1 什么是阻塞队列8.2.2 生产者消费者模型8.2.3 标准库中的阻塞队列8.2.4 阻塞队列的应用场景8.2.4.1 消息队列 8.2.5 异步操作8.2.5 自定义实现阻塞队列8.2.6 阻塞队列--生产者消费者模型 8.3 …...

WP 高级摘要插件:助力 WordPress 文章摘要精准自定义显示

wordpress插件介绍 “WP高级摘要插件”功能丰富&#xff0c;它允许用户在WordPress后台自定义文章摘要。 可设置摘要长度&#xff0c;灵活调整展示字数&#xff1b;设定摘要最后的显示字符&#xff0c; 如常用的省略号等以提示内容未完整展示&#xff1b;指定允许在摘要中显示…...

论文阅读 EEG-Inception

EEG-Inception: A Novel Deep Convolutional Neural Network for Assistive ERP-Based Brain-Computer Interfaces EEG-Inception是第一个集成Inception模块进行ERP检测的模型&#xff0c;它有效地结合了轻型架构中的其他结构&#xff0c;提高了我们方法的性能。 本研究的主要目…...

FFmpeg入门:最简单的音频播放器

FFmpeg入门&#xff1a;最简单的音频播放器 欢迎大家来到FFmpeg入门的第二章&#xff0c;今天只做一个最简单的FFmpeg音频播放器&#xff1b;同样&#xff0c;话不多说&#xff0c;先上流程图 流程图 以上流程和视频播放器的解码过程基本上是一致的&#xff1b; 不同点在于 S…...

物联网感应层数据采集器实现协议转换 数据格式化

数据采集器的核心功能实现涉及多个技术层面的协同工作,以下是各模块的详细实现解析: 协议转换实现 协议解析引擎:采用插件式架构,例如: P r o t o c o l P a r...

基于Linux系统的物联网智能终端

背景 产品研发和项目研发有什么区别&#xff1f;一个令人发指的问题&#xff0c;刚开始工作时项目开发居多&#xff0c;认为项目开发和产品开发区别不大&#xff0c;待后来随着自身能力的提升&#xff0c;逐步感到要开发一个好产品还是比较难的&#xff0c;我认为项目开发的目的…...

8.1.STM32_OLED

4.STM32_OLED 跟着江协科大的视频&#xff0c;无法点亮OLED屏幕解决办法 每个人使用的0.96寸OLED屏幕信号不一样&#xff0c;存在很多兼容性问题 归根结底就是驱动的问题&#xff01; 本人的OLED是SSD1306,在淘宝店铺找了驱动文件后成功点亮&#xff0c;示例见文末 请针对自…...

Netty笔记9:粘包半包

Netty笔记1&#xff1a;线程模型 Netty笔记2&#xff1a;零拷贝 Netty笔记3&#xff1a;NIO编程 Netty笔记4&#xff1a;Epoll Netty笔记5&#xff1a;Netty开发实例 Netty笔记6&#xff1a;Netty组件 Netty笔记7&#xff1a;ChannelPromise通知处理 Netty笔记8&#xf…...

【算法方法总结·三】滑动窗口的一些技巧和注意事项

【算法方法总结三】滑动窗口的一些技巧和注意事项 【算法方法总结一】二分法的一些技巧和注意事项【算法方法总结二】双指针的一些技巧和注意事项【算法方法总结三】滑动窗口的一些技巧和注意事项 【滑动窗口】 数组的和 随着 右边指针 移动一定是 非递减 的&#xff0c;就是 …...

LabVIEW虚拟弗兰克赫兹实验仪

随着信息技术的飞速发展&#xff0c;虚拟仿真技术已经成为教学和研究中不可或缺的工具。开发了一种基于LabVIEW平台开发的虚拟弗兰克赫兹实验仪&#xff0c;该系统不仅能模拟实验操作&#xff0c;还能实时绘制数据图形&#xff0c;极大地丰富了物理实验的教学内容和方式。 ​ …...

spring boot + vue 搭建环境

参考文档&#xff1a;https://blog.csdn.net/weixin_44215249/article/details/117376417?fromshareblogdetail&sharetypeblogdetail&sharerId117376417&sharereferPC&sharesourceqxpapt&sharefromfrom_link. spring boot vue 搭建环境 一、浏览器二、jd…...

清华团队提出HistoCell,从组织学图像推断超分辨率细胞空间分布助力癌症研究|顶刊精析·25-03-02

小罗碎碎念 今天和大家分享一篇2025-02-21发表于nature communications的文章&#xff0c;内容涉及病理空转单细胞。 从组织学图像推断细胞空间分布对癌症研究意义重大&#xff0c;但现有方法存在标注工作量大、分辨率或特征挖掘不足等局限。研究旨在开发一种高效准确的方法。 …...

分布式锁—2.Redisson的可重入锁一

大纲 1.Redisson可重入锁RedissonLock概述 2.可重入锁源码之创建RedissonClient实例 3.可重入锁源码之lua脚本加锁逻辑 4.可重入锁源码之WatchDog维持加锁逻辑 5.可重入锁源码之可重入加锁逻辑 6.可重入锁源码之锁的互斥阻塞逻辑 7.可重入锁源码之释放锁逻辑 8.可重入锁…...

html+js 轮播图

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>轮播图示例</title><style>/* 基本样式…...