简体   繁体   中英

How could I create “markers” for a QSlider on PySide2?

This picture is a pretty good representation of what I'm trying to emulate.

The goal is to create items or widgets, looking like the example above, that a user could create on a QSlider by a MouseDoubleClicked event, and which would remain at the Tick position it was originally created (it would remain immobile). I've already made a few attempts using either QLabels with Pixmaps or a combination of QGraphicsItems and QGraphicsView, in vain.

Still, I have the feeling that I'm most likely over complicating things, and that there might be a simpler way to achieve that.

What would be your approach to make those "markers"?

EDIT: I've tried my best to edit one of my previous attempts, in order to make it a Minimal Reproducible Example. Might still be too long though, but here it goes.

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()

Your approach is indeed correct, it only has a few issues:

  1. The geometry of the marker should be updated to reflect its contents. This is required as QLabel is a very special type of widget, and usually adapts its size only when added to a layout or is a top level window.

  2. The marker pixmap is not correctly aligned (it's slightly on the left of its center).

  3. The marker positioning should not only use the rect center, but also the marker width and the slider position (since you are adding the slider to the parent and there's a layout, the slider is actually off by the size of the layout's contents margins).

  4. The markers should be repositioned everytime the widget is resized, so they should keep a reference to their value.

  5. Markers should be transparent for mouse events, otherwise they would block mouse control on the slider.

Given the above, I suggest you the following:

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()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM