pyside控件_左右范围滑动控件
背景:
PySide2 与 qt5 有版本冲突,
# from superqt import QRangeSlider # 得QT6才行
from qtrangeslider import QRangeSlider 也不兼容
有不方便升级 PySide2
所以自实现写一个 完全 PySide2 5.15.2 兼容的双滑块控件,不依赖外部 qtrangeslider
,直接用 QWidget
+ QPainter
自绘两个滑块,支持水平模式、数值范围、信号触发。
效果如下:
实现:
自定义控件
(统一放到新建main_test.py文件中)
from PySide2.QtCore import Qt, QRect, Signal, QPoint
from PySide2.QtGui import QPainter, QColor, QPen, QBrush
from PySide2.QtWidgets import QWidget, QSizePolicyclass QRangeSlider(QWidget):valueChanged = Signal(tuple) # (low, high)def __init__(self, orientation=Qt.Horizontal, parent=None):super().__init__(parent)self.orientation = orientationself._min = 0self._max = 100self._low_value = 20self._high_value = 80self._handle_radius = 8self._bar_height = 4self._active_handle = Noneself.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)self.setMinimumHeight(30)# ------- Public API -------def setRange(self, min_val, max_val):self._min = min_valself._max = max_valself.update()def setValue(self, values):low, high = valuesself._low_value = max(self._min, min(low, self._max))self._high_value = max(self._min, min(high, self._max))self.update()self.valueChanged.emit((self._low_value, self._high_value))def value(self):return (self._low_value, self._high_value)# ------- Qt Events -------def paintEvent(self, event):painter = QPainter(self)painter.setRenderHint(QPainter.Antialiasing)# 计算坐标bar_rect = QRect(self._handle_radius,(self.height() - self._bar_height) // 2,self.width() - self._handle_radius * 2,self._bar_height)# 绘制背景条painter.setPen(Qt.NoPen)painter.setBrush(QColor(200, 200, 200))painter.drawRect(bar_rect)# 绘制选中范围low_x = self._value_to_pos(self._low_value)high_x = self._value_to_pos(self._high_value)selected_rect = QRect(low_x, bar_rect.y(), high_x - low_x, self._bar_height)painter.setBrush(QColor(100, 180, 255))painter.drawRect(selected_rect)# 绘制两个滑块painter.setBrush(QBrush(QColor(255, 100, 100)))painter.drawEllipse(QPoint(low_x, self.height() // 2), self._handle_radius, self._handle_radius)painter.setBrush(QBrush(QColor(100, 100, 255)))painter.drawEllipse(QPoint(high_x, self.height() // 2), self._handle_radius, self._handle_radius)def mousePressEvent(self, event):if event.button() == Qt.LeftButton:low_x = self._value_to_pos(self._low_value)high_x = self._value_to_pos(self._high_value)if abs(event.x() - low_x) < self._handle_radius + 2:self._active_handle = "low"elif abs(event.x() - high_x) < self._handle_radius + 2:self._active_handle = "high"def mouseMoveEvent(self, event):if self._active_handle:new_val = self._pos_to_value(event.x())if self._active_handle == "low":self._low_value = new_valelif self._active_handle == "high":self._high_value = new_valself.update()self.valueChanged.emit((self._low_value, self._high_value))def mouseReleaseEvent(self, event):if self._low_value > self._high_value:# 交换,保证 low <= highself._low_value, self._high_value = self._high_value, self._low_valueself._active_handle = Noneself.update()self.valueChanged.emit((self._low_value, self._high_value))# ------- Utils -------def _value_to_pos(self, value):bar_start = self._handle_radiusbar_end = self.width() - self._handle_radiusratio = (value - self._min) / (self._max - self._min)return int(bar_start + ratio * (bar_end - bar_start))def _pos_to_value(self, pos):bar_start = self._handle_radiusbar_end = self.width() - self._handle_radiusratio = (pos - bar_start) / (bar_end - bar_start)ratio = max(0, min(1, ratio))return int(self._min + ratio * (self._max - self._min))
测试:
在上述同一py文件下,添加:
if __name__ == "__main__":from PySide2.QtWidgets import QApplication, QVBoxLayout, QWidgetimport sysapp = QApplication(sys.argv)win = QWidget()layout = QVBoxLayout(win)slider = QRangeSlider()slider.setRange(0, 100)slider.setValue((20, 80))slider.valueChanged.connect(lambda v: print("当前值:", v))layout.addWidget(slider)win.show()sys.exit(app.exec_())
布局
上述控件定义好后,如何在QT creater中使用呢?
可以采用占位符 + 替换的方式,替换成自己的控件
首先,先在ui中插入一个QWidget, 如下:
<widget class="QWidget" name="range_slider_placeholder" native="true"><property name="geometry"><rect><x>150</x><y>440</y><width>451</width><height>81</height></rect></property></widget>
然后代码替换:
placeholder = self.ui.findChild(QWidget, "range_slider_placeholder")
range_slider = QRangeSliderJ(Qt.Horizontal, self.ui)
range_slider.setRange(0, 100)
range_slider.setValue((20, 80))replace_placeholder_with_widget(placeholder, range_slider)
替换函数的具体实现如下:
def replace_placeholder_with_widget(placeholder: QWidget, new_widget: QWidget):"""将 UI 中的占位符控件替换为新的控件,兼容:- QGridLayout- QHBoxLayout / QVBoxLayout- 无布局的父控件(直接定位到占位符的位置):param placeholder: 原占位符 QWidget:param new_widget: 替换用的新 QWidget"""if placeholder is None or placeholder.parent() is None:raise ValueError("占位符无效或没有父控件")parent = placeholder.parent()layout = parent.layout()if layout is None:# 无布局:直接放到占位符的几何位置new_widget.setParent(parent)new_widget.setGeometry(placeholder.geometry())elif isinstance(layout, QGridLayout):index = layout.indexOf(placeholder)if index != -1:row, col, row_span, col_span = layout.getItemPosition(index)layout.addWidget(new_widget, row, col, row_span, col_span)layout.removeWidget(placeholder)else:layout.addWidget(new_widget)else:# QHBoxLayout / QVBoxLayoutindex = layout.indexOf(placeholder)if index != -1:layout.insertWidget(index, new_widget)layout.removeWidget(placeholder)else:layout.addWidget(new_widget)placeholder.hide()placeholder.deleteLater()new_widget.show()