Search code examples
pythonqtpyqtpyqt5

PyQt5--How do I prevent a widget from resizing the window?


I have essentially two buttons in a grid layout, with code written to save window size when refreshing the program. However, this presents one issue--despite having the window size saved, the window will always expand slightly, which becomes more and more noticeable as you reload the program again and again.

Here's my code if you want to see the effect:

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from configparser import ConfigParser

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.loadDefaults()
        self.resize(int(self.windowWidth),int(self.windowHeight))
        self.setSizePolicy(QSizePolicy.Policy.Fixed,QSizePolicy.Policy.Fixed)
        size=QSize(int(self.windowWidth),int(self.windowHeight))
        container=QWidget()
        layout=QGridLayout()

        button=QPushButton("button1")
        button2=QPushButton("button2")

        layout.addWidget(button)
        layout.addWidget(button2)

        container.setLayout(layout)
        self.setCentralWidget(container)

    def resizeEvent(self, a0: QResizeEvent) -> None:
        self.windowWidth=self.frameGeometry().width()
        self.windowHeight=self.frameGeometry().height()
        self.saveSettings()
        return super().resizeEvent(a0)

    def loadDefaults(self):
        config=ConfigParser()
        config.read('config.ini')
        try:
            config.add_section('settings')
            config['settings']={
                'windowWidth':int(self.screen().size().width()/2),
                'windowHeight':int(self.screen().size().height()/2),
                }
            self.windowWidth=self.screen().size().width()/2
            self.windowHeight=self.screen().size().height()/2
            print("no ini file")
        except:
            self.windowWidth=config['settings'].getint('windowWidth')
            self.windowHeight=config['settings'].getint('windowHeight')
            print("ini file found")
        with open('config.ini', 'w') as f:
            config.write(f)

    def saveSettings(self):
        config=ConfigParser()
        config.read('config.ini')
        config['settings']={
            'windowWidth':self.windowWidth,
            'windowHeight':self.windowHeight,
            }
        with open('config.ini', 'w') as f:
            config.write(f)

app=QApplication(sys.argv)
window=MainWindow()
window.show()
sys.exit(app.exec())

How can I prevent the window from resizing due to its child widgets, without forcing either of those aspects to have a fixed size?

I've looked into several posts:

  • This post suggests reimplementing the QSizeHint() constant, but I'm not sure how to translate the C++ code given to Python.
  • This post asks what I believe is the same question, but it remains unanswered.
  • Other posts asking similar questions tend have answers just telling them to use fixed sizes.

Solution

  • The problem is that you are using the frameGeometry() to get the current size, but that is inconsistent with resize() (or its main counterpart, setGeometry()), as shown in the documentation:

    Image showing how Qt geometries are used

    Using frameGeometry() you will get the outer boundaries of the window frame, but the documentation about the size property (including the setter function resize()) is clear:

    This property holds the size of the widget excluding any window frame

    The result is that you're always storing a larger window size, and, since resize() doesn't consider the window frame, when you reload the configuration the window will be bigger, and the increment will exactly be the size of the frame width.

    So, the simplest solution would be to use size() instead of frameGeometry().

    Be aware, though, that Qt already provides this functionality, and that's through the saveGeometry() and restoreGeometry() functions.

    Now the problem is that the returned data is a QByteArray (fundamentally, a byte array), and ConfigParser only supports ordinary strings. While you could try to convert that data to strings (for instance, using hex or base64), Qt can still help us: use the QSettings API instead, which supports a more flexible way of storing data.

    class MainWindow(QMainWindow):
        def __init__(self):
            super().__init__()
            container = QWidget()
            layout = QGridLayout(container)
    
            button = QPushButton("button1")
            button2 = QPushButton("button2")
    
            layout.addWidget(button)
            layout.addWidget(button2)
    
            self.setCentralWidget(container)
    
            self.settings = QSettings()
            if self.settings.contains('geometry'):
                try:
                    self.restoreGeometry(self.settings.value('geometry'))
                except TypeError:
                    pass
    
        def resizeEvent(self, a0: QResizeEvent) -> None:
            self.settings.setValue('geometry', self.saveGeometry())
            return super().resizeEvent(a0)
    
    
    app = QApplication(sys.argv)
    app.setOrganizationName('My Company')
    app.setApplicationName('My App')
    window = MainWindow()
    window.show()
    sys.exit(app.exec())
    

    Note that the size policy is only considered for widgets added to layout managers; setting it for a top level window is completely useless.

    If you want to prevent the user to resize the window, do any of the following:

    • call self.setMaximumSize(self.sizeHint()) after adding all widgets to the window (usually, near the end of the __init__);
    • use the MSWindowsFixedSizeDialogHint window flag: self.setWindowFlag(Qt.MSWindowsFixedSizeDialogHint);
    • assuming that the window has a layout (and it should), use window.layout().setSizeConstraint(QLayout.SetFixedSize); note that if you're using a QMainWindow, you have to call that on the layout of the main window itself, not on that of the central widget;