我如何在 PySide2 上为 QSlider 创建 "markers"?
How could I create "markers" for a QSlider on PySide2?
这张图片很好地展示了我要模拟的内容。
目标是创建项目或小部件,如上例所示,用户可以通过 MouseDoubleClicked 事件在 QSlider 上创建,并且将保留在最初创建的 Tick 位置(它将保持不动).
我已经尝试过使用 QLabels 和 Pixmaps 或 QGraphicsItems 和 QGraphicsView 的组合,但都没有成功。
不过,我觉得我很可能把事情复杂化了,而且可能有更简单的方法来实现它。
您制作这些“标记”的方法是什么?
编辑:我已尽最大努力编辑我以前的尝试之一,以使其成为最小可重现示例。虽然可能还是太长了,但就到此为止吧。
import random
from PySide2 import QtCore, QtGui, QtWidgets
class Marker(QtWidgets.QLabel):
def __init__(self, parent=None):
super(Marker, self).__init__(parent)
self._slider = None
self.setAcceptDrops(True)
pix = QtGui.QPixmap(30, 30)
pix.fill(QtGui.QColor("transparent"))
paint = QtGui.QPainter(pix)
slider_color = QtGui.QColor(random.randint(130, 180), random.randint(130, 180), random.randint(130, 180))
handle_pen = QtGui.QPen(QtGui.QColor(slider_color.darker(200)))
handle_pen.setWidth(3)
paint.setPen(handle_pen)
paint.setBrush(QtGui.QBrush(slider_color, QtCore.Qt.SolidPattern))
points = QtGui.QPolygon([
QtCore.QPoint(5, 5),
QtCore.QPoint(5, 19),
QtCore.QPoint(13, 27),
QtCore.QPoint(21, 19),
QtCore.QPoint(21, 5),
])
paint.drawPolygon(points)
del paint
self.setPixmap(pix)
class myTimeline(QtWidgets.QWidget):
def __init__(self, parent=None):
super(myTimeline, self).__init__(parent)
layout = QtWidgets.QGridLayout(self)
self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.slider.setMinimum(0)
self.slider.setMaximum(50)
self.slider.setTickPosition(QtWidgets.QSlider.TicksAbove)
self.slider.setTickInterval(1)
self.slider.setSingleStep(1)
self.slider.setAcceptDrops(True)
self.resize(self.width(), 50)
layout.addWidget(self.slider)
def create_marker(self):
bookmark = Marker(self)
opt = QtWidgets.QStyleOptionSlider()
self.slider.initStyleOption(opt)
rect = self.slider.style().subControlRect(
QtWidgets.QStyle.CC_Slider,
opt,
QtWidgets.QStyle.SC_SliderHandle,
self.slider
)
bookmark.move(rect.center().x(), 0)
bookmark.show()
def mouseDoubleClickEvent(self, event):
self.create_marker()
你的做法确实是正确的,只是有几个问题:
应更新标记的几何形状以反映其内容。这是必需的,因为 QLabel 是一种非常特殊的小部件类型,通常仅在添加到布局或处于顶层时才调整其大小 window.
标记像素图未正确对齐(它稍微偏左)。
标记定位不仅要使用rect
中心,还要使用标记宽度和滑块位置(因为你要添加滑块到父级并且有一个布局,滑块实际上偏离了布局内容边距的大小。
每次调整小部件大小时,标记都应重新定位,因此它们应保留对其值的引用。
标记对于鼠标事件应该是透明的,否则它们会阻塞滑块上的鼠标控制。
鉴于以上情况,我建议您如下:
class Marker(QtWidgets.QLabel):
def __init__(self, value, parent=None):
super(Marker, self).__init__(parent)
self.value = value
# ...
# correctly centered polygon
points = QtGui.QPolygon([
QtCore.QPoint(7, 5),
QtCore.QPoint(7, 19),
QtCore.QPoint(15, 27),
QtCore.QPoint(22, 19),
QtCore.QPoint(22, 5),
])
paint.drawPolygon(points)
del paint
self.setPixmap(pix)
# this is important!
self.adjustSize()
class myTimeline(QtWidgets.QWidget):
def __init__(self, parent=None):
# ...
self.markers = []
def mouseDoubleClickEvent(self, event):
self.create_marker()
def create_marker(self):
bookmark = Marker(self.slider.value(), self)
bookmark.show()
bookmark.installEventFilter(self)
self.markers.append(bookmark)
self.updateMarkers()
def updateMarkers(self):
opt = QtWidgets.QStyleOptionSlider()
self.slider.initStyleOption(opt)
for marker in self.markers:
opt.sliderValue = opt.sliderPosition = marker.value
rect = self.slider.style().subControlRect(
QtWidgets.QStyle.CC_Slider,
opt,
QtWidgets.QStyle.SC_SliderHandle,
)
marker.move(rect.center().x() - marker.width() / 2 + self.slider.x(), 0)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
return True
return super().eventFilter(source, event)
def resizeEvent(self, event):
super().resizeEvent(event)
self.updateMarkers()
这张图片很好地展示了我要模拟的内容。
目标是创建项目或小部件,如上例所示,用户可以通过 MouseDoubleClicked 事件在 QSlider 上创建,并且将保留在最初创建的 Tick 位置(它将保持不动). 我已经尝试过使用 QLabels 和 Pixmaps 或 QGraphicsItems 和 QGraphicsView 的组合,但都没有成功。
不过,我觉得我很可能把事情复杂化了,而且可能有更简单的方法来实现它。
您制作这些“标记”的方法是什么?
编辑:我已尽最大努力编辑我以前的尝试之一,以使其成为最小可重现示例。虽然可能还是太长了,但就到此为止吧。
import random
from PySide2 import QtCore, QtGui, QtWidgets
class Marker(QtWidgets.QLabel):
def __init__(self, parent=None):
super(Marker, self).__init__(parent)
self._slider = None
self.setAcceptDrops(True)
pix = QtGui.QPixmap(30, 30)
pix.fill(QtGui.QColor("transparent"))
paint = QtGui.QPainter(pix)
slider_color = QtGui.QColor(random.randint(130, 180), random.randint(130, 180), random.randint(130, 180))
handle_pen = QtGui.QPen(QtGui.QColor(slider_color.darker(200)))
handle_pen.setWidth(3)
paint.setPen(handle_pen)
paint.setBrush(QtGui.QBrush(slider_color, QtCore.Qt.SolidPattern))
points = QtGui.QPolygon([
QtCore.QPoint(5, 5),
QtCore.QPoint(5, 19),
QtCore.QPoint(13, 27),
QtCore.QPoint(21, 19),
QtCore.QPoint(21, 5),
])
paint.drawPolygon(points)
del paint
self.setPixmap(pix)
class myTimeline(QtWidgets.QWidget):
def __init__(self, parent=None):
super(myTimeline, self).__init__(parent)
layout = QtWidgets.QGridLayout(self)
self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.slider.setMinimum(0)
self.slider.setMaximum(50)
self.slider.setTickPosition(QtWidgets.QSlider.TicksAbove)
self.slider.setTickInterval(1)
self.slider.setSingleStep(1)
self.slider.setAcceptDrops(True)
self.resize(self.width(), 50)
layout.addWidget(self.slider)
def create_marker(self):
bookmark = Marker(self)
opt = QtWidgets.QStyleOptionSlider()
self.slider.initStyleOption(opt)
rect = self.slider.style().subControlRect(
QtWidgets.QStyle.CC_Slider,
opt,
QtWidgets.QStyle.SC_SliderHandle,
self.slider
)
bookmark.move(rect.center().x(), 0)
bookmark.show()
def mouseDoubleClickEvent(self, event):
self.create_marker()
你的做法确实是正确的,只是有几个问题:
应更新标记的几何形状以反映其内容。这是必需的,因为 QLabel 是一种非常特殊的小部件类型,通常仅在添加到布局或处于顶层时才调整其大小 window.
标记像素图未正确对齐(它稍微偏左)。
标记定位不仅要使用
rect
中心,还要使用标记宽度和滑块位置(因为你要添加滑块到父级并且有一个布局,滑块实际上偏离了布局内容边距的大小。每次调整小部件大小时,标记都应重新定位,因此它们应保留对其值的引用。
标记对于鼠标事件应该是透明的,否则它们会阻塞滑块上的鼠标控制。
鉴于以上情况,我建议您如下:
class Marker(QtWidgets.QLabel):
def __init__(self, value, parent=None):
super(Marker, self).__init__(parent)
self.value = value
# ...
# correctly centered polygon
points = QtGui.QPolygon([
QtCore.QPoint(7, 5),
QtCore.QPoint(7, 19),
QtCore.QPoint(15, 27),
QtCore.QPoint(22, 19),
QtCore.QPoint(22, 5),
])
paint.drawPolygon(points)
del paint
self.setPixmap(pix)
# this is important!
self.adjustSize()
class myTimeline(QtWidgets.QWidget):
def __init__(self, parent=None):
# ...
self.markers = []
def mouseDoubleClickEvent(self, event):
self.create_marker()
def create_marker(self):
bookmark = Marker(self.slider.value(), self)
bookmark.show()
bookmark.installEventFilter(self)
self.markers.append(bookmark)
self.updateMarkers()
def updateMarkers(self):
opt = QtWidgets.QStyleOptionSlider()
self.slider.initStyleOption(opt)
for marker in self.markers:
opt.sliderValue = opt.sliderPosition = marker.value
rect = self.slider.style().subControlRect(
QtWidgets.QStyle.CC_Slider,
opt,
QtWidgets.QStyle.SC_SliderHandle,
)
marker.move(rect.center().x() - marker.width() / 2 + self.slider.x(), 0)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
return True
return super().eventFilter(source, event)
def resizeEvent(self, event):
super().resizeEvent(event)
self.updateMarkers()