Проблема с выравниванием виджетов в PyQt Slider

У меня есть слайдер, чьи галочки должны иметь названия года. Приведенный ниже код работает, но метка галочек не выровнена должным образом. Каждый год номер должен быть помимо галочки.

class Slider(QWidget):
    def __init__(self, minimum, maximum, parent=None):
        super(Slider, self).__init__(parent=parent)
        container = QtGui.QWidget(self)        
        layout = QHBoxLayout(container)
        self.sl = QSlider(Qt.Vertical)
        self.sl.setMinimum(minimum)
        self.sl.setMaximum(maximum)
        self.sl.setValue(minimum)
        self.sl.setTickPosition(QSlider.TicksLeft)
        self.sl.setTickInterval(1)
        self.sl.setSingleStep(1)
        self.sl.valueChanged.connect(self.valuechange)
        self.setLayout(layout)
        self.sl.resize(100,3000)        
        layout.addWidget(self.sl)

        for i in range(minimum, maximum + 1):
            label = QLabel(str(i))
            label.setContentsMargins(0, 0, 0, 0)
            label.setAlignment(QtCore.Qt.AlignLeft)
            layout.addWidget(label)

        container.setStyleSheet("background-color:red;")

Кроме того, изменение размера слайдера также не работает. Я не уверен, что я делаю здесь неправильно. Этикетки в настоящее время выглядят так.

1 ответ

Решение

Одна из основных проблем в вашем коде заключается в том, что вы размещаете метки по горизонтали, а не по вертикали, помимо того, что размещаете их в столбцах с индексами, которые зависят от минимального значения, вместо того, чтобы начинать с 0 до необходимого числа, а другая проблема заключается в том, что вы должен начинаться с максимума до минимума.

import sys
from pyqtgraph.Qt import QtCore, QtGui

class Slider(QtGui.QWidget):
    def __init__(self, minimum, maximum, parent=None):
        super(Slider, self).__init__(parent=parent)
        layout = QtGui.QGridLayout(self)
        self.sl = QtGui.QSlider(QtCore.Qt.Vertical)
        self.sl.setMinimum(minimum)
        self.sl.setMaximum(maximum)
        self.sl.setValue(minimum)
        self.sl.setTickPosition(QtGui.QSlider.TicksLeft)
        self.sl.setTickInterval(1)
        self.sl.setSingleStep(1)

        self.sl.valueChanged.connect(lambda value: print(value))

        for index, value in enumerate(range(maximum, minimum-1, -1)):
            label = QtGui.QLabel("{}".format(value))
            layout.addWidget(label, index, 0, QtCore.Qt.AlignLeft)

        layout.addWidget(self.sl, 0, 1, maximum - minimum + 1, 1, QtCore.Qt.AlignLeft)

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    w = Slider(2015, 2019)
    w.show()
    sys.exit(app.exec_())

Я нашел этот хак, и после небольшого возни мне удалось перевести его на python с некоторыми изменениями. Вы можете указать ориентацию или задать массив текстов пользовательских меток, отличных от целочисленных уровней.

Нужно быть осторожным, чтобы на самом деле ссылаться на QSlider, вам придется использовать slider.sl, например slider.sl.valueChanged.connect(someFunc),

(Я использую PyQt5, и импорт немного отличается от pyqtgraph.)

import sys
from PyQt5 import QtWidgets
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QStyle, QStyleOptionSlider
from PyQt5.QtCore import QRect, QPoint, Qt


