pyside控件_左右范围滑动控件

发布于:2025-08-13 ⋅ 阅读:(14) ⋅ 点赞:(0)

背景:

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, QSizePolicy


class QRangeSlider(QWidget):
    valueChanged = Signal(tuple)  # (low, high)

    def __init__(self, orientation=Qt.Horizontal, parent=None):
        super().__init__(parent)
        self.orientation = orientation
        self._min = 0
        self._max = 100
        self._low_value = 20
        self._high_value = 80
        self._handle_radius = 8
        self._bar_height = 4
        self._active_handle = None
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.setMinimumHeight(30)

    # ------- Public API -------
    def setRange(self, min_val, max_val):
        self._min = min_val
        self._max = max_val
        self.update()

    def setValue(self, values):
        low, high = values
        self._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_val
            elif self._active_handle == "high":
                self._high_value = new_val
            self.update()
            self.valueChanged.emit((self._low_value, self._high_value))
    def mouseReleaseEvent(self, event):
        if self._low_value > self._high_value:
            # 交换,保证 low <= high
            self._low_value, self._high_value = self._high_value, self._low_value
        self._active_handle = None
        self.update()
        self.valueChanged.emit((self._low_value, self._high_value))

    # ------- Utils -------
    def _value_to_pos(self, value):
        bar_start = self._handle_radius
        bar_end = self.width() - self._handle_radius
        ratio = (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_radius
        bar_end = self.width() - self._handle_radius
        ratio = (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, QWidget
    import sys

    app = 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 / QVBoxLayout
        index = 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()


网站公告

今日签到

点亮在社区的每一天
去签到