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:
Here is a picture of it not being aligned on the left:
Here is a picture of it not aligned on the right:
And finally, Here is it aligned in the center:
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_())