class LabeledSlider(QtWidgets.QWidget):
    def __init__(self, minimum, maximum, interval=1, orientation=Qt.Horizontal,
            labels=None, parent=None):
        super(LabeledSlider, self).__init__(parent=parent)

        levels=range(minimum, maximum+interval, interval)
        if labels is not None:
            if not isinstance(labels, (tuple, list)):
                raise Exception("<labels> is a list or tuple.")
            if len(labels) != len(levels):
                raise Exception("Size of <labels> doesn't match levels.")
            self.levels=list(zip(levels,labels))
        else:
            self.levels=list(zip(levels,map(str,levels)))

        if orientation==Qt.Horizontal:
            self.layout=QtWidgets.QVBoxLayout(self)
        elif orientation==Qt.Vertical:
            self.layout=QtWidgets.QHBoxLayout(self)
        else:
            raise Exception("<orientation> wrong.")

        # gives some space to print labels
        self.left_margin=10
        self.top_margin=10
        self.right_margin=10
        self.bottom_margin=10

        self.layout.setContentsMargins(self.left_margin,self.top_margin,
                self.right_margin,self.bottom_margin)

        self.sl=QtWidgets.QSlider(orientation, self)
        self.sl.setMinimum(minimum)
        self.sl.setMaximum(maximum)
        self.sl.setValue(minimum)
        if orientation==Qt.Horizontal:
            self.sl.setTickPosition(QtWidgets.QSlider.TicksBelow)
            self.sl.setMinimumWidth(300) # just to make it easier to read
        else:
            self.sl.setTickPosition(QtWidgets.QSlider.TicksLeft)
            self.sl.setMinimumHeight(300) # just to make it easier to read
        self.sl.setTickInterval(interval)
        self.sl.setSingleStep(1)

        self.layout.addWidget(self.sl)

    def paintEvent(self, e):

        super(LabeledSlider,self).paintEvent(e)

        style=self.sl.style()
        painter=QPainter(self)
        st_slider=QStyleOptionSlider()
        st_slider.initFrom(self.sl)
        st_slider.orientation=self.sl.orientation()

        length=style.pixelMetric(QStyle.PM_SliderLength, st_slider, self.sl)
        available=style.pixelMetric(QStyle.PM_SliderSpaceAvailable, st_slider, self.sl)

        for v, v_str in self.levels:

            # get the size of the label
            rect=painter.drawText(QRect(), Qt.TextDontPrint, v_str)

            if self.sl.orientation()==Qt.Horizontal:
                # I assume the offset is half the length of slider, therefore
                # + length//2
                x_loc=QStyle.sliderPositionFromValue(self.sl.minimum(),
                        self.sl.maximum(), v, available)+length//2

                # left bound of the text = center - half of text width + L_margin
                left=x_loc-rect.width()//2+self.left_margin
                bottom=self.rect().bottom()

                # enlarge margins if clipping
                if v==self.sl.minimum():
                    if left<=0:
                        self.left_margin=rect.width()//2-x_loc
                    if self.bottom_margin<=rect.height():
                        self.bottom_margin=rect.height()

                    self.layout.setContentsMargins(self.left_margin,
                            self.top_margin, self.right_margin,
                            self.bottom_margin)

                if v==self.sl.maximum() and rect.width()//2>=self.right_margin:
                    self.right_margin=rect.width()//2
                    self.layout.setContentsMargins(self.left_margin,
                            self.top_margin, self.right_margin,
                            self.bottom_margin)

            else:
                y_loc=QStyle.sliderPositionFromValue(self.sl.minimum(),
                        self.sl.maximum(), v, available, upsideDown=True)

                bottom=y_loc+length//2+rect.height()//2+self.top_margin-3
                # there is a 3 px offset that I can't attribute to any metric

                left=self.left_margin-rect.width()
                if left<=0:
                    self.left_margin=rect.width()+2
                    self.layout.setContentsMargins(self.left_margin,
                            self.top_margin, self.right_margin,
                            self.bottom_margin)

            pos=QPoint(left, bottom)
            painter.drawText(pos, v_str)

        return


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    frame=QtWidgets.QWidget()
    ha=QtWidgets.QHBoxLayout()
    frame.setLayout(ha)

    w = LabeledSlider(1999, 2006 , 1, orientation=Qt.Vertical,
            labels=['Y%d' %ii for ii in range(1999,2007)])

    ha.addWidget(w)
    frame.show()
    sys.exit(app.exec_())

И скриншот:

Другие вопросы по тегам