2025, Oct 03 17:18

Как центрировать виджет в PyQt5: растяжки и выравнивание

Разбираем, почему QLabel не центрируется в PyQt5 при AlignBottom, и показываем правильный способ: QVBoxLayout с addStretch. Пошаговый код и советы по путям.

Центрирование виджета в макете PyQt5 может оказаться неочевидным, если смешивать выравнивание на уровне макета и вызовы выравнивания на уровне самого виджета. Типичная ситуация: метку добавляют в QVBoxLayout с AlignBottom, затем другие элементы скрывают — и метка упорно остается у нижней кромки. Задача — после нажатия кнопки разместить метку по центру страницы.

Постановка задачи

Ниже — минимальный пример игры-угадайки, где после нажатия кнопки метка по-прежнему находится внизу, хотя мы пытаемся ее переориентировать:

import sys
import time

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

class AppWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        QFontDatabase.addApplicationFont("fonts\Evangelie.ttf")
        QFontDatabase.addApplicationFont("fonts\Bebas_Neue_Cyrillic.ttf")
        QFontDatabase.addApplicationFont("fonts\MontserratAlternates-Medium.ttf")
        QFontDatabase.addApplicationFont("fonts\MontserratAlternates-Bold.ttf")
        QFontDatabase.addApplicationFont("fonts\MontserratAlternates-SemiBold.ttf")

        self.msg_label = QLabel("Скільки чулувіків має крапка?", self)
        self.input_field = QLineEdit(self)
        self.submit_btn = QPushButton("Відповісти.", self)

        self.build_ui()

    def build_ui(self):
        host = QWidget()
        self.setCentralWidget(host)

        self.setWindowTitle("Guess.")
        self.resize(800, 700)
        self.setStyleSheet("background-color: #697565;")

        self.msg_label.setFont(QFont("Montserrat Alternates SemiBold", 24))
        self.msg_label.adjustSize()
        self.msg_label.setStyleSheet(
            "margin: 0, 20, 0, 0;"
            "color: #ECDFCC;"
            "font-style: bold;"
        )

        self.input_field.setFixedSize(200, 80)
        self.input_field.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.input_field.setFont(QFont("Montserrat Alternates SemiBold", 20))
        self.input_field.setStyleSheet(
            "background-color: #181C14;"
            "color: #697565;"
            "border-radius: 15px;"
            "margin: 0,20,0,0;"
        )

        self.submit_btn.setStyleSheet(
            "border-radius:15px;"
            "background-color: #ECDFCC;"
            "color: #181C14;"
        )
        self.submit_btn.setFixedSize(150, 50)
        self.submit_btn.setFont(QFont("Montserrat Alternates Medium", 10))
        self.submit_btn.clicked.connect(self.handle_click)

        layout_main = QVBoxLayout()
        layout_main.addWidget(self.msg_label, alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignJustify)
        layout_main.addWidget(self.input_field, alignment=Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignJustify)
        layout_main.addWidget(self.submit_btn, alignment=Qt.AlignmentFlag.AlignJustify | Qt.AlignmentFlag.AlignTop)
        host.setLayout(layout_main)

    def handle_click(self):
        self._user_value = self.input_field.text()
        print(self._user_value)
        if self._user_value == '0':
            self.msg_label.setText("You won!")
            self.input_field.hide()
            self.submit_btn.hide()
            self.msg_label.setAlignment(Qt.AlignmentFlag.AlignCenter)

    def center_window(self):
        frame_geo = self.frameGeometry()
        screen_center = QDesktopWidget().availableGeometry().center()
        frame_geo.moveCenter(screen_center)
        self.move(frame_geo.topLeft())


def main():
    app = QApplication(sys.argv)
    ui = AppWindow()
    ui.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

Почему метка не перемещается в центр

Вертикальное положение определяется выравниванием, указанным при добавлении виджета в макет. Метка была добавлена с AlignBottom, поэтому сохраняет свой вертикальный «слот» у нижней границы. Вызов setAlignment() для метки не меняет ее место в макете; он влияет лишь на выравнивание содержимого самой метки.

Решение: используйте растяжки для центрирования блока

