PySide6로 데스크톱 앱 만들기 #2 위젯과 레이아웃 — 화면을 조립하는 법
지난 글에서 버튼 하나가 창을 꽉 채운 앱을 만들었습니다. 실제 앱의 화면은 입력창, 목록, 버튼 여러 개가 정해진 자기 위치에 놓여야 하고, 창 크기를 바꿔도 배치가 자연스럽게 따라와야 합니다. 이번 글은 그 배치를 담당하는 두 가지 재료, 위젯과 레이아웃을 다룹니다.
총 7편으로 구성됩니다.
- #1 PySide6란 — Qt와 파이썬으로 데스크톱 앱
- #2 위젯과 레이아웃 — 화면을 조립하는 법 ← 이번 글
- #3 시그널과 슬롯 — 이벤트 처리의 핵심
- #4 Qt Designer와 UI 파일 — 화면을 그려서 불러오기
- #5 모델과 뷰 — 리스트·테이블에 데이터 연결
- #6 스레드와 타이머 — 멈추지 않는 UI
- #7 패키징과 배포 — PyInstaller로 실행 파일 만들기
이번 글이 끝나면 할 일 앱의 화면 뼈대가 완성됩니다. 동작은 아직 없지만, 다음 글에서 시그널을 연결하면 곧바로 살아 움직이는 구조입니다.
자주 쓰는 위젯 카탈로그 #
위젯은 화면을 구성하는 부품입니다. Qt에는 수십 종이 있지만, 업무 도구 수준의 앱은 다음 일곱 개로 대부분 조립됩니다.
| 위젯 | 용도 |
|---|---|
QLabel | 텍스트나 이미지 표시. 편집 불가 |
QLineEdit | 한 줄 텍스트 입력 |
QPushButton | 클릭 버튼 |
QCheckBox | 켜고 끄는 체크박스 |
QComboBox | 드롭다운 선택 목록 |
QTextEdit | 여러 줄 텍스트 입력과 표시 |
QSpinBox | 위아래 화살표가 달린 숫자 입력 |
여기에 항목 목록을 보여 주는 QListWidget을 더하면 이번 글의 재료가 전부입니다. 각 위젯의 세부 옵션은 필요할 때 공식 문서에서 찾으면 되고, 지금 중요한 것은 어떤 부품이 존재하는지를 아는 것입니다.
좌표 배치의 함정과 레이아웃 매니저 #
위젯을 배치하는 가장 원시적인 방법은 좌표 지정입니다. button.move(50, 100)처럼 픽셀 위치를 직접 찍는 방식인데, 이 방식은 실전에서 금방 무너집니다. 창 크기를 바꾸면 위젯이 따라오지 않고, 글꼴 크기가 다른 환경에서는 위젯이 겹치거나 잘립니다.
그래서 Qt는 레이아웃 매니저에 배치를 맡깁니다. 위젯을 레이아웃에 추가하기만 하면, 창 크기가 바뀔 때마다 레이아웃이 각 위젯의 위치와 크기를 다시 계산합니다. 기본 레이아웃은 네 가지입니다.
from PySide6.QtWidgets import (
QVBoxLayout, # 위에서 아래로 쌓기
QHBoxLayout, # 왼쪽에서 오른쪽으로 나열
QGridLayout, # 행과 열의 격자에 배치
QFormLayout, # 라벨-입력 쌍을 두 열로 정렬
)QVBoxLayout과 QHBoxLayout이 기본입니다. 격자가 필요한 계산기 화면은 QGridLayout, 설정 화면처럼 “이름: 입력창” 쌍이 반복되는 폼은 QFormLayout이 맞습니다.
# 격자 배치 — addWidget(위젯, 행, 열)
grid = QGridLayout()
grid.addWidget(QPushButton("7"), 0, 0)
grid.addWidget(QPushButton("8"), 0, 1)
grid.addWidget(QPushButton("9"), 0, 2)
# 폼 배치 — addRow(라벨, 입력 위젯)
form = QFormLayout()
form.addRow("이름", QLineEdit())
form.addRow("포트", QSpinBox())중첩 — 레이아웃 안에 레이아웃 #
실전 화면은 레이아웃 하나로 끝나지 않습니다. 할 일 앱의 화면을 설계해 보면 이렇게 나뉩니다.
┌─────────────────────────────┐
│ [ 입력창 ] [추가] │ ← 가로 배치 (QHBoxLayout)
├─────────────────────────────┤
│ │
│ 할 일 목록 (QListWidget) │ ← 중앙, 남는 공간 전부
│ │
├─────────────────────────────┤
│ [선택 삭제] [전체 삭제] │ ← 가로 배치 (QHBoxLayout)
└─────────────────────────────┘
↑ 전체를 세로로 쌓기 (QVBoxLayout)가로 줄 두 개와 목록 하나를 세로 레이아웃에 쌓는 구조입니다. 코드로 옮기면 다음과 같습니다.
import sys
from PySide6.QtWidgets import (
QApplication, QMainWindow, QWidget,
QVBoxLayout, QHBoxLayout,
QLineEdit, QPushButton, QListWidget,
)
class TodoWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("할 일 관리")
self.resize(400, 500)
# 상단 입력줄 — 입력창 + 추가 버튼
self.todo_input = QLineEdit()
self.todo_input.setPlaceholderText("할 일을 입력하세요")
self.add_button = QPushButton("추가")
input_row = QHBoxLayout()
input_row.addWidget(self.todo_input)
input_row.addWidget(self.add_button)
# 중앙 목록
self.todo_list = QListWidget()
# 하단 버튼줄 — 오른쪽 정렬
self.delete_button = QPushButton("선택 삭제")
self.clear_button = QPushButton("전체 삭제")
button_row = QHBoxLayout()
button_row.addStretch()
button_row.addWidget(self.delete_button)
button_row.addWidget(self.clear_button)
# 전체 조립 — 세로로 쌓기
layout = QVBoxLayout()
layout.addLayout(input_row)
layout.addWidget(self.todo_list)
layout.addLayout(button_row)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = TodoWindow()
window.show()
sys.exit(app.exec())실행하면 설계 그림 그대로의 창이 뜨고, 창을 늘리면 목록 영역이 함께 늘어납니다. 조립 규칙은 세 가지입니다.
- 위젯은 addWidget, 레이아웃은 addLayout — 레이아웃 안에 레이아웃을 넣을 때는 addLayout을 씁니다.
- 레이아웃은 위젯에 실어야 화면에 나옵니다.
QMainWindow의 중앙에는 위젯만 들어갈 수 있으므로, 빈QWidget에 setLayout으로 레이아웃을 싣고 그 위젯을 setCentralWidget에 넘깁니다. - 위젯을 self 속성으로 보관합니다.
self.todo_input처럼 붙잡아 둔 위젯만 다음 글에서 시그널을 연결하거나 값을 읽을 수 있습니다.
여백, 정렬, 남는 공간 #
배치를 다듬는 세 가지 도구를 확인합니다.
- stretch — 하단 버튼줄의
addStretch()는 빈 공간을 차지하는 스프링입니다. 버튼들 앞에 넣었으므로 버튼이 오른쪽으로 밀려 정렬됩니다. - 여백과 간격 —
layout.setContentsMargins(12, 12, 12, 12)는 레이아웃 바깥 여백,layout.setSpacing(8)은 위젯 사이 간격을 지정합니다. - 크기 비율 —
layout.addWidget(self.todo_list, stretch=1)처럼 stretch 값을 주면 창이 커질 때 남는 공간을 그 위젯이 우선 가져갑니다.
마무리 #
이번 글의 핵심은 세 가지입니다.
- 화면은 위젯이라는 부품과 레이아웃이라는 배치 규칙으로 조립하고, 좌표 지정은 쓰지 않습니다.
- 실전 화면은 QHBoxLayout과 QVBoxLayout의 중첩으로 대부분 표현되고, 레이아웃은 빈 QWidget에 실어 setCentralWidget으로 창에 올립니다.
- addStretch, 여백, stretch 비율로 배치를 다듬습니다.
지금의 할 일 앱은 버튼을 눌러도 아무 일도 일어나지 않습니다. 다음 글인 “PySide6로 데스크톱 앱 만들기 #3 시그널과 슬롯 — 이벤트 처리의 핵심"에서 Qt 이벤트 처리의 핵심 모델인 시그널과 슬롯을 배우고, 이 화면에 실제 동작을 연결하겠습니다.