Hernn0
Hernn0

Reputation: 99

How to make QGridLayout resize cells the exact same size?

image representation of code

Whenever I resize the window (a QDialog), Reference Viewer and Selected Viewer (subclasses of QScrollArea) should have the exact same size at all time, even after a resize event. However, once out of twice, I get a size 1 pixel smaller for the Selected Viewer (QScrollArea widget on the right). By once out of twice, I mean every odd pixel count.

It seems that the QGridLayout is forcing the right-most panel to that smaller size, probably due to rounding down the value of the space still available.

I use a QGridLayout because I need the toolbar to stay aligned in the center between the panels and it works well.

Here is a screencast demonstrating the problem: you can see the scrollbar showing up every-time the Selected Viewer (panel on the right) is resized one pixel shorter in width compared to the panel on the left.

Here is mostly what I'm doing:

       verticalLayout = QVBoxLayout(self)
       verticalLayout.setSpacing(0)
       verticalLayout.setContentsMargins(0, 0, 0, 0)
       gridLayout = QGridLayout()
       # Minimum width for the toolbar in the middle:
       gridLayout.setColumnMinimumWidth(1, 30)
       gridLayout.setColumnStretch(0,1)
       gridLayout.setColumnStretch(1,0)
       gridLayout.setColumnStretch(2,1)
       gridLayout.setSpacing(3)
    
       selectedImageViewer = ScrollAreaImageViewer(self)
       gridLayout.addWidget(selectedImageViewer, 0, 0, 3, 1)

       verticalToolBar = QToolBar(self)
       verticalToolBar.setOrientation(Qt.Orientation(Qt.Vertical))

       gridLayout.addWidget(verticalToolBar, 1, 1, 1, 1, Qt.AlignCenter)

       referenceImageViewer = ScrollAreaImageViewer(self)
       gridLayout.addWidget(referenceImageViewer, 0, 2, 3, 1)

       verticalLayout.addLayout(gridLayout)

I add another widget below in the QVBoxLayout but it's irrelevant here. I have tried adding spacers but it doesn't seem to change anything:

gridLayout.addItem(QSpacerItem(5,0, QSizePolicy.Minimum), 1, 3, 1, 1, Qt.Alignment(Qt.AlignCenter))

Is there a way to ensure both Viewers get the same size without using resize() on them on every resizeEvent()? Or should this actually be considered a bug in Qt?

I have tried the following which works around the scrollbar flickering issue:

def resizeEvent(self, event):
    self.gridLayout.setColumnMinimumWidth(0, self.selectedImageViewer.size().width())
    self.gridLayout.setColumnMinimumWidth(2, self.selectedImageViewer.size().width())

But the sizes still differ by one pixel once out of twice.

Edit: here is a minimal reproducible example

from PyQt5.QtCore import QSize, Qt
from PyQt5.QtWidgets import (QDialog, QLayout, QVBoxLayout,
    QLabel, QSizePolicy, QToolBar, QGridLayout,
    QWidget, QApplication )

class MyWidget(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        self.label = QLabel(self)

    def resizeEvent(self, event):
        self.label.setText(f"{self.size()}")

class MyDialog(QDialog):
    def __init__(self, parent):
        super().__init__(parent)
        self.setMinimumSize(QSize(500, 100))
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setSpacing(0)
        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
        self.gridLayout = QGridLayout()
        self.gridLayout.setColumnMinimumWidth(1, 30)
        self.gridLayout.setColumnStretch(0,1)
        self.gridLayout.setColumnStretch(1,0)
        self.gridLayout.setColumnStretch(2,1)
        self.gridLayout.setSpacing(3)

        self.selectedImageViewer = MyWidget(self)
        self.gridLayout.addWidget(self.selectedImageViewer, 0, 0, 3, 1)
        self.verticalToolBar = QToolBar(self)
        self.verticalToolBar.setOrientation(Qt.Orientation(Qt.Vertical))
        self.gridLayout.addWidget(self.verticalToolBar, 1, 1, 1, 1, Qt.AlignCenter)
        self.referenceImageViewer = MyWidget(self)
        self.gridLayout.addWidget(self.referenceImageViewer, 0, 2, 3, 1)
        self.verticalLayout.addLayout(self.gridLayout)

def main():
    app = QApplication([()])
    window = QWidget()
    dialog = MyDialog(window)
    dialog.show()
    return app.exec()

if __name__ == "__main__":
    main()

Upvotes: 1

Views: 2343

Answers (1)

Robert.K
Robert.K

Reputation: 126

I assume that problem is with ScrollArea being used for referenceImageViewer, so in each resize event actual referenceImageViewer is trying to add a horizontal scrollbar to itself.

As a solution you can

  1. Set referenceImageViewer's adjust policy to (QAbstractScrollArea.AdjustIgnored or QAbstractScrollArea.AdjustToContents).
  2. Try to use Widgets instead of ScrollAreaImageViewer instances in gridLayout, and then adding ScrollAreaImageViewer inside that widgets.

Edited.

There must be difference between width of 1st and 3rd widgets as long as ToolBar's width is fixed. E.g when window width is 501 and toolbar width is fixed at 20 auto alignment can't equally divide remaining 481 pixels in a half..

As a solution your toolbar must be resizable too. For reducing ToolBar width changes you can increase 1st and 3rd column stretch in GridLayout for example to value 8, and set 2nd column stretch to 1, so layout will automatically adjust width of each column.

Upvotes: 1

Related Questions