Чтобы после нажатия кнопки метка оказалась по центру окна, добавьте гибкое пространство над и под группой виджетов. В QVBoxLayout вызов addStretch() создает расширяемую область. Разместив такие растяжки до и после ваших элементов, вы удержите контент по вертикали в центре. Дальше достаточно задать только горизонтальное выравнивание.

import sys
import time

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

class AppWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        QFontDatabase.addApplicationFont("fonts\Evangelie.ttf")
        QFontDatabase.addApplicationFont("fonts\Bebas_Neue_Cyrillic.ttf")
        QFontDatabase.addApplicationFont("fonts\MontserratAlternates-Medium.ttf")
        QFontDatabase.addApplicationFont("fonts\MontserratAlternates-Bold.ttf")
        QFontDatabase.addApplicationFont("fonts\MontserratAlternates-SemiBold.ttf")

        self.msg_label = QLabel("Скільки чулувіків має крапка?", self)
        self.input_field = QLineEdit(self)
        self.submit_btn = QPushButton("Відповісти.", self)

        self.build_ui()

    def build_ui(self):
        host = QWidget()
        self.setCentralWidget(host)

        self.setWindowTitle("Guess.")
        self.resize(800, 700)
        self.setStyleSheet("background-color: #697565;")

        self.msg_label.setFont(QFont("Montserrat Alternates SemiBold", 24))
        self.msg_label.adjustSize()
        self.msg_label.setStyleSheet(
            "margin: 0, 20, 0, 0;"
            "color: #ECDFCC;"
            "font-style: bold;"
        )

        self.input_field.setFixedSize(200, 80)
        self.input_field.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.input_field.setFont(QFont("Montserrat Alternates SemiBold", 20))
        self.input_field.setStyleSheet(
            "background-color: #181C14;"
            "color: #697565;"
            "border-radius: 15px;"
            "margin: 0,20,0,0;"
        )

        self.submit_btn.setStyleSheet(
            "border-radius:15px;"
            "background-color: #ECDFCC;"
            "color: #181C14;"
        )
        self.submit_btn.setFixedSize(150, 50)
        self.submit_btn.setFont(QFont("Montserrat Alternates Medium", 10))
        self.submit_btn.clicked.connect(self.handle_click)

        layout_main = QVBoxLayout()
        layout_main.addStretch()
        layout_main.addWidget(self.msg_label, alignment=Qt.AlignmentFlag.AlignHCenter)
        layout_main.addWidget(self.input_field, alignment=Qt.AlignmentFlag.AlignHCenter)
        layout_main.addWidget(self.submit_btn, alignment=Qt.AlignmentFlag.AlignHCenter)
        layout_main.addStretch()
        host.setLayout(layout_main)

    def handle_click(self):
        self._user_value = self.input_field.text()
        print(self._user_value)
        if self._user_value == '0':
            self.msg_label.setText("You won!")
            self.input_field.hide()
            self.submit_btn.hide()
            self.msg_label.setAlignment(Qt.AlignmentFlag.AlignCenter)

    def center_window(self):
        frame_geo = self.frameGeometry()
        screen_center = QDesktopWidget().availableGeometry().center()
        frame_geo.moveCenter(screen_center)
        self.move(frame_geo.topLeft())


def main():
    app = QApplication(sys.argv)
    ui = AppWindow()
    ui.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

Почему это важно знать

Незаметные флаги выравнивания могут приводить к неожиданному поведению интерфейса. Поддерживая вертикальный баланс с помощью addStretch() и задавая виджетам простое горизонтальное выравнивание, вы избавляете себя от борьбы с системой компоновки в дальнейшем, особенно при динамическом показе и скрытии элементов. И еще практический совет по путям в строках Python: обратная косая черта — это символ экранирования. Если используете обратные слэши в путях, пишите их двойными или применяйте необработанные строки; в противном случае используйте прямые слэши — Qt их повсеместно понимает, и они кроссплатформенны.

Итог

Если нужно, чтобы виджет перемещался в центр после изменений интерфейса, не пытайтесь добиться этого только выравниванием самого виджета. Поручите основную работу макету: окружите содержимое растяжками для вертикального центрирования и задайте каждому элементу простое AlignHCenter. Такой подход делает интерфейсы предсказуемыми, когда элементы скрываются, показываются или заменяются.

Статья основана на вопросе на StackOverflow от Krapka и ответе от bruno.