pythonlayoutpyqt5widgetqgridlayout

How to resize my widgets in PyQt5(QGridLayout)


I have a Problem. I started learning PyQt5 and I want to make a calculator. So I learned how to passionate my Widgets with QGridLayout, but I can't change the sizes of Widgets. Some widgets are bigger than others and I do not understand that. I tried with different methods like (resize, setGeometry) and it didn't work. I also want to resize my QLineEdit variable, but I can't. Here is the code and a picture of my problem:

import PyQt5.QtWidgets as qwd
import PyQt5.QtGui as qtg

class CreateWindow(qwd.QWidget):
    # create window
    def __init__(self):
        super().__init__()
        
        self.setWindowTitle('calculator')
        self.setWindowIcon(qtg.QIcon('calculator.png'))
        self.setStyleSheet('background: #FAEBD7;')
        # self.setFixedWidth(300)
        # self.setFixedHeight(170)

        # show result
        self.text = '0'

        self.UIcomponents()

        self.show()

    def UIcomponents(self):
        # layout

        grid = qwd.QGridLayout()
        

        # show numbers
        self.resultText = qwd.QLineEdit(self.text)
        self.resultText.setReadOnly(True)
        self.resultText.setStyleSheet('background: #FFFAFA;')

        # buttons
        self.but9 = qwd.QPushButton('9')
        self.but8 = qwd.QPushButton('8')
        self.but7 = qwd.QPushButton('7')
        self.but6 = qwd.QPushButton('6')
        self.but5 = qwd.QPushButton('5')
        self.but4 = qwd.QPushButton('4')
        self.but3 = qwd.QPushButton('3')
        self.but2 = qwd.QPushButton('2')
        self.but1 = qwd.QPushButton('1')
        self.but0 = qwd.QPushButton('0')
        self.but_add = qwd.QPushButton('+')
        self.but_decreas = qwd.QPushButton('-')
        self.but_multiply = qwd.QPushButton('*')
        self.but_devide = qwd.QPushButton('/')     
        self.but_equal = qwd.QPushButton('=')

        # positionate the buttons
        grid.addWidget(self.resultText, 0, 0)
        grid.addWidget(self.but9, 1, 2)
        grid.addWidget(self.but8, 1, 1)
        grid.addWidget(self.but7, 1, 0)
        grid.addWidget(self.but6, 2, 2)
        grid.addWidget(self.but5, 2, 1)
        grid.addWidget(self.but4, 2, 0)
        grid.addWidget(self.but3, 3, 2)
        grid.addWidget(self.but2, 3, 1)
        grid.addWidget(self.but1, 3, 0)
        grid.addWidget(self.but0, 4, 0)

        # buttons for operation
        self.but_devide.setStyleSheet('background: #7FFFD4')
        grid.addWidget(self.but_devide, 1, 4)
        self.but_multiply.setStyleSheet('background: #7FFFD4')
        grid.addWidget(self.but_multiply, 2, 4)
        self.but_add.setStyleSheet('background: #7FFFD4')
        grid.addWidget(self.but_add, 3, 4)
        self.but_decreas.setStyleSheet('background: #7FFFD4')
        grid.addWidget(self.but_decreas, 4, 4)
        self.but_equal.setStyleSheet('background: #008B8B')
        grid.addWidget(self.but_equal, 4, 2)

        # connect buttons
        self.but9.clicked.connect(lambda state, number = '9': self.show_result(number))
        self.but8.clicked.connect(lambda state, number = '8': self.show_result(number))
        self.but7.clicked.connect(lambda state, number = '7': self.show_result(number))
        self.but6.clicked.connect(lambda state, number = '6': self.show_result(number))
        self.but5.clicked.connect(lambda state, number = '5': self.show_result(number))
        self.but4.clicked.connect(lambda state, number = '4': self.show_result(number))
        self.but3.clicked.connect(lambda state, number = '3': self.show_result(number))
        self.but2.clicked.connect(lambda state, number = '2': self.show_result(number))
        self.but1.clicked.connect(lambda state, number = '1': self.show_result(number))
        self.but0.clicked.connect(lambda state, number = '0': self.show_result(number))
        self.but_decreas.clicked.connect(lambda state, oper = '-': self.chose_oper(oper))
        self.but_add.clicked.connect(lambda state, oper = '+': self.chose_oper(oper))
        self.but_multiply.clicked.connect(lambda state, oper = '*': self.chose_oper(oper))
        self.but_devide.clicked.connect(lambda state, oper = '/': self.chose_oper(oper))

        self.setLayout(grid)

    def show_result(self, number):
        if len(self.text) != 20:
            print(len(self.text))
            if self.text[0] == '0':
                self.text = ''
            self.text += number
            self.resultText.setText(str(self.text))
            
    def chose_oper(self, oper):
        if self.text[0] != '0':
                self.text += oper
        self.resultText.setText(str(self.text))

def create_app():
    app = qwd.QApplication([])
    wind = CreateWindow()
    # set app style
    app.setStyle('Fusion')

    app.exec_()

create_app()

Here is how my widgets look like


Solution

  • tl;dr

    Choose an appropriate column span when adding a widget to a grid layout:

        grid.addWidget(self.resultText, 0, 0, 1, 5)
    

    Explanation

    The point of a layout manager is that it manages the layout.

    Based on the widget on which it is set and all items it manages (widgets or even other nested layouts), considering all constraints (possible minimum/maximum size), hints (the preferred size) and size policies (if and how a widget can be resized), then it decides where those items should be placed and how big they are.

    With all that in mind, you can understand that there's no point in trying to resize those widgets manually, as the layout will override it as soon as possible1.

    Most widget will try to take as much space as the layout gives them, based on their sizeHint suggestion, and if more space is available they might try to use it. That's the case for the width of buttons, but not for their height (which is normally fixed).

    When a window is mapped (shown the first time), the layout takes into account all the above, including the size hint, and tries to use a size that allows all those hints to be respected. In fact, if you try to resize the window to the small size possible, you'll see that all buttons (and the line edit) will most likely get the same size.

    You are using a grid layout, which allows to put items inside the cells of a grid: rows and columns. Since you put the QLineEdit in the first column, and that widget has a default size hint wider than buttons, the result is that when the window is shown the first time the first column will use that width, and since buttons adapt their widths to the available space, those in the first column will appear as wide as the line edit.

    Grid layouts allow using spanning (see the documentation, which is a way to let an item occupy more than one cell row and/or column. Considering the purpose of your program, that line edit will probably look better if it occupies the whole horizontal space, and since you're using 5 columns (the operation buttons are on column 4, indexes always start from 0) that's how we got the line above:

        grid.addWidget(self.resultText, 0, 0, 1, 5)
    

    Which says: add the widget on row 0 and column 0, and make it occupy just one row but five columns.
    If span arguments are not given, they implicitly occupy only one row and one column. Note that even if you are only interested in one span "direction", both span arguments are required (they are not keyword arguments).

    1 It is possible to use resize or setGeometry, after a layout has done its job; but there's little point in trying to do it: the layout will override those manual attempts as soon as the window is resized in any way (from the user or the system). This is exactly what happens if you try to do that before showing the window: when the widget is shown the first time, it also receives a resize event, and if that widget has a layout it automatically notifies that layout so that it can perform all computations.

    ADDENDUM , for the example in Question :

    with either grid.addWidget(self.resultText, 0, 0) or grid.addWidget(self.resultText, 0, 0, 1, 3) , output at start:

    enter image description here

    trying to resize grid.addWidget(self.resultText, 0, 0):

    enter image description here

    while with grid.addWidget(self.resultText, 0, 0, 1, 3)

    enter image description here