메뉴 닫기

PySide Qt for Python 레이아웃 완전정복 QVBoxLayout QHBoxLayout QGridLayout QFormLayout stretch sizePolicy spacing 가이드

PySide Qt for Python 레이아웃 완전정복 QVBoxLayout QHBoxLayout QGridLayout QFormLayout stretch sizePolicy spacing 가이드

🧩 버튼 하나도 흐트러지지 않는 PySide 레이아웃 설계법을 기초부터 실무 팁까지 한 번에 정리합니다

데스크톱 앱을 만들다 보면 버튼 위치가 미세하게 어긋나고 입력창이 리사이즈할 때 따라오지 않는 장면을 자주 마주하게 됩니다.
폼을 깔끔하게 잡는 능력은 결국 레이아웃을 얼마나 이해했는지에 달려 있죠.
PySide(Qt for Python)의 레이아웃 관리자는 위젯 배치를 자동으로 정렬하고 여백과 비율을 예측 가능하게 만들어 줍니다.
한 번 제대로 익혀 두면 화면 크기가 달라도 UI가 안정적으로 유지되고, 코드 수정량도 크게 줄어듭니다.
이 글에서는 초보자도 실수 없이 적용할 수 있도록 용어를 쉽고 명확하게 풀어 설명하고, 흔히 막히는 부분을 실제 사용 흐름에 맞춰 자연스럽게 해소해 보겠습니다.

핵심은 네 가지 기본 레이아웃과 세 가지 미세 조정 파라미터입니다.
수직·수평 정렬을 담당하는 QVBoxLayout과 QHBoxLayout, 격자 배치를 위한 QGridLayout, 라벨과 필드를 짝지어 주는 QFormLayout이 기본 축이 됩니다.
여기에 stretch, sizePolicy, spacing을 더하면 창 크기가 바뀌어도 요소 간 비율과 간격이 무너지지 않습니다.
어떤 상황에서 어떤 레이아웃을 선택할지, 그리고 서로를 어떻게 중첩해야 깔끔한 화면이 되는지 감을 빠르게 잡을 수 있도록 구성했습니다.
실무에서 바로 쓸 수 있는 예제와 체크리스트도 함께 담았습니다.



🔗 QVBoxLayout와 QHBoxLayout 기본 개념과 사용법

QVBoxLayout와 QHBoxLayout은 위젯을 각각 수직, 수평 방향으로 자동 정렬하는 가장 기본적인 레이아웃입니다.
가장 단순하지만 대부분의 화면은 이 둘의 중첩만으로도 충분히 설계할 수 있습니다.
핵심 개념은 ‘컨테이너 위젯에 레이아웃을 설정하고, 그 레이아웃에 자식 위젯을 추가한다’는 흐름입니다.
이때 컨테이너는 QMainWindow의 centralWidget, QWidget, 또는 대화상자 등 어떤 위젯이든 될 수 있습니다.
레이아웃은 창 크기 변화에 따라 각 자식의 크기와 위치를 다시 계산해 주므로 수동 배치보다 유지 보수가 훨씬 쉽습니다.

사용 순서는 일정합니다.
컨테이너 위젯 생성 → 레이아웃 인스턴스 생성 → addWidget, addLayout 등으로 요소 추가 → setLayout으로 컨테이너에 적용.
여기에 정렬 플래그(Qt.AlignLeft, Qt.AlignRight, Qt.AlignTop, Qt.AlignVCenter 등)를 옵션으로 전달해 세밀한 정렬을 맞출 수 있습니다.
버튼 막대, 툴바, 검색창처럼 가로로 나열할 때는 QHBoxLayout, 폼을 위에서 아래로 쌓을 때는 QVBoxLayout을 우선 고려하면 실패 확률이 낮습니다.

🧱 기본 패턴과 최소 예제

CODE BLOCK
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QHBoxLayout
from PySide6.QtCore import Qt
import sys

app = QApplication(sys.argv)

root = QWidget()
root.setWindowTitle("VBox / HBox 기본")

