<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Python GUIs - qsizepolicy</title><link href="https://www.pythonguis.com/" rel="alternate"/><link href="https://www.pythonguis.com/feeds/qsizepolicy.tag.atom.xml" rel="self"/><id>https://www.pythonguis.com/</id><updated>2021-04-15T09:00:00+00:00</updated><subtitle>Create GUI applications with Python and Qt</subtitle><entry><title>Removing Gaps Between Custom Widgets in PyQt6 Layouts — Use size policies and size hints to control how custom-painted widgets fit together</title><link href="https://www.pythonguis.com/faq/custom-widgets-without-a-gap-between-each-widget/" rel="alternate"/><published>2021-04-15T09:00:00+00:00</published><updated>2021-04-15T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2021-04-15:/faq/custom-widgets-without-a-gap-between-each-widget/</id><summary type="html">I've created custom widgets using QPainter (a button and a top bar) and placed them in a layout, but there's a visible gap between them. How can I line up custom widgets in a layout without any space between them?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;I've created custom widgets using QPainter (a button and a top bar) and placed them in a layout, but there's a visible gap between them. How can I line up custom widgets in a layout without any space between them?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When you build custom widgets with &lt;code&gt;QPainter&lt;/code&gt; and place them into a layout, Qt doesn't automatically know how big each widget should be or how it should behave when the window is resized. Without that information, the layout manager distributes extra space between your widgets &amp;mdash; and that's where those unwanted gaps come from.&lt;/p&gt;
&lt;p&gt;The solution involves two things: &lt;strong&gt;size policies&lt;/strong&gt; and &lt;strong&gt;size hints&lt;/strong&gt;. Together, these tell the layout exactly how your widgets should be sized and whether they should stretch to fill available space.&lt;/p&gt;
&lt;h2 id="what-are-size-policies-and-size-hints"&gt;What are size policies and size hints?&lt;/h2&gt;
&lt;p&gt;Every widget in Qt has a &lt;em&gt;size hint&lt;/em&gt; &amp;mdash; a preferred default size &amp;mdash; and a &lt;em&gt;size policy&lt;/em&gt; &amp;mdash; a rule that describes how the widget should grow or shrink relative to that preferred size.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A top bar should expand horizontally to fill the width of the window, but stay at a fixed height.&lt;/li&gt;
&lt;li&gt;A button should stay at a fixed size in both directions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You communicate these intentions to the layout system using &lt;code&gt;QSizePolicy&lt;/code&gt; and by implementing the &lt;code&gt;sizeHint()&lt;/code&gt; method on your widget class.&lt;/p&gt;
&lt;h2 id="setting-a-size-hint"&gt;Setting a size hint&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;sizeHint()&lt;/code&gt; method returns a &lt;code&gt;QSize&lt;/code&gt; that tells the layout what your widget's ideal dimensions are. You override it in your custom widget class like this:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def sizeHint(self):
    return QtCore.QSize(80, 80)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This gives the layout a starting point. Without it, the layout has to guess &amp;mdash; and it often guesses wrong.&lt;/p&gt;
