简体   繁体   中英

PyQt5: Difference in placing buttons directly in QMainWindow versus using QGridLayout

I am trying to build a UI using PyQt5 that places buttons in exact positions. I believe that QGridLayout is a good way to do this, versus plotting them directly in the QMainWindow. The reason I am looking at QGridLayout is because I would like to have a separation of the grid of buttons from other parts of the UI, such as an image to the right of the buttons grid (using QHBoxLayout).

When I plot the buttons directly in the QMainWindow, the buttons appear in the correct locations. However, when I use QGridLayout with the same coordinates, the buttons are not placed in the correct locations and appear to be squished.

Approach 1: I plot the buttons directly in the QMainWindow using the QPushButton .move() and .resize() methods. In this case the buttons are plotted in the correct locations.

# below imports, data variable, and main() are used for both examples
import sys
from PyQt5.QtWidgets import (QApplication, QPushButton, QMainWindow, 
                            QGridLayout, QWidget)

data = [{'xmin': 2, 'ymin': 4,  'width': 205, 'height': 54, 'label': 'Go to Page 1'}, 
{'xmin': 34, 'ymin': 22, 'width': 141, 'height': 17, 'label': 'Go to Page 2'}, 
{'xmin': 2, 'ymin': 90, 'width': 187, 'height': 54, 'label': 'Go to Page 3'}, 
{'xmin': 34, 'ymin': 108, 'width': 122, 'height': 17, 'label': 'Go to Page 4'}, 
{'xmin': 2, 'ymin': 176, 'width': 231, 'height': 54, 'label': 'Go to Page 5'}]

class VisualizerWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        for rect in data:
            button = QPushButton(rect['label'], parent=self)
            button.move(rect['xmin'], rect['ymin'])
            button.resize(rect['width'], rect['height']+10)

def main():
    app = QApplication(sys.argv)
    viz_window = VisualizerWindow()
    viz_window.showMaximized()
    sys.exit(app.exec())

if __name__ == '__main__':
    main()

方法 1 窗口截图 - 正确位置

Approach 2: When I take the approach of using QMainWindow, QGridLayout, and then add QPushButtons through layout.addWidget(), the buttons are not placed in the correct locations on the screen, even though I am supplying the exact coordinates in the .addWidget() method.

class VisualizerWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        layout = QGridLayout()
        
        for rect in data:
            button = QPushButton(rect['label'], parent=self)
            layout.addWidget(button, rect['xmin'], 
                                rect['ymin'], rect['width'], 
                                rect['height']+10)
        widget = QWidget(self)
        widget.setLayout(layout)
        self.setCentralWidget(widget)

方法 2 窗口截图 - 位置不正确

The Grid Layout in Approach 2 appears to be squished in the Main Window. Why am I seeing different plotting behavior between these two approaches? How can I use QGridLayout correctly to plot the buttons in the correct locations?

Premise: using fixed geometries (what you call "plot the buttons") is rarely a good idea. Layout managers, like QGridLayout, ensure that your interface is always optimized for any window resolution by taking into account font and styles that all widget use to compute their size and guarantee that they are always correctly shown, without overlapping one each other, hiding them if the window is too small, or leaving them in a small area if there is more space available.

The problem with your grid code is that you're trying to add widgets to the grid using pixel as references, but addWidget() arguments are referred to rows and columns of the grid.
When using layout managers, manually setting the position of widget is useless, as it's the layout manager responsibility to set their position, based on the row/column (and row/column spans) on which the widget was added.

Since layout managers also take care of resizing widgets (making them bigger if there's enough space, or smaller if there's not), if you want to set specific sizes you'll need to use the widget setFixedSize() , setFixedWidth() or setFixedHeight() functions.

The layout manager also always tries to use all the available space, so when setting fixed or maximum widgets sizes, the layout manager will place those widgets equally in the available space. If you need to put a widget with a fixed size on a side of its grid cell, use the alignment argument in addWidget() .
Also, in your case, the result would be that the buttons will be equally distributed vertically, so you'll need to add a "spacer".

Finally, to add margins (that will be applied to the whole layout or the widget), QWidget.setContentsMargins() and QLayout.setContentsMargins() can be used.

Here's a revised version of your code:

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QApplication, QPushButton, QMainWindow, 
                            QGridLayout, QWidget, QSpacerItem, QSizePolicy)

leftMargin = 2
topMargin = 4
data = [
    {'row': 0, 'column': 0, 'width': 205, 'height': 54, 'label': 'Go to Page 1'}, 
    {'row': 1, 'column': 0, 'width': 141, 'height': 54, 'label': 'Go to Page 2'}, 
    {'row': 2, 'column': 0, 'width': 187, 'height': 54, 'label': 'Go to Page 3'}, 
]

class VisualizerWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        layout = QGridLayout()

        layout.setContentsMargins(leftMargin, topMargin, 0, 0)

        for rect in data:
            button = QPushButton(rect['label'], parent=self)
            layout.addWidget(button, rect['row'], rect['column'], alignment=Qt.AlignLeft)
            button.setFixedSize(rect['width'], rect['height'])

        # add a spacer item at the bottom of the layout, so that it will occupy all
        # the remaining available space, and will "put" the buttons on top
        spacer = QSpacerItem(20, 20, QSizePolicy.Expanding, QSizePolicy.Expanding)
        layout.addItem(spacer, layout.rowCount(), 0)

        widget = QWidget(self)
        widget.setLayout(layout)
        self.setCentralWidget(widget)

Note that I just used three buttons taken from your example, as I really don't understand why you're using overlapping buttons.

I strongly suggest you to read the documentation about layout managers and do some experiments using layouts in Designer, which will probably clarify most of your doubts.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM