Search code examples
pythonpyqtpysideqsizepolicy

What's the difference between QSizePolicy.Minimum and QSizePolicy.Preferred?


I read that "preferred policy is very flexible policy and the size of widget can be smaller and larger than sizeHint()."
But while I increased or decreased the size of windows, I couldn't find any differences between them.

< QSizePolicy.Minimum >

from PySide2 import QtWidgets

app = QtWidgets.QApplication([])

window = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout()
vMinimumButton = QtWidgets.QPushButton("h_Minimum, v_Fixed")
hMinimumButton = QtWidgets.QPushButton("h_Fixed, v_Minimum")
bMinimumButton = QtWidgets.QPushButton("h_Minimum, v_Minimum")

vMinimumButton.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
hMinimumButton.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum)
bMinimumButton.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)

layout.addWidget(vMinimumButton)
layout.addWidget(hMinimumButton)
layout.addWidget(bMinimumButton)
window.setLayout(layout)
window.show()

app.exec_()

< QSizePolicy.Preferred >

from PySide2 import QtWidgets

app = QtWidgets.QApplication([])

window = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout()
vPreferredButton = QtWidgets.QPushButton("h_Preferred, v_Fixed")
hPreferredButton = QtWidgets.QPushButton("h_Fixed, v_Preferred")
bPreferredButton = QtWidgets.QPushButton("h_Preferred, v_Preferred")

vPreferredButton.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
hPreferredButton.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
bPreferredButton.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)

layout.addWidget(vPreferredButton)
layout.addWidget(hPreferredButton)
layout.addWidget(bPreferredButton)
window.setLayout(layout)
window.show()

app.exec_()

Solution

  • I'd like to add some insight other than what provided by the succint but complete answer given by paxdiablo.

    First of all, Qt size policy names are, on first sight, counterintuitive.
    Understanding them requires careful and patient understanding on the documentation, and possibly some testing. Once you get the gist of it, you will probably realize the logic and assumptions behind that naming, and it actually makes sense (unintuitive doesn't always mean wrong: complex subjects require more awareness in order to avoid wrong choices only based on intuitions).

    The important thing to understand is that they always refer to the size hint of the widget, and they indicate how that hint is used as a reference.

    Maximum means that the hint will be used as the maximum size of the widget: it cannot be bigger than that size.
    Minimum means that the hint is the minimum size, so it cannot be smaller.
    Remember that minimum* and maximum* (with * being Size, Width or Height) override those aspects. Expanding means that the hint is the preferred size, and if there's some space left, that widget will try to use it.

    Then we have to consider that QWidget also has a minimumSizeHint, which is a recommended minimum size: if it returns a valid size, it can not be smaller (again, unless a minimum size is explicitly given).

    We also have to realize that some widgets have peculiar behavior, which also explains why you don't see differences in your code: QPushButton, for instance, returns sizeHint() even for minimumSizeHint(), it's based on its contents, and is only valid for its width; by default, Qt buttons have a fixed height.

    As always, experimenting is a perfect way to understand the behavior.
    The following is a basic example that better shows how policies work; using a basic QWidget as base allows avoiding unexpected behavior caused by widget-specific hints and policies.

    You can use context menus to set the policy, then eventually resize the window in order to see the result. The displayed value shows the current policy and size hint.

    from PySide2 import QtCore, QtGui, QtWidgets
    
    policyNames = 'Fixed', 'Minimum', 'Maximum', 'Preferred', 'Expanding'
    policyNameDict = {}
    policyValueDict = {}
    for name in policyNames:
        policy = getattr(QtWidgets.QSizePolicy, name)
        policyNameDict[policy] = name
        policyValueDict[name] = policy
    
    class TestWidget(QtWidgets.QWidget):
        def sizeHint(self):
            return QtCore.QSize(150, 30)
    
        def minimumSizeHint(self):
            return QtCore.QSize(10, 10)
    
        def contextMenuEvent(self, event):
            currentPolicy = self.sizePolicy()
            menu = QtWidgets.QMenu()
            group = QtWidgets.QActionGroup(menu, exclusive=True)
            for name in policyNames:
                action = group.addAction(name)
                action.setCheckable(True)
                policy = policyValueDict[name]
                action.setData(policy)
                if policy == currentPolicy.horizontalPolicy():
                    action.setChecked(True)
            menu.addActions(group.actions())
            res = menu.exec_(event.globalPos())
            if res:
                # PySide requires reconversion of the data, on PyQt the
                # res.data() alone is enough;
                currentPolicy.setHorizontalPolicy(
                    QtWidgets.QSizePolicy.Policy(res.data()))
                self.setSizePolicy(currentPolicy)
                self.update()
    
        def paintEvent(self, event):
            qp = QtGui.QPainter(self)
            qp.drawRect(self.rect().adjusted(0, 0, -1, -1))
            text = '{policy}\n{width}x{height}'.format(
                policy=policyNameDict[self.sizePolicy().horizontalPolicy()], 
                width=self.sizeHint().width(), 
                height=self.sizeHint().height(), 
            )
            qp.drawText(self.rect(), QtCore.Qt.AlignCenter, text)
    
    
    app = QtWidgets.QApplication([])
    
    window = QtWidgets.QWidget()
    layout = QtWidgets.QHBoxLayout(window)
    for i in range(3):
        layout.addWidget(TestWidget())
    window.show()
    
    app.exec_()