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. Такой подход делает интерфейсы предсказуемыми, когда элементы скрываются, показываются или заменяются.