# 상단: 가로 버튼 바
bar = QHBoxLayout()
bar.addWidget(QPushButton("새로고침"))
bar.addWidget(QPushButton("저장"), 1)  # stretch factor 1
bar.addWidget(QPushButton("닫기"))

# 본문: 세로 스택
vbox = QVBoxLayout(root)   # 컨테이너에 바로 바인딩
vbox.addLayout(bar)        # 레이아웃 중첩
vbox.addWidget(QPushButton("콘텐츠 A"), alignment=Qt.AlignLeft)
vbox.addWidget(QPushButton("콘텐츠 B"))

root.setLayout(vbox)       # 이미 parent로 바인딩했더라도 setLayout 호출로 명시
root.resize(420, 260)
root.show()
sys.exit(app.exec())

위 코드의 포인트는 레이아웃을 서로 중첩해 구조를 만드는 것입니다.
QHBoxLayout로 상단 버튼 막대를 만들고, QVBoxLayout의 첫 줄에 addLayout으로 끼워 넣는 패턴은 실무에서 매우 자주 등장합니다.
또한 addWidget의 두 번째 인수나 별도의 addStretch를 사용해 남는 공간 분배를 제어할 수 있습니다.
stretch 값이 큰 요소가 더 많은 공간을 차지하며, 생략하면 기본값 0으로 최소 크기만 확보합니다.

🧭 언제 VBox, 언제 HBox를 선택할까

상황 권장 레이아웃
툴바·버튼 줄을 한 줄로 정렬 QHBoxLayout
폼 요소를 위에서 아래로 나열 QVBoxLayout
좌측 네비게이션 + 우측 콘텐츠 2분할 QHBoxLayout로 1차 분할, 각 영역 내부는 QVBoxLayout
  • 🛠️컨테이너 위젯에 setLayout 호출을 잊지 않았는지 확인
  • ⚙️addWidget(widget, stretch, alignment) 인자의 역할을 정확히 이해하고 필요한 곳에만 적용
  • 🧩상위는 QHBoxLayout으로 큰 분할, 하위는 QVBoxLayout로 세부 배치처럼 중첩을 적극 활용
  • 🧭정렬이 필요하면 alignment에 Qt.Align* 플래그 전달

⚠️ 주의: 같은 위젯에 레이아웃을 두 번 설정하거나, 한 위젯을 여러 레이아웃에 동시에 추가하면 예외가 발생합니다.
addWidget로 이미 다른 부모를 가진 위젯을 다시 추가하려 하지 않았는지 확인하세요.

💡 TIP: 여백은 레이아웃의 setContentsMargins와 setSpacing으로 전역 조절하고, 특정 간격만 넓히고 싶다면 addSpacing 또는 QSpacerItem을 활용하세요.

🛠️ QGridLayout로 반응형 그리드 만들기

QGridLayout은 이름 그대로 격자(grid) 형태로 위젯을 배치할 때 사용하는 레이아웃입니다.
웹의 테이블처럼 행(row)과 열(column)을 지정해 요소를 정렬할 수 있으며, 셀 병합(colspan, rowspan)도 지원합니다.
대시보드, 이미지 갤러리, 설정창 등 균일한 공간 배분이 필요한 화면에 적합합니다.
위젯 크기를 자동으로 조절하면서도 각 영역을 예측 가능하게 제어할 수 있어 ‘반응형’ GUI를 구현하기에 매우 유용합니다.

QGridLayout의 핵심 메서드는 addWidget(widget, row, column, rowspan, colspan)입니다.
행과 열을 지정하면 해당 좌표 위치에 위젯이 삽입되며, rowspan과 colspan을 이용하면 셀을 병합해 큰 영역을 차지하도록 할 수 있습니다.
또한 setRowStretch, setColumnStretch, setSpacing, setAlignment 같은 옵션을 통해 간격과 비율을 세밀하게 조절할 수 있습니다.

📐 기본 사용 예제

CODE BLOCK
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QGridLayout
import sys

app = QApplication(sys.argv)

window = QWidget()
window.setWindowTitle("QGridLayout 예제")

layout = QGridLayout()

