我目前需要一个范围滑块(我可以设置最小值和最大值的滑块)。我发现了两个相关的问题Range slider in Qt (two handles in a QSlider)和Why RangeSlider is available in QtQuick and not as standard Widget,但它们都不是用python3编写的,而且我对C ++不是很熟悉。
我找到了这个完美的github工具https://github.com/rsgalloway/qrangeslider,但遗憾的是它是为PyQt4编写的,我使用的是PyQt5。
我打算用PyQt5绑定重新格式化这个github源代码,但在此之前我想知道是否有人之前已经这样做了所以我可以节省时间? 或者,如果有人有不同的解决方案,我愿意接受建议。
答案 0 :(得分:6)
下面是QRangeSlider widget的PyQt5端口。为了简洁起见,我删除了所有注释,doc-strings,assert语句等。它似乎对Python 2和Python 3都没用,但我还没有真正测试过它。
<强> qrangeslider.py 强>:
import sys, os
from PyQt5 import QtCore, QtGui, QtWidgets
__all__ = ['QRangeSlider']
DEFAULT_CSS = """
QRangeSlider * {
border: 0px;
padding: 0px;
}
QRangeSlider #Head {
background: #222;
}
QRangeSlider #Span {
background: #393;
}
QRangeSlider #Span:active {
background: #282;
}
QRangeSlider #Tail {
background: #222;
}
QRangeSlider > QSplitter::handle {
background: #393;
}
QRangeSlider > QSplitter::handle:vertical {
height: 4px;
}
QRangeSlider > QSplitter::handle:pressed {
background: #ca5;
}
"""
def scale(val, src, dst):
return int(((val - src[0]) / float(src[1]-src[0])) * (dst[1]-dst[0]) + dst[0])
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("QRangeSlider")
Form.resize(300, 30)
Form.setStyleSheet(DEFAULT_CSS)
self.gridLayout = QtWidgets.QGridLayout(Form)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setSpacing(0)
self.gridLayout.setObjectName("gridLayout")
self._splitter = QtWidgets.QSplitter(Form)
self._splitter.setMinimumSize(QtCore.QSize(0, 0))
self._splitter.setMaximumSize(QtCore.QSize(16777215, 16777215))
self._splitter.setOrientation(QtCore.Qt.Horizontal)
self._splitter.setObjectName("splitter")
self._head = QtWidgets.QGroupBox(self._splitter)
self._head.setTitle("")
self._head.setObjectName("Head")
self._handle = QtWidgets.QGroupBox(self._splitter)
self._handle.setTitle("")
self._handle.setObjectName("Span")
self._tail = QtWidgets.QGroupBox(self._splitter)
self._tail.setTitle("")
self._tail.setObjectName("Tail")
self.gridLayout.addWidget(self._splitter, 0, 0, 1, 1)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("QRangeSlider", "QRangeSlider"))
class Element(QtWidgets.QGroupBox):
def __init__(self, parent, main):
super(Element, self).__init__(parent)
self.main = main
def setStyleSheet(self, style):
self.parent().setStyleSheet(style)
def textColor(self):
return getattr(self, '__textColor', QtGui.QColor(125, 125, 125))
def setTextColor(self, color):
if type(color) == tuple and len(color) == 3:
color = QtGui.QColor(color[0], color[1], color[2])
elif type(color) == int:
color = QtGui.QColor(color, color, color)
setattr(self, '__textColor', color)
def paintEvent(self, event):
qp = QtGui.QPainter()
qp.begin(self)
if self.main.drawValues():
self.drawText(event, qp)
qp.end()
class Head(Element):
def __init__(self, parent, main):
super(Head, self).__init__(parent, main)
def drawText(self, event, qp):
qp.setPen(self.textColor())
qp.setFont(QtGui.QFont('Arial', 10))
qp.drawText(event.rect(), QtCore.Qt.AlignLeft, str(self.main.min()))
class Tail(Element):
def __init__(self, parent, main):
super(Tail, self).__init__(parent, main)
def drawText(self, event, qp):
qp.setPen(self.textColor())
qp.setFont(QtGui.QFont('Arial', 10))
qp.drawText(event.rect(), QtCore.Qt.AlignRight, str(self.main.max()))
class Handle(Element):
def __init__(self, parent, main):
super(Handle, self).__init__(parent, main)
def drawText(self, event, qp):
qp.setPen(self.textColor())
qp.setFont(QtGui.QFont('Arial', 10))
qp.drawText(event.rect(), QtCore.Qt.AlignLeft, str(self.main.start()))
qp.drawText(event.rect(), QtCore.Qt.AlignRight, str(self.main.end()))
def mouseMoveEvent(self, event):
event.accept()
mx = event.globalX()
_mx = getattr(self, '__mx', None)
if not _mx:
setattr(self, '__mx', mx)
dx = 0
else:
dx = mx - _mx
setattr(self, '__mx', mx)
if dx == 0:
event.ignore()
return
elif dx > 0:
dx = 1
elif dx < 0:
dx = -1
s = self.main.start() + dx
e = self.main.end() + dx
if s >= self.main.min() and e <= self.main.max():
self.main.setRange(s, e)
class QRangeSlider(QtWidgets.QWidget, Ui_Form):
endValueChanged = QtCore.pyqtSignal(int)
maxValueChanged = QtCore.pyqtSignal(int)
minValueChanged = QtCore.pyqtSignal(int)
startValueChanged = QtCore.pyqtSignal(int)
minValueChanged = QtCore.pyqtSignal(int)
maxValueChanged = QtCore.pyqtSignal(int)
startValueChanged = QtCore.pyqtSignal(int)
endValueChanged = QtCore.pyqtSignal(int)
_SPLIT_START = 1
_SPLIT_END = 2
def __init__(self, parent=None):
super(QRangeSlider, self).__init__(parent)
self.setupUi(self)
self.setMouseTracking(False)
self._splitter.splitterMoved.connect(self._handleMoveSplitter)
self._head_layout = QtWidgets.QHBoxLayout()
self._head_layout.setSpacing(0)
self._head_layout.setContentsMargins(0, 0, 0, 0)
self._head.setLayout(self._head_layout)
self.head = Head(self._head, main=self)
self._head_layout.addWidget(self.head)
self._handle_layout = QtWidgets.QHBoxLayout()
self._handle_layout.setSpacing(0)
self._handle_layout.setContentsMargins(0, 0, 0, 0)
self._handle.setLayout(self._handle_layout)
self.handle = Handle(self._handle, main=self)
self.handle.setTextColor((150, 255, 150))
self._handle_layout.addWidget(self.handle)
self._tail_layout = QtWidgets.QHBoxLayout()
self._tail_layout.setSpacing(0)
self._tail_layout.setContentsMargins(0, 0, 0, 0)
self._tail.setLayout(self._tail_layout)
self.tail = Tail(self._tail, main=self)
self._tail_layout.addWidget(self.tail)
self.setMin(0)
self.setMax(99)
self.setStart(0)
self.setEnd(99)
self.setDrawValues(True)
def min(self):
return getattr(self, '__min', None)
def max(self):
return getattr(self, '__max', None)
def setMin(self, value):
setattr(self, '__min', value)
self.minValueChanged.emit(value)
def setMax(self, value):
setattr(self, '__max', value)
self.maxValueChanged.emit(value)
def start(self):
return getattr(self, '__start', None)
def end(self):
return getattr(self, '__end', None)
def _setStart(self, value):
setattr(self, '__start', value)
self.startValueChanged.emit(value)
def setStart(self, value):
v = self._valueToPos(value)
self._splitter.splitterMoved.disconnect()
self._splitter.moveSplitter(v, self._SPLIT_START)
self._splitter.splitterMoved.connect(self._handleMoveSplitter)
self._setStart(value)
def _setEnd(self, value):
setattr(self, '__end', value)
self.endValueChanged.emit(value)
def setEnd(self, value):
v = self._valueToPos(value)
self._splitter.splitterMoved.disconnect()
self._splitter.moveSplitter(v, self._SPLIT_END)
self._splitter.splitterMoved.connect(self._handleMoveSplitter)
self._setEnd(value)
def drawValues(self):
return getattr(self, '__drawValues', None)
def setDrawValues(self, draw):
setattr(self, '__drawValues', draw)
def getRange(self):
return (self.start(), self.end())
def setRange(self, start, end):
self.setStart(start)
self.setEnd(end)
def keyPressEvent(self, event):
key = event.key()
if key == QtCore.Qt.Key_Left:
s = self.start()-1
e = self.end()-1
elif key == QtCore.Qt.Key_Right:
s = self.start()+1
e = self.end()+1
else:
event.ignore()
return
event.accept()
if s >= self.min() and e <= self.max():
self.setRange(s, e)
def setBackgroundStyle(self, style):
self._tail.setStyleSheet(style)
self._head.setStyleSheet(style)
def setSpanStyle(self, style):
self._handle.setStyleSheet(style)
def _valueToPos(self, value):
return scale(value, (self.min(), self.max()), (0, self.width()))
def _posToValue(self, xpos):
return scale(xpos, (0, self.width()), (self.min(), self.max()))
def _handleMoveSplitter(self, xpos, index):
hw = self._splitter.handleWidth()
def _lockWidth(widget):
width = widget.size().width()
widget.setMinimumWidth(width)
widget.setMaximumWidth(width)
def _unlockWidth(widget):
widget.setMinimumWidth(0)
widget.setMaximumWidth(16777215)
v = self._posToValue(xpos)
if index == self._SPLIT_START:
_lockWidth(self._tail)
if v >= self.end():
return
offset = -20
w = xpos + offset
self._setStart(v)
elif index == self._SPLIT_END:
_lockWidth(self._head)
if v <= self.start():
return
offset = -40
w = self.width() - xpos + offset
self._setEnd(v)
_unlockWidth(self._tail)
_unlockWidth(self._head)
_unlockWidth(self._handle)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
rs = QRangeSlider()
rs.show()
rs.setRange(15, 35)
rs.setBackgroundStyle('background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #222, stop:1 #333);')
rs.handle.setStyleSheet('background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #282, stop:1 #393);')
app.exec_()
如果您想运行examples,只需更改以下代码块(位于文件顶部):
<强> examples.py 强>:
import sys, os
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5 import QtWidgets
from qrangeslider import QRangeSlider
app = QtWidgets.QApplication(sys.argv)
...
答案 1 :(得分:1)
这是一个具有原生外观的PySide2示例:
from PySide2.QtWidgets import *
from PySide2.QtCore import *
from PySide2.QtGui import *
import sys
class RangeSlider(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.first_position = 1
self.second_position = 8
self.opt = QStyleOptionSlider()
self.opt.minimum = 0
self.opt.maximum = 10
self.setTickPosition(QSlider.TicksAbove)
self.setTickInterval(1)
self.setSizePolicy(
QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed, QSizePolicy.Slider)
)
def setRangeLimit(self, minimum: int, maximum: int):
self.opt.minimum = minimum
self.opt.maximum = maximum
def setRange(self, start: int, end: int):
self.first_position = start
self.second_position = end
def getRange(self):
return (self.first_position, self.second_position)
def setTickPosition(self, position: QSlider.TickPosition):
self.opt.tickPosition = position
def setTickInterval(self, ti: int):
self.opt.tickInterval = ti
def paintEvent(self, event: QPaintEvent):
painter = QPainter(self)
# Draw rule
self.opt.initFrom(self)
self.opt.rect = self.rect()
self.opt.sliderPosition = 0
self.opt.subControls = QStyle.SC_SliderGroove | QStyle.SC_SliderTickmarks
# Draw GROOVE
self.style().drawComplexControl(QStyle.CC_Slider, self.opt, painter)
# Draw INTERVAL
color = self.palette().color(QPalette.Highlight)
color.setAlpha(160)
painter.setBrush(QBrush(color))
painter.setPen(Qt.NoPen)
self.opt.sliderPosition = self.first_position
x_left_handle = (
self.style()
.subControlRect(QStyle.CC_Slider, self.opt, QStyle.SC_SliderHandle)
.right()
)
self.opt.sliderPosition = self.second_position
x_right_handle = (
self.style()
.subControlRect(QStyle.CC_Slider, self.opt, QStyle.SC_SliderHandle)
.left()
)
groove_rect = self.style().subControlRect(
QStyle.CC_Slider, self.opt, QStyle.SC_SliderGroove
)
selection = QRect(
x_left_handle,
groove_rect.y(),
x_right_handle - x_left_handle,
groove_rect.height(),
).adjusted(-1, 1, 1, -1)
painter.drawRect(selection)
# Draw first handle
self.opt.subControls = QStyle.SC_SliderHandle
self.opt.sliderPosition = self.first_position
self.style().drawComplexControl(QStyle.CC_Slider, self.opt, painter)
# Draw second handle
self.opt.sliderPosition = self.second_position
self.style().drawComplexControl(QStyle.CC_Slider, self.opt, painter)
def mousePressEvent(self, event: QMouseEvent):
self.opt.sliderPosition = self.first_position
self._first_sc = self.style().hitTestComplexControl(
QStyle.CC_Slider, self.opt, event.pos(), self
)
self.opt.sliderPosition = self.second_position
self._second_sc = self.style().hitTestComplexControl(
QStyle.CC_Slider, self.opt, event.pos(), self
)
def mouseMoveEvent(self, event: QMouseEvent):
distance = self.opt.maximum - self.opt.minimum
pos = self.style().sliderValueFromPosition(
0, distance, event.pos().x(), self.rect().width()
)
if self._first_sc == QStyle.SC_SliderHandle:
if pos <= self.second_position:
self.first_position = pos
self.update()
return
if self._second_sc == QStyle.SC_SliderHandle:
if pos >= self.first_position:
self.second_position = pos
self.update()
def sizeHint(self):
""" override """
SliderLength = 84
TickSpace = 5
w = SliderLength
h = self.style().pixelMetric(QStyle.PM_SliderThickness, self.opt, self)
if (
self.opt.tickPosition & QSlider.TicksAbove
or self.opt.tickPosition & QSlider.TicksBelow
):
h += TickSpace
return (
self.style()
.sizeFromContents(QStyle.CT_Slider, self.opt, QSize(w, h), self)
.expandedTo(QApplication.globalStrut())
)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = RangeSlider()
w.show()
# q = QSlider()
# q.show()
app.exec_()
答案 2 :(得分:0)
据我所知,@ ekhumoro的实现存在一个错误,即由于滑块的宽度,当您尝试将滑块拖动到上限时,缩放比例设置错误。
我通过更改以下内容进行了修复;
v = self._posToValue(xpos)
到;
if index == 1:
v = self._posToValue(xpos)
elif index == 2:
v = self._posToValue(xpos+hw)