0

In the following code:

import sys

from PyQt5           import QtWidgets
from PyQt5.QtCore    import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QTableWidget, QPushButton

class Window(QtWidgets.QMainWindow):
    def __init__(self, method):
        super(Window, self).__init__()

        mainWidget = QWidget()
        mainLayout = QHBoxLayout(mainWidget)

        table = QTableWidget(10, 3)
        button1 = QPushButton("Play")
        button2 = QPushButton("Cancel")

        mainLayout.addWidget(table)
        mainLayout.addWidget(button1)
        mainLayout.addWidget(button2)

        if   (method == 1):
            rtnValue = mainLayout.setAlignment(button1, Qt.AlignTop)
            print("Method 1:", rtnValue)
        elif (method == 2):
            rtnValue = mainLayout.setAlignment(mainLayout, Qt.AlignTop)
            print("Method 2:", rtnValue)
        else:
            rtnValue = mainLayout.setAlignment(Qt.AlignTop)
            print("Method X:", rtnValue)

        self.setCentralWidget(mainWidget)
        self.show()

if __name__ == "__main__":
    print("python QLayoutAlignment.py[ <MethodToUse=1>")
    app = QApplication(sys.argv)
    method = 1 if (len(sys.argv) < 2) else int(sys.argv[1])
    GUI = Window(method)
    sys.exit(app.exec_())

when I call the program like below with mainLayout.setAlignment(button1, Qt.AlignTop) being called, it works as expected with the "Play" button aligned at the top and "Cancel" button aligned at the center vertically. I also found the documentation for bool QLayout::setAlignment(QWidget *w, Qt::Alignment alignment) although in Qt.

python QLayoutAlignment.py 1

However when I call the the program like below with mainLayout.setAlignment(mainLayout, Qt.AlignTop) being called, it does not seem to work. All the buttons are vertically center aligned. I interpreted the Qt documentation of bool QLayout::setAlignment(QLayout *l, Qt::Alignment alignment)) as "it align all the added widget of the layout to the set alignment". So what does this function actually do (when is it used)?

python QLayoutAlignment.py 2

Lastly, I also saw another example from Center and top align a QVBoxLayout. When I call the program like below with mainLayout.setAlignment(Qt.AlignTop) being called, it also does not work with all the buttons vertically center aligned. For this one I could not find its documentation. So what does this function actually do (when is it used) and where can I find its documentation?