layout.addWidget(QPushButton("(0,0)"), 0, 0)
layout.addWidget(QPushButton("병합 (0,1)-(0,2)"), 0, 1, 1, 2)
layout.addWidget(QPushButton("(1,0)"), 1, 0)
layout.addWidget(QPushButton("(1,1)"), 1, 1)
layout.addWidget(QPushButton("(1,2)"), 1, 2)

layout.setColumnStretch(0, 1)
layout.setColumnStretch(1, 2)
layout.setSpacing(10)

window.setLayout(layout)
window.resize(400, 250)
window.show()
sys.exit(app.exec())

이 예제에서는 첫 번째 행의 두 번째 버튼이 두 칸을 병합해 넓게 배치됩니다.
setColumnStretch를 통해 각 열의 비율을 다르게 지정하면 창 크기가 바뀌어도 균형이 유지됩니다.
실제 폼 UI나 대시보드 구성 시에는 이런 비율 제어가 디자인 일관성을 유지하는 핵심 포인트가 됩니다.

📊 QGridLayout을 선택해야 하는 상황

상황 이유
폼 입력 항목이 행과 열로 정렬되어야 할 때 QGridLayout은 행·열 기준으로 라벨과 입력칸을 나란히 배치 가능
창 크기가 변해도 비율을 유지해야 할 때 setColumnStretch, setRowStretch로 자동 비율 조정 가능
정확한 위치 배치가 필요한 대시보드 형태 셀 좌표 기반 배치로 수동 배치보다 구조적

💎 핵심 포인트:
QGridLayout은 위젯 간 상대적 비율을 조정하기에 적합하며, stretch를 잘 설정하면 완벽한 반응형 GUI를 구현할 수 있습니다.

💬 QGridLayout은 격자 단위로 화면을 구성하기 때문에,
복잡한 폼이나 테이블 구조를 단순한 수평·수직 배치보다 훨씬 명확하게 표현할 수 있습니다.



⚙️ QFormLayout으로 설정 화면 구성하기

QFormLayout은 라벨과 입력 필드를 짝지어 배치하기에 최적화된 레이아웃입니다.
로그인 화면, 회원가입 폼, 환경설정 창처럼 ‘항목 : 입력’ 구조를 반복하는 경우 QFormLayout을 쓰면 코드가 간결해지고, 정렬도 자동으로 맞춰집니다.
특히 라벨과 위젯의 폭이 균등하게 배분되며, 다국어 UI에서도 자동으로 폭을 재조정해 주는 점이 강점입니다.

레이아웃 객체를 생성한 뒤 addRow(label, widget) 또는 addRow(label_text, widget) 형태로 간단히 추가할 수 있습니다.
또한 addItem, addWidget, addLayout을 이용하면 일반 위젯이나 중첩 레이아웃도 삽입 가능하여 복합적인 폼 구성에도 유연하게 대응합니다.
Qt Designer에서 생성한 폼과 동일한 구조를 코드로 재현할 수 있어 유지보수에도 유리합니다.

🧾 기본 폼 구성 예제

CODE BLOCK
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QLineEdit, QPushButton, QFormLayout
import sys

app = QApplication(sys.argv)

window = QWidget()
window.setWindowTitle("QFormLayout 예제")

form = QFormLayout()
form.addRow(QLabel("사용자명:"), QLineEdit())
form.addRow(QLabel("비밀번호:"), QLineEdit())
form.addRow(QLabel("이메일:"), QLineEdit())
form.addRow(QPushButton("가입하기"))

window.setLayout(form)
window.resize(350, 200)
window.show()
sys.exit(app.exec())

이처럼 QFormLayout은 수직으로 폼 항목을 자동 정렬하고, 라벨 영역과 입력 영역의 비율을 자동 조정합니다.
여기에 setFormAlignmentsetLabelAlignment을 사용하면 정렬 방향을 통일할 수 있습니다.
예를 들어 setLabelAlignment(Qt.AlignRight)를 적용하면 모든 라벨이 우측 정렬되어 시각적으로 안정된 구성이 됩니다.

🧩 QFormLayout의 RowWrapPolicy

