我正在尝试在 PyQt(特别是 PyQt6)中实现(或者更确切地说,使用其他人的实现)QRangeSlider。我找到了这个很好的答案它使用 Qt 的内置样式在一个凹槽上绘制两个滑块句柄,方法是在绘制时在位置之间交替,并分别处理滑块句柄。这一直很好,除了一个例外:样式实际上并没有绘制。我不知道这是否是我的适应问题(我从 pyside2 转换为 pyqt6,虽然 99% 只是从 pyqt6 添加额外的枚举,如 qt.penstyle.none 之类的)还是什么,但我无法获得风格.drawcomplexcontrol 来渲染小部件的组件。通过访问它们的矩形来手动绘制组件并使用 qpainter 绘制它们可以正常工作,但这违背了使用 Qt 的本机样式看起来不错的目的。我还在另一台机器上测试了这段代码,同样的事情发生了。
代码:
from PyQt6.QtGui import QBrush, QCursor, QMouseEvent, QPaintEvent, QPainter, QPalette
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton, QSizePolicy, QSlider, QStyle, QStyleFactory, QStyleOptionSlider, QWidget
from PyQt6.QtCore import QRect, QSize, Qt
import sys
class RangeSlider(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setMouseTracking(True);
self.first_position = 1
self.second_position = 8
self.opt = QStyleOptionSlider()
self.opt.minimum = 0
self.opt.maximum = 100
self.setTickPosition(QSlider.TickPosition.TicksAbove)
self.setTickInterval(10)
self.setSizePolicy(
QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed, QSizePolicy.ControlType.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.SubControl.SC_SliderGroove | QStyle.SubControl.SC_SliderTickmarks
# Draw GROOVE
self.style().drawComplexControl(QStyle.ComplexControl.CC_Slider, self.opt, painter)
self.style().drawControl(QStyle.ControlElement.CE_ScrollBarSlider, self.opt, painter);
# Draw INTERVAL
color = self.palette().color(QPalette.ColorRole.Highlight)
color.setAlpha(160)
painter.setBrush(QBrush(color))
painter.setPen(Qt.PenStyle.NoPen)
self.opt.sliderPosition = self.first_position
left_handle = (
self.style()
.subControlRect(QStyle.ComplexControl.CC_Slider, self.opt, QStyle.SubControl.SC_SliderHandle)
)
self.opt.sliderPosition = self.second_position
right_handle = (
self.style()
.subControlRect(QStyle.ComplexControl.CC_Slider, self.opt, QStyle.SubControl.SC_SliderHandle)
)
groove_rect = self.style().subControlRect(
QStyle.ComplexControl.CC_Slider, self.opt, QStyle.SubControl.SC_SliderGroove
)
print(f"Groove Rect: {groove_rect}, Left handle x: {left_handle}, right handle x: {right_handle}");
selection = QRect(
left_handle.right(),
groove_rect.y(),
right_handle.left() - left_handle.right(),
groove_rect.height(),
).adjusted(-1, 1, 1, -1)
painter.drawRect(selection)
# Draw first handle
self.opt.subControls = QStyle.SubControl.SC_SliderHandle
self.opt.sliderPosition = self.first_position
self.style().drawComplexControl(QStyle.ComplexControl.CC_Slider, self.opt, painter)
painter.setPen(Qt.GlobalColor.gray);
painter.setBrush(Qt.GlobalColor.gray);
painter.drawRect(left_handle);
painter.drawRect(right_handle);
# Draw second handle
self.opt.sliderPosition = self.second_position
self.style().drawComplexControl(QStyle.ComplexControl.CC_Slider, self.opt, painter)
def mousePressEvent(self, event: QMouseEvent):
self.opt.sliderPosition = self.first_position
self._first_sc = self.style().hitTestComplexControl(
QStyle.ComplexControl.CC_Slider, self.opt, event.position().toPoint(), self
)
self.opt.sliderPosition = self.second_position
self._second_sc = self.style().hitTestComplexControl(
QStyle.ComplexControl.CC_Slider, self.opt, event.position().toPoint(), self
)
def mouseMoveEvent(self, event: QMouseEvent):
if Qt.MouseButton.LeftButton in event.buttons():
print("dragging")
distance = self.opt.maximum - self.opt.minimum
pos = self.style().sliderValueFromPosition(
0, distance, event.position().x(), self.rect().width()
)
if self._first_sc == QStyle.SubControl.SC_SliderHandle:
if pos <= self.second_position:
self.first_position = pos
self.update()
return
if self._second_sc == QStyle.SubControl.SC_SliderHandle:
if pos >= self.first_position:
self.second_position = pos
self.update()
else:
print("moving")
print(self.opt.sliderPosition);
self.opt.sliderPosition = self.first_position;
first_hit = self.style().hitTestComplexControl(
QStyle.ComplexControl.CC_Slider, self.opt, event.position().toPoint(), self
) == QStyle.SubControl.SC_SliderHandle;
self.opt.sliderPosition = self.second_position;
second_hit = self.style().hitTestComplexControl(
QStyle.ComplexControl.CC_Slider, self.opt, event.position().toPoint(), self
) == QStyle.SubControl.SC_SliderHandle;
self.setCursor(Qt.CursorShape.SizeHorCursor if first_hit or second_hit else QCursor());
def sizeHint(self):
""" override """
SliderLength = 84
TickSpace = 5
w = SliderLength
h = self.style().pixelMetric(QStyle.PixelMetric.PM_SliderThickness, self.opt, self)
if (
QSlider.TickPosition.TicksAbove == self.opt.tickPosition
or QSlider.TickPosition.TicksBelow == self.opt.tickPosition
):
h += TickSpace
return (
self.style()
.sizeFromContents(QStyle.ContentsType.CT_Slider, self.opt, QSize(w, h), self)
# .expandedTo(QApplication.globalStrut())
)
if __name__ == "__main__":
app = QApplication(sys.argv)
main = QMainWindow();
#main.setStyle(QStyleFactory.create("breeze"))
w = RangeSlider()
main.setCentralWidget(w);
main.setMenuWidget(QPushButton("hello"));
main.show();
# q = QSlider()
# q.show()
app.exec()
结果图像:[手动绘制的内凹槽和手柄矩形]