python QLayoutAlignment.py 3
mak
  • 197
  • 9
  • 1
    The first two methods are overloaded to accept a different type for the first argument (which is required because C++ is statically typed). They both only apply the alignment to *child items*. So, since `mainLayout` cannot be a child of itself, your "method 2" has no effect. The third method is inherited from [QLayoutItem](https://doc.qt.io/qt-5/qlayoutitem.html#setAlignment). It applies the alignemt to itself (but not its children). So, since `mainLayout` isn't the child of another layout, your "method 3" has no effect. – ekhumoro Sep 21 '21 at 11:45

2 Answers2

2

The .setAlignment method which accepts a layout is used for aligning sub-layouts, that is child layouts you've added to the parent layout using .addLayout.

Below is a little demo based on your code.

import sys

from PyQt5           import QtWidgets
from PyQt5.QtCore    import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QTableWidget, QPushButton

class Window(QtWidgets.QMainWindow):
    def __init__(self, method=0):
        super(Window, self).__init__()

        mainWidget = QWidget()
        self.mainLayout = QHBoxLayout(mainWidget)

        table = QTableWidget(10, 3)
        self.button1 = QPushButton("Play")
        self.button2 = QPushButton("Cancel")

        self.subLayout = QHBoxLayout()
        buttona1 = QPushButton("1")
        buttona1.clicked.connect(self.clicked1)
        buttona2 = QPushButton("2")
        buttona2.clicked.connect(self.clicked2)
        buttona3 = QPushButton("3")
        buttona3.clicked.connect(self.clicked3)
        buttona4 = QPushButton("4")
        buttona4.clicked.connect(self.clicked4)
        self.subLayout.addWidget(buttona1)
        self.subLayout.addWidget(buttona2)
        self.subLayout.addWidget(buttona3)
        self.subLayout.addWidget(buttona4)

        self.mainLayout.addWidget(table)
        self.mainLayout.addWidget(self.button1)
        self.mainLayout.addWidget(self.button2)
        self.mainLayout.addLayout(self.subLayout)

        self.setCentralWidget(mainWidget)
        self.show()

    def clicked1(self):
        rtnValue = self.mainLayout.setAlignment(self.button1, Qt.AlignTop)
        print("Method 1:", rtnValue)

    def clicked2(self):
        rtnValue = self.mainLayout.setAlignment(self.mainLayout, Qt.AlignTop)
        print("Method 2:", rtnValue)

    def clicked3(self):
        rtnValue = self.mainLayout.setAlignment(Qt.AlignTop)
        print("Method 3:", rtnValue)

    def clicked4(self):
        rtnValue = self.mainLayout.setAlignment(self.subLayout, Qt.AlignTop)
        print("Method 4:", rtnValue)


if __name__ == "__main__":
    print("python QLayoutAlignment.py[ <MethodToUse=1>")
    app = QApplication(sys.argv)
    method = 1 if (len(sys.argv) < 2) else int(sys.argv[1])
    GUI = Window(method)
    sys.exit(app.exec_())

You'll notice if you trigger this self.mainLayout.setAlignment(self.mainLayout, Qt.AlignTop) the return value is False. This is telling you that the layout you're aligning could not be found in the current layout. Since you're calling .setAlignment on mainLayout the layout you're affecting must be in that layout.

In the 4th method, I've added a sub-layout, and as you can see this ( rtnValue = self.mainLayout.setAlignment(self.subLayout, Qt.AlignTop)) works as expected and returns True.

mfitzp
  • 15,275
  • 7
  • 50
  • 70
  • Thank you for your working code. I now understand what `QLayout.setAlignment(QWidget, Qt.Alignment)` and `QLayout.setAlignment(QLayout, Qt.Alignment)` do. Is it possible to show me a working example of `QLayoutItem.setAlignment(Qt.Alignment)` by adding additional codes above (in your code) with comments? – mak Sep 22 '21 at 07:17
0

First of all, it's important to understand that the Qt layout system works by using layout items (see QLayoutItem), which are abstract items that are used as virtual containers for objects: widgets, spacers and layouts (when nested layouts are used). Every QLayout is, in fact, a subclass of QLayoutItem.

Using setAlignment means setting the alignment of the specified layout item:

  • layout.setAlignment(item, alignment) sets the alignment of item, which has to be directly contained in layout;
  • layout.setAlignment(alignment) sets the alignment of layout related to its parent layout; note that this does not mean that items inside layout will use the specified alignment;

Your second case, mainLayout.setAlignment(mainLayout, Qt.AlignTop), does not work and returns False because mainLayout is, obviously, not "contained" in mainLayout. In fact, if you carefully read the documentation, it says:

returns true if w/l is found in this layout (not including child layouts);

In your third case, you don't see any result because mainLayout is the top layout for the widget, and since there's no parent layout the alignment seems to be ignored. As specified above, using layout.setAlignment(alignment) does not set the alignment of child items, but only of the layout item of layout. If you add that mainLayout to another layout, you will see that the alignment is respected for the layout.

Setting the layout alignment is rarely used, also because it's often counterintuitive: one might led to believe that setting the alignment of a layout will align its contents, but that's not the case.

To clarify, consider that setting the layout alignment is almost the same as creating a widget with that layout, and adding that widget with the specified alignment. With that in mind, it makes more sense: you're not aligning the contents of the widget, but the widget itself.

Consider the following example: besides the table on the left (used for comparison), I'm adding a layout on the left by specifying its alignment, then I'm adding a widget on the right by specifying the alignment of the widget for the main layout. As you can see, they appear exactly the same.

from PyQt5 import QtCore, QtWidgets
import sys
app = QtWidgets.QApplication(sys.argv)

test = QtWidgets.QWidget()
mainLayout = QtWidgets.QHBoxLayout(test)
# a very tall table to show the difference in alignment
mainLayout.addWidget(QtWidgets.QTableView(minimumHeight=300))

leftLayout = QtWidgets.QHBoxLayout()
# setting the alignment of leftLayout relative to mainLayout
leftLayout.setAlignment(QtCore.Qt.AlignTop)
leftLayout.addWidget(QtWidgets.QTableView(maximumHeight=100))
leftLayout.addWidget(QtWidgets.QPushButton())
mainLayout.addLayout(leftLayout)

rightWidget = QtWidgets.QWidget()
# adding the widget to mainLayout by aligning it on top as with leftLayout
mainLayout.addWidget(rightWidget, alignment=QtCore.Qt.AlignTop)
rightLayout = QtWidgets.QHBoxLayout(rightWidget)
rightLayout.addWidget(QtWidgets.QTableView(maximumHeight=100))
rightLayout.addWidget(QtWidgets.QPushButton())

test.show()
sys.exit(app.exec_())

Finally, if you want to align widgets, you either specify the alignment for each widget, or you add nested layout.
When many widgets are going to be added with the same alignment, the nested layout is usually the best solution: in your case, add a vertical layout to the main layout, then add horizontal layout to the vertical for the buttons, and add a stretch to the vertical to "push" the horizontal layout on top.

Alternatively, you can use a grid layout and eventually use spacers to ensure that the widgets are "aligned" as required.

from PyQt5 import QtCore, QtWidgets
import sys
app = QtWidgets.QApplication(sys.argv)

test = QtWidgets.QWidget()
mainLayout = QtWidgets.QHBoxLayout(test)

class Button(QtWidgets.QPushButton):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setSizePolicy(
            QtWidgets.QSizePolicy.Preferred, 
            QtWidgets.QSizePolicy.Preferred
        )

noAlignGroup = QtWidgets.QGroupBox('no alignment')
mainLayout.addWidget(noAlignGroup)
noAlignLayout = QtWidgets.QHBoxLayout(noAlignGroup)
noAlignLayout.addWidget(QtWidgets.QTableView())
noAlignLayout.addWidget(Button())
noAlignLayout.addWidget(Button())

widgetAlignGroup = QtWidgets.QGroupBox('addWidget(widget, alignment)')
mainLayout.addWidget(widgetAlignGroup)
widgetAlignLayout = QtWidgets.QHBoxLayout(widgetAlignGroup)
widgetAlignLayout.addWidget(QtWidgets.QTableView())
widgetAlignLayout.addWidget(Button(), alignment=QtCore.Qt.AlignTop)
widgetAlignLayout.addWidget(Button(), alignment=QtCore.Qt.AlignTop)

layoutAlignGroup = QtWidgets.QGroupBox('nestedLayout.setAlignment()')
mainLayout.addWidget(layoutAlignGroup)
layoutAlignLayout = QtWidgets.QHBoxLayout(layoutAlignGroup)
layoutAlignLayout.addWidget(QtWidgets.QTableView())
buttonLayout = QtWidgets.QHBoxLayout()
layoutAlignLayout.addLayout(buttonLayout)
buttonLayout.setAlignment(QtCore.Qt.AlignTop)
buttonLayout.addWidget(Button())
buttonLayout.addWidget(Button())

stretchGroup = QtWidgets.QGroupBox('nestedLayout + stretch')
mainLayout.addWidget(stretchGroup)
stretchLayout = QtWidgets.QHBoxLayout(stretchGroup)
stretchLayout.addWidget(QtWidgets.QTableView())
rightLayout = QtWidgets.QVBoxLayout()
stretchLayout.addLayout(rightLayout)
buttonLayout = QtWidgets.QHBoxLayout()
rightLayout.addLayout(buttonLayout)
buttonLayout.addWidget(Button())
buttonLayout.addWidget(Button())
rightLayout.addStretch()

gridAlignGroup = QtWidgets.QGroupBox('grid + spacer')
mainLayout.addWidget(gridAlignGroup)
gridLayout = QtWidgets.QGridLayout(gridAlignGroup)
gridLayout.addWidget(QtWidgets.QTableView(), 0, 0, 2, 1)
gridLayout.addWidget(Button(), 0, 1)
gridLayout.addWidget(Button(), 0, 2)
spacer = QtWidgets.QSpacerItem(1, 50, vPolicy=QtWidgets.QSizePolicy.Expanding)
gridLayout.addItem(spacer, 1, 1)

test.show()
sys.exit(app.exec_())
musicamante
  • 41,230
  • 6
  • 33
  • 58