QFormLayout은 긴 라벨이나 작은 창에서도 자동 줄바꿈 기능을 제공합니다.
setRowWrapPolicy()를 통해 지정할 수 있으며, 대표적인 옵션은 다음과 같습니다.

옵션 설명
QFormLayout.DontWrapRows 기본값. 라벨과 위젯이 한 줄에 고정
QFormLayout.WrapLongRows 창이 좁아질 경우 다음 줄로 위젯을 자동 이동
QFormLayout.WrapAllRows 모든 행을 2줄 형태로 표시 (모바일 UI 등에 적합)

💎 핵심 포인트:
폼 레이아웃은 단순히 항목을 나열하는 것을 넘어, 라벨 정렬·줄바꿈 정책·간격 조정 등을 일괄 관리할 수 있는 강력한 도구입니다.

  • 🧮addRow()를 일관성 있게 사용하고, 라벨과 필드 개수를 맞춘다.
  • ⚙️창 크기가 작아질 때 WrapLongRows 옵션을 활용해 가독성 유지.
  • 📏라벨 정렬을 우측으로 통일하면 시각적 안정감 향상.
  • 🧩다른 레이아웃(QVBox/QHBox)을 행 내부에 addLayout()으로 중첩 가능.

🔌 stretch sizePolicy spacing 레이아웃 미세 조정

PySide에서 레이아웃을 섬세하게 제어하려면 stretch, sizePolicy, spacing의 세 가지 속성을 반드시 이해해야 합니다.
이들은 위젯 간의 여백, 공간 비율, 확장 동작을 조정해 전체 레이아웃의 밸런스를 잡아주는 요소입니다.
단순히 addWidget으로 배치하는 것만으로는 원하는 결과가 나오지 않을 때, 이 속성들을 조합하면 디테일한 완성도를 얻을 수 있습니다.

⚖️ stretch로 공간 비율 조절하기

stretch는 위젯 사이의 남는 공간을 어떻게 분배할지를 결정합니다.
레이아웃의 addStretch()addWidget(widget, stretch)에서 지정하며, 숫자가 클수록 더 많은 공간을 차지합니다.

CODE BLOCK
layout = QHBoxLayout()
layout.addWidget(QPushButton("왼쪽"), 1)
layout.addWidget(QPushButton("오른쪽"), 3)  # 오른쪽 버튼이 더 넓게 확장

이 예제에서 오른쪽 버튼은 stretch=3으로 설정되어, 왼쪽 버튼보다 세 배의 여유 공간을 갖게 됩니다.
레이아웃 내 여러 위젯이 균형 잡히도록 시각적 비율을 맞출 때 매우 유용합니다.

📏 sizePolicy로 위젯 확장 동작 제어

sizePolicy는 위젯이 주어진 공간을 어떻게 사용할지를 정의합니다.
기본적으로 모든 위젯은 QSizePolicy.Preferred 정책을 따르지만, 이를 적절히 바꾸면 특정 위젯만 가로 또는 세로로 확장되게 만들 수 있습니다.

CODE BLOCK
from PySide6.QtWidgets import QSizePolicy

button = QPushButton("확장 버튼")
policy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
button.setSizePolicy(policy)

위 코드에서 가로 방향은 확장(Expanding), 세로는 고정(Fixed)으로 설정되어, 창 크기를 늘려도 버튼의 높이는 그대로 유지됩니다.
이처럼 sizePolicy를 세밀하게 조절하면 UI 균형이 훨씬 자연스러워집니다.

🧩 spacing과 margin으로 여백 다듬기

spacing은 위젯 간의 간격을, margin은 레이아웃 외곽의 여백을 의미합니다.
각각 setSpacing(value)setContentsMargins(left, top, right, bottom)으로 지정합니다.

CODE BLOCK
layout.setSpacing(12)
layout.setContentsMargins(10, 15, 10, 15)

이 설정으로 전체 여백이 균일해지고, 위젯 간 시각적 간격이 일정하게 유지됩니다.
PySide 애플리케이션에서 UI 품질을 높이려면 이런 간격 조정이 필수적입니다.