&lt;h2 id="setting-a-size-policy"&gt;Setting a size policy&lt;/h2&gt;
&lt;p&gt;A size policy tells the layout how flexible the widget is in each direction. You set it with &lt;code&gt;setSizePolicy()&lt;/code&gt;. The two most useful policies here are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;QSizePolicy.Policy.Fixed&lt;/code&gt; &amp;mdash; the widget stays exactly at its size hint.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QSizePolicy.Policy.Expanding&lt;/code&gt; &amp;mdash; the widget grows to fill available space.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a top bar that stretches horizontally but keeps a fixed height:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;self.setSizePolicy(
    QSizePolicy.Policy.Expanding,
    QSizePolicy.Policy.Fixed,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;For a button that should remain a constant size:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;self.setSizePolicy(
    QSizePolicy.Policy.Fixed,
    QSizePolicy.Policy.Fixed,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When the bar is set to &lt;code&gt;Expanding&lt;/code&gt; horizontally and the button is &lt;code&gt;Fixed&lt;/code&gt;, the bar will grow to consume all remaining space in the layout &amp;mdash; leaving no gap between the two widgets.&lt;/p&gt;
&lt;h2 id="common-mistakes-to-avoid"&gt;Common mistakes to avoid&lt;/h2&gt;
&lt;p&gt;There are a couple of pitfalls that can cause unexpected layout behavior with custom widgets.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don't override &lt;code&gt;self.width&lt;/code&gt; or &lt;code&gt;self.height&lt;/code&gt; with plain attributes.&lt;/strong&gt; Qt widgets have built-in methods called &lt;code&gt;self.width()&lt;/code&gt; and &lt;code&gt;self.height()&lt;/code&gt;. If you write &lt;code&gt;self.width = 200&lt;/code&gt; in your &lt;code&gt;__init__&lt;/code&gt;, you replace the method with an integer, and other parts of Qt that call &lt;code&gt;self.width()&lt;/code&gt; will break. Instead, use your own attribute names (like &lt;code&gt;self._bar_width&lt;/code&gt;) or rely on &lt;code&gt;sizeHint()&lt;/code&gt; to communicate sizes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don't call &lt;code&gt;self.resize()&lt;/code&gt; inside &lt;code&gt;paintEvent&lt;/code&gt;.&lt;/strong&gt; Resizing a widget triggers a repaint, and if your &lt;code&gt;paintEvent&lt;/code&gt; calls &lt;code&gt;resize()&lt;/code&gt;, you can end up in an infinite loop. Let the layout handle sizing based on your size hint and size policy.&lt;/p&gt;
&lt;h2 id="putting-it-all-together"&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;Here's a complete working example showing a custom top bar and a custom hamburger-menu button sitting side by side with no gap. The bar expands to fill horizontal space while the button stays fixed at 80&amp;times;80 pixels.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys

from PyQt6 import QtWidgets, QtCore, QtGui
from PyQt6.QtWidgets import QSizePolicy

class TopBar(QtWidgets.QWidget):
    clicked = QtCore.pyqtSignal(str)

    def __init__(self, name, parent=None):
        super().__init__(parent)
        self._name = name
        self._text_size = 30
        self.setSizePolicy(
            QSizePolicy.Policy.Expanding,
            QSizePolicy.Policy.Fixed,
        )

    def sizeHint(self):
        return QtCore.QSize(200, 80)

    def paintEvent(self, event):
        width = event.rect().width()
        height = self.height()

        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing)

        # Draw background
        bar_rect = QtCore.QRect(0, 0, width, height)
        painter.fillRect(
            bar_rect, QtGui.QBrush(QtGui.QColor(160, 166, 167, 255))
        )

        # Draw text
        font = QtGui.QFont("Arial", self._text_size)
        painter.setFont(font)
        text_rect = QtCore.QRect(20, 0, width - 20, height)
        painter.drawText(
            text_rect, QtCore.Qt.AlignmentFlag.AlignVCenter, self._name
        )

        painter.end()

class CustomButton(QtWidgets.QWidget):
    clicked = QtCore.pyqtSignal(bool)

    def __init__(self, parent=None):
        super().__init__(parent)
        self._mouse_checked = False
        self._mouse_over = False
        self._number_lines = 3
        self.setMouseTracking(True)
        self.setSizePolicy(
            QSizePolicy.Policy.Fixed,
            QSizePolicy.Policy.Fixed,
        )

    def sizeHint(self):
        return QtCore.QSize(80, 80)

    def paintEvent(self, event):
        painter = QtGui.QPainter(self)
        painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing)

        # Draw background
        painter.setBrush(QtGui.QColor(160, 166, 167, 255))
        painter.setPen(QtCore.Qt.PenStyle.NoPen)
        rect = QtCore.QRect(0, 0, self.width(), self.height())
        painter.drawRect(rect)

        # Draw pressed state
        if self._mouse_checked:
            painter.setBrush(QtGui.QColor("#c00000"))
            painter.setPen(QtGui.QColor("#c00000"))
            painter.drawRect(rect)

        # Draw hamburger lines
        painter.setPen(QtGui.QColor(71, 77, 78))
        painter.setBrush(QtGui.QColor(71, 77, 78))
        for i in range(self._number_lines):
            y = int(
                (i + 1) * self.height() / ((self._number_lines - 1) * 2)
            )
            line_rect = QtCore.QRect(
                int(self.width() * 0.1), y, int(self.width() * 0.8), 5
            )
            painter.drawRect(line_rect)

        painter.end()

    def mousePressEvent(self, event):
        self._mouse_checked = True
        self.update()

    def mouseReleaseEvent(self, event):
        self._mouse_checked = False
        self.clicked.emit(True)
        self.update()

    def enterEvent(self, event):
        self._mouse_over = True

    def leaveEvent(self, event):
        self._mouse_over = False

class MainWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Custom Widgets &amp;mdash; No Gaps")

        menu_button = CustomButton(self)
        menu_button.clicked.connect(self.menu_pressed)

        top_bar = TopBar("My Application", self)

        top_layout = QtWidgets.QHBoxLayout()
        top_layout.setSpacing(0)
        top_layout.setContentsMargins(0, 0, 0, 0)
        top_layout.addWidget(menu_button)
        top_layout.addWidget(top_bar)

        content_area = QtWidgets.QLabel("Content goes here")
        content_area.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)

        main_layout = QtWidgets.QVBoxLayout(self)
        main_layout.setSpacing(0)
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.addLayout(top_layout)
        main_layout.addWidget(content_area)

    def menu_pressed(self):
        print("Menu button pressed!")

def main():
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.resize(600, 400)
    window.show()
    sys.exit(app.exec())

if __name__ == "__main__":
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When you run this, the button and bar sit flush against each other at the top of the window. Resize the window and the bar stretches to fill the space while the button stays the same size &amp;mdash; exactly what you'd expect from a toolbar layout.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.martinfitzpatrick.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="custom-widgets"/><category term="layouts"/><category term="qpainter"/><category term="qsizepolicy"/><category term="python"/><category term="qt"/><category term="qt6"/><category term="pyqt6-custom-widgets"/></entry></feed>