Pyqt5 – grid layout misbehaving

Question:

So I’m trying to get a grip on Qt (more specifically, Pyqt), and I want to create a simple feedback form. It should have

  • a title
  • a name (‘author’)
  • a message
  • a send and a cancel button

Let’s try without the buttons, first (the App class just provides a button to create a popup. the question concerns the Form class below it):

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QDesktopWidget,
    QHBoxLayout, QVBoxLayout, QGridLayout,
    QPushButton, QLabel,QLineEdit, QTextEdit,
    qApp
from PyQt5.QtGui import QIcon


class App(QMainWindow):

    def __init__(self):
        super().__init__()
        self.title = 'PyQt5 Layout Demo'
        self.popup = None

        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setWindowIcon(QIcon('imgs/python3.png'))

        formButton = QPushButton("show form")
        formButton.clicked.connect(self.showPopup)

        formBox = QHBoxLayout()
        formBox.addWidget(formButton)
        formBox.addStretch(1)

        vbox = QVBoxLayout()
        vbox.addLayout(formBox)
        vbox.addStretch(1)

        # self.setLayout(vbox)  # would work if this was a QWidget

        # instead, define new central widget
        window = QWidget()
        window.setLayout(vbox)
        self.setCentralWidget(window)

        self.center(self)
        self.show()

    @staticmethod
    def center(w: QWidget):
        qr = w.frameGeometry()  # get a rectangle for the entire window

        # center point = center of screen resolution
        cp = QDesktopWidget().availableGeometry().center()

        qr.moveCenter(cp)  # move center of rectangle to cp
        w.move(qr.topLeft())  # move top-left point of window to top-let point of rectangle

    def showPopup(self):
        if self.popup is None:
            self.popup = Form(self)
            self.popup.setGeometry(10, 10, 300, 400)
            self.center(self.popup)

        self.popup.show()


class Form(QWidget):
    def __init__(self, main):
        super().__init__()
        self.initUI()
        self.main = main

    def initUI(self):
        self.setWindowTitle('Feedback')
        self.setWindowIcon(QIcon('imgs/python3.png'))

        title = QLabel('Title')
        author = QLabel('Author')
        message = QLabel('Message')

        titleEdit = QLineEdit()
        authorEdit = QLineEdit()
        messageEdit = QTextEdit()

        grid = QGridLayout()
        grid.setSpacing(10)

        grid.addWidget(title, 1, 0)
        grid.addWidget(titleEdit,1, 1)

        grid.addWidget(author, 2, 0)
        grid.addWidget(authorEdit,2, 1)

        grid.addWidget(message, 3, 0)
        grid.addWidget(messageEdit, 4, 0, 6, 0)

        self.setLayout(grid)

    # probably should delegate to self.main, but bear with me
    def send(self):
        self.main.popup = None
        self.hide()

    def cancel(self):
        self.hide()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

enter image description here

Ok, looks about right. There’s a bit too much spacing in-between the line edits and the text edit, but since I want to add some buttons below it, that should be be a problem.

So I add:

    sendBtn = QPushButton("send")
    cancelBtn = QPushButton("cancel")

    sendBtn.clicked.connect(self.send)
    cancelBtn.clicked.connect(self.cancel)

    grid.addWidget(sendBtn, 7, 1)
    grid.addWidget(cancelBtn, 7, 2)

which yields

enter image description here

Now obviously, I forgot to stretch the title and author line edits to the newly introduced column 2. Easy enough to fix but what really bothers me is the placement of the buttons.

WHY do they show up in the middle of the text edit? I can see how Qt chooses the column size, and why that would lead to the buttons’ being of different size, but since the tutorial doesn’t actually add buttons to the form, I have no idea how to fix that.

I could, of course, simply add boxes:

sendBtn = QPushButton("send")
cancelBtn = QPushButton("cancel")

sendBtn.clicked.connect(self.send)
cancelBtn.clicked.connect(self.cancel)

btns = QHBoxLayout()
btns.addStretch(1)
btns.addWidget(sendBtn)
btns.addWidget(cancelBtn)

l = QVBoxLayout()
l.addLayout(grid)
l.addLayout(btns)

self.setLayout(l)

With which the popup then actually starts looking closer to something acceptable:

enter image description here

But is there a way to fix this within the grid layout, instead?

Asked By: User1291

||

Answers:

You seem to have misunderstood the signature of addWidget. The second and third arguments specify the row and column that the widget is placed in, whilst the third and fourth specify the row-span and column-span.

In your example, the problems start here:

    grid.addWidget(message, 3, 0)
    grid.addWidget(messageEdit, 4, 0, 6, 0)

where you make the text-edit span six rows and zero columns – which I doubt is what you intended. Instead, you probably want this:

    grid.addWidget(message, 3, 0, 1, 2)
    grid.addWidget(messageEdit, 4, 0, 1, 2)

which will make the message label and text-edit span the two columns created by the title and author fields above.

Now when you add the buttons, they must have a layout of their own, since the top two rows are already determining the width of the two columns. If you added the buttons directly to the grid, they would be forced to have the same widths as the widgets in the top two rows (or vice versa). So the buttons should be added like this:

    hbox = QHBoxLayout()

    sendBtn = QPushButton("send")
    cancelBtn = QPushButton("cancel")

    sendBtn.clicked.connect(self.send)
    cancelBtn.clicked.connect(self.cancel)

    hbox.addStretch()
    hbox.addWidget(sendBtn)
    hbox.addWidget(cancelBtn)

    grid.addLayout(hbox, 5, 0, 1, 2)
Answered By: ekhumoro
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.