💎 핵심 포인트:
stretch는 비율, sizePolicy는 확장성, spacing은 간격을 제어합니다. 이 세 가지를 조합하면 디자이너 없이도 균형 잡힌 UI를 만들 수 있습니다.

  • ⚙️stretch는 상대적 비율로 공간 분배를 제어.
  • 📐sizePolicy로 특정 위젯만 확장 또는 고정.
  • 🧱spacing / margin은 시각적 여백 조정에 사용.
  • 🎨프로토타입 단계에서도 이 세 속성을 설정하면 완성도가 높아짐.



💡 실무 예제 PySide6로 로그인 창 배치하기

지금까지 살펴본 QVBoxLayout, QHBoxLayout, QGridLayout, QFormLayout, 그리고 stretch, sizePolicy, spacing 등을 종합적으로 활용하면
실무 수준의 로그인 화면을 손쉽게 구성할 수 있습니다.
이번 예제에서는 PySide6를 사용해 반응형으로 동작하는 로그인 창을 직접 구현해 보겠습니다.
이 예제는 화면 크기 변화에도 레이아웃이 자연스럽게 유지되는 점을 확인할 수 있습니다.

🔐 PySide6 로그인 레이아웃 예제 코드

CODE BLOCK
from PySide6.QtWidgets import (
    QApplication, QWidget, QLabel, QLineEdit, QPushButton,
    QVBoxLayout, QHBoxLayout, QFormLayout
)
from PySide6.QtCore import Qt
import sys

app = QApplication(sys.argv)

class LoginWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("로그인 화면 예제")

        # 폼 영역
        form = QFormLayout()
        form.addRow("아이디", QLineEdit())
        form.addRow("비밀번호", QLineEdit())

        # 버튼 영역
        btn_login = QPushButton("로그인")
        btn_cancel = QPushButton("취소")

        button_box = QHBoxLayout()
        button_box.addStretch(1)
        button_box.addWidget(btn_login)
        button_box.addWidget(btn_cancel)

        # 전체 레이아웃
        layout = QVBoxLayout()
        layout.addLayout(form)
        layout.addLayout(button_box)
        layout.setSpacing(12)
        layout.setContentsMargins(30, 30, 30, 30)

        self.setLayout(layout)
        self.resize(350, 200)

window = LoginWindow()
window.show()
sys.exit(app.exec())

이 예제는 QFormLayout으로 입력 폼을 구성하고, QHBoxLayout으로 버튼을 정렬한 뒤,
QVBoxLayout으로 전체를 감싸는 구조입니다.
버튼 영역에는 addStretch()를 추가해 버튼들이 오른쪽으로 정렬되며,
setSpacing과 setContentsMargins로 여백을 통일해 깔끔한 정렬이 완성됩니다.
이와 같은 구조는 실무에서 거의 모든 폼 화면의 기본 패턴으로 활용됩니다.

💎 핵심 포인트:
QFormLayout으로 입력 구조를 만들고, QHBoxLayout으로 버튼 정렬, QVBoxLayout으로 전체를 감싸면 가장 안정적인 UI 레이아웃 패턴을 구현할 수 있습니다.

🎨 시각적 품질 향상을 위한 추가 팁

  • 위젯 사이 여백은 layout.setSpacing()으로 일관성 있게 조정합니다.
  • 🧭텍스트 입력창의 폭을 일정하게 유지하려면 setFixedWidth() 또는 sizePolicy를 병행합니다.
  • 🎯창 크기 변경 시 비율이 깨지지 않도록 QVBoxLayout과 QHBoxLayout을 중첩해 구성합니다.
  • 💡레이아웃마다 setContentsMargins()를 달리하면 시각적으로 레이어감이 생깁니다.

💬 레이아웃 설계의 핵심은 “화면이 바뀌어도 흐트러지지 않는 정렬”입니다.
stretch와 spacing, 그리고 여백 설정을 균형 있게 활용하는 습관이 좋은 UX를 만듭니다.

자주 묻는 질문 (FAQ)

