1

I am trying to align a QSlider and a QLabel that I have in a QGridBoxLayout

For reference, this is for viewing frames in a video, which are shown elsewhere. The QLabel here is for showing labelled sections of the video, with green sections being "labelled" and black sections being "not labelled"

My QSLider is initialized as:

self.slider = QSlider(Qt.Horizontal, self)
self.slider.setSingleStep(1)
self.slider.setFocusPolicy(Qt.NoFocus)
self.slider.valueChanged.connect(self.sliderChanged)
self.layout.addWidget(self.slider)

The QLabel contains a QPixmap, which has a QImage created from a numpy array of shape (100,1000,3). Here is how it is initialized:

image = np.zeros((100,1000,3), dtype=np.uint8) #Initialize a black picture
for combo in indices_list:
    start, end = combo
    start = int(start / self.length * 1000)
    end = int(end / self.length * 1000)
    image[:,start:end] = [0,255,0] #Label the columns as green
height, width, bytevalue = image.shape
qimage = QImage(image, width, height, bytevalue * width, QImage.Format_RGB888)
pixmap = QPixmap(qimage)
self.label.setPixmap(pixmap)

What the above code does is take a list of start and end indices and then change the columns in that range to green.

The problem I am having is that even though the QSlider and the QLabel are stacked together in a QVBoxLayout, the slider position does not always match up with the corresponding pixel. I believe this is because the leftmost side of the slider "handle" (I am not sure of the correct name) stops at the leftmost side of the slider. They match up directly in the center, and then match up less and less as you move away from the center.

I would like the center of the handle to be directly underneath the corresponding column in the QLabel for the current slider value.

I have tried setting the margins of the slider to 0, but that did not work.

Thank you for your help.

Edit:

Pictures will probably be helpful:

Here is a picture of the entire bar:

Entire bar.

Here is a picture of it not being aligned on the left:

Not aligned left.

Here is a picture of it not aligned on the right:

Not aligned right.

And finally, Here is it aligned in the center:

Aligned in the center.

  • You could show a picture of what you currently get and another picture of what you want to get to understand you better – eyllanesc Mar 07 '19 at 18:23
  • please provide a [mcve] – eyllanesc Mar 07 '19 at 18:28
  • I guess the images you've shown are what you get now, but it would be great if you put an image of what you want to get. – eyllanesc Mar 07 '19 at 18:30
  • I suppose that length is the number of frames of the video and suppose that it is 200; and if for example indices_list is [(0, 10), (50, 100)], then the slider will have 200 steps and if it is in step 50 it should be at the left edge of the second green rectangle and if it is at 100 in the right edge, am I right? – eyllanesc Mar 07 '19 at 18:38
  • @eyllanesc that is correct. The last picture shows alignment with the right edge of the rectangle. – pariscraigm Mar 07 '19 at 18:53
  • @eyllanesc. I cant really provided a picture of what I want to get because doing so would require having code that works. When labelling the user should be able to easily go back to the correct frame, but without having the slider aligned, this is impossible. – pariscraigm Mar 07 '19 at 18:55

1 Answers1

0

To obtain the horizontal position you must use QStyle::subControlRect():

from PyQt5 import QtCore, QtGui, QtWidgets

class Label(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Label, self).__init__(parent)
        self._slider = None
        self._values = []

    def setSlider(self, slider):
        self._slider = slider
        self.update()

    def set_values(self, values):
        self._values = values
        self.update()

    def paintEvent(self, event):
        if self._slider is None: return
        painter = QtGui.QPainter(self)
        X1 = self.get_pos_by_value(self._slider.minimum())
        X2 = self.get_pos_by_value(self._slider.maximum())
        R = QtCore.QRect(QtCore.QPoint(X1, 0), QtCore.QPoint(X2, self.height()))
        painter.fillRect(R, QtGui.QColor("#000000"))
        for start, end in self._values:
            x1 = self.get_pos_by_value(start)
            x2 = self.get_pos_by_value(end)
            r = QtCore.QRect(QtCore.QPoint(x1, 0), QtCore.QPoint(x2, self.height()))
            painter.fillRect(r, QtGui.QColor("#00ff00"))

    def get_pos_by_value(self, value):
        opt = QtWidgets.QStyleOptionSlider()
        self._slider.initStyleOption(opt)
        opt.sliderPosition = value
        r = self._slider.style().subControlRect(
            QtWidgets.QStyle.CC_Slider, 
            opt, 
            QtWidgets.QStyle.SC_SliderHandle, 
            self._slider
        )
        return r.center().x()

class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Widget, self).__init__(parent)
        length = 1000
        indices_list = [(100, 200), (400, 500), (700, 900)]
        self.label = Label()
        self.slider = QtWidgets.QSlider(
            orientation=QtCore.Qt.Horizontal,
            minimum = 0,
            maximum=length,
            singleStep=1,
            pageStep=1
        )
        self.label.setSlider(self.slider)
        self.label.set_values(indices_list)
        label_value = QtWidgets.QLabel(alignment=QtCore.Qt.AlignCenter)
        self.slider.valueChanged.connect(label_value.setNum)
        label_value.setNum(self.slider.value())

        lay = QtWidgets.QVBoxLayout(self)
        lay.addWidget(self.label, 1)
        lay.addWidget(self.slider)
        lay.addWidget(label_value)
        self.resize(600, 300)

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    app.setStyle('fusion')
    w = Widget()
    w.show()
    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241