QVBoxLayout와 QHBoxLayout을 같이 써도 되나요?
네, 오히려 대부분의 PySide 레이아웃은 두 레이아웃의 중첩으로 구성됩니다.
큰 틀은 QHBoxLayout으로 나누고 내부 세부 영역은 QVBoxLayout으로 쌓는 구조가 가장 일반적입니다.
QGridLayout에서 셀 간 여백을 조절하려면 어떻게 하나요?
setSpacing()을 사용하면 모든 셀 간의 기본 간격을 조정할 수 있습니다.
개별 여백을 조정하려면 QSpacerItem을 특정 위치에 추가할 수도 있습니다.
QFormLayout 대신 QGridLayout을 써도 되나요?
가능합니다.
다만 QFormLayout은 라벨과 필드의 짝을 자동으로 맞춰 주기 때문에 폼 구조에는 훨씬 효율적입니다.
QGridLayout은 더 복잡한 격자 배치에 적합합니다.
stretch와 sizePolicy 중 어느 것이 더 우선인가요?
sizePolicy가 위젯의 확장 가능성을 먼저 정의하고, stretch는 그 후 남는 공간 분배를 결정합니다.
즉, 두 설정을 병행하면 더 정밀한 제어가 가능합니다.
레이아웃 여백이 너무 커 보일 때는 어떻게 하나요?
각 레이아웃의 setContentsMargins(0,0,0,0)으로 외곽 여백을 줄이거나,
setSpacing()으로 내부 간격을 조정하세요.
한 위젯에 여러 레이아웃을 동시에 적용할 수 있나요?
한 위젯에는 하나의 메인 레이아웃만 직접 설정할 수 있습니다.
여러 레이아웃을 사용하려면 addLayout으로 서로 중첩시켜야 합니다.
QVBoxLayout 안에 QGridLayout을 넣어도 되나요?
네, 가능합니다.
이런 구조는 로그인 폼이나 대시보드처럼 복합적인 UI에서 흔히 사용됩니다.
addLayout으로 자유롭게 중첩하면 됩니다.
레이아웃을 사용하지 않고도 위젯 위치를 지정할 수 있나요?
가능하지만 권장되지 않습니다.
setGeometry로 수동 배치하면 해상도나 창 크기가 바뀔 때 UI가 깨질 수 있습니다.
유지보수와 반응형 디자인을 고려하면 반드시 레이아웃 관리자를 사용하는 것이 좋습니다.

🧠 PySide 레이아웃 관리의 모든 핵심 정리

PySide(Qt for Python)에서 레이아웃은 단순한 배치 도구가 아니라, 애플리케이션의 구조적 안정성을 결정하는 핵심 요소입니다.
QVBoxLayout과 QHBoxLayout으로 수직·수평 배치를 구성하고, QGridLayout으로 반응형 격자를 만들며, QFormLayout으로 폼형 입력창을 깔끔하게 정리할 수 있습니다.
여기에 stretch, sizePolicy, spacing을 함께 조합하면 창 크기가 변하더라도 모든 위젯이 자연스럽게 비율을 유지하며 재배치됩니다.

레이아웃 관리자의 가장 큰 장점은 ‘예측 가능한 정렬’입니다.
코드를 몇 줄만 추가해도 위젯의 위치, 크기, 여백이 자동으로 계산되어 복잡한 수동 배치를 대체합니다.
특히 실무에서는 QHBoxLayout과 QVBoxLayout을 중첩하는 구조가 가장 많이 쓰이며, 거기에 QGridLayout이나 QFormLayout을 필요한 부분에만 결합하는 방식이 효율적입니다.

stretch는 공간 비율을 제어하고, sizePolicy는 확장 동작을 조절하며, spacing은 시각적 여백을 정리합니다.
이 세 가지를 적절히 조합하면 디자이너의 도움 없이도 완성도 높은 GUI를 구축할 수 있습니다.
결국, PySide의 레이아웃 시스템을 제대로 이해한다면 유지보수가 쉬운 구조적 코드를 작성할 수 있으며, 다양한 해상도에서도 균형 잡힌 UI를 구현할 수 있습니다.


🏷️ 관련 태그 : PySide6, QtforPython, QVBoxLayout, QHBoxLayout, QGridLayout, QFormLayout, stretch, sizePolicy, spacing, GUI레이아웃