PySide Qt for Python QMainWindow QWidget QDialog 기본 구조와 centralWidget 도킹 상태바 완전 가이드
🧭 처음부터 구조를 제대로 잡아 생산적인 데스크톱 앱을 완성하는 핵심만 골라 담았습니다
GUI를 만들다 보면 창의 종류와 배치 방식이 뒤섞여 무엇부터 손대야 할지 막막할 때가 많습니다.
작은 도구라도 구조를 잘 잡으면 유지보수와 확장이 훨씬 수월해지고, 반대로 초기에 헐겁게 짜면 기능을 추가할 때마다 전체를 갈아엎게 되죠.
이번 글은 PySide(Qt for Python)에서 자주 헷갈리는 QMainWindow, QWidget, QDialog의 쓰임과 차이를 맥락 속에서 정리합니다.
각 클래스의 책임을 정확히 구분하고, centralWidget과 도킹 영역, 상태바를 올바르게 활용하는 흐름을 자연스럽게 이어가 보겠습니다.
개발 환경이나 규모가 달라도 흔들리지 않는 기본 골격을 가져가도록 실제 프로젝트에서 통하는 기준을 담았습니다.
핵심은 “올바른 창 타입을 고르고, 그 위에 레이아웃과 위젯을 질서 있게 쌓는 것”입니다.
도큐먼트 중심의 앱이라면 메뉴바와 툴바, 도킹 패널, 상태바가 있는 QMainWindow가 제격이고, 심플한 단일 화면 위젯이라면 QWidget 기반이 더 가볍습니다.
사용자 입력을 잠시 받아야 할 때는 QDialog를 통해 흐름을 간결하게 제어할 수 있습니다.
centralWidget은 QMainWindow의 심장부로, 실제 콘텐츠 레이아웃이 놓이는 자리입니다.
도킹 위젯은 보조 패널을 유연하게 붙였다 떼도록 만들고, 상태바는 맥락 정보를 안정적으로 보여줍니다.
이 글은 이러한 요소를 연결하는 설계 기준과 실무 팁을 하나씩 짚어 앱의 뼈대를 단단히 세우는 데 도움을 줄 것입니다.
📋 목차
🏛️ QMainWindow 기본 구조와 역할
QMainWindow는 데스크톱 앱의 뼈대를 담당하며 메뉴바, 툴바, 도킹 영역, 상태바, 중앙 영역을 체계적으로 배치할 수 있는 컨테이너입니다.
중앙 영역은 반드시 하나의 centralWidget을 기준으로 화면 레이아웃이 구성되며, 좌우상하의 도킹 영역에는 보조 패널을 배치합니다.
이 구조 덕분에 주 콘텐츠와 보조 도구가 충돌하지 않고 확장 가능한 UI를 유지할 수 있습니다.
🧱 핵심 구성 요소와 책임
🧩 centralWidget
콘텐츠의 중심이 되는 메인 위젯입니다.
일반적으로 QWidget을 생성해 레이아웃(QVBoxLayout, QHBoxLayout, QGridLayout 등)을 적용한 뒤 QMainWindow.setCentralWidget()에 전달합니다.
centralWidget은 오직 하나만 설정하며, 실제 화면 배치와 리사이즈 동작의 기준점이 됩니다.
🧭 메뉴바와 툴바
메뉴바는 전역 명령을, 툴바는 자주 쓰는 동작을 아이콘과 함께 제공합니다.
QMainWindow.menuBar() 또는 setMenuBar()로 구성하고, addToolBar()로 여러 개의 툴바를 상단·좌우에 배치할 수 있습니다.
🧲 도킹 영역(QDockWidget)
프로젝트 탐색기, 로그, 속성 편집기 같은 보조 패널을 도킹하거나 분리 가능한 부동 창으로 제공합니다.
addDockWidget(Qt.LeftDockWidgetArea, dock)처럼 영역을 지정해 배치합니다.
사용자는 드래그로 위치를 바꾸거나 접을 수 있어 작업 맥락에 맞춘 워크플로우가 가능합니다.
📶 상태바(QStatusBar)
진행 상태, 좌표, 모드 전환 등 즉시 피드백이 필요한 정보를 하단에 노출합니다.
QMainWindow.statusBar().showMessage(“저장 완료”, 2000)처럼 일시 메시지를 띄우거나, 영구 위젯을 추가해 상시 정보를 보여줄 수 있습니다.
⚙️ 최소 골격 코드 예시(PySide6)
from PySide6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout,
QLabel, QToolBar, QStatusBar, QDockWidget, QListWidget
)
from PySide6.QtCore import Qt
import sys
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("QMainWindow 기본 구조")
# 1) centralWidget
central = QWidget(self)
layout = QVBoxLayout(central)
layout.addWidget(QLabel("여기가 centralWidget 입니다"))
self.setCentralWidget(central)
# 2) 메뉴바
menu = self.menuBar()
file_menu = menu.addMenu("파일")
file_menu.addAction("새로 만들기")
# 3) 툴바
toolbar = QToolBar("메인 툴바", self)
self.addToolBar(Qt.TopToolBarArea, toolbar)
# 4) 도킹 패널
dock = QDockWidget("프로젝트", self)
dock.setWidget(QListWidget())
self.addDockWidget(Qt.LeftDockWidgetArea, dock)
# 5) 상태바
status = QStatusBar(self)
self.setStatusBar(status)
status.showMessage("준비 완료", 2000)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MainWindow()
w.resize(900, 600)
w.show()
sys.exit(app.exec())
💎 핵심 포인트:
centralWidget은 하나만 존재하며, 모든 메인 레이아웃은 centralWidget 안에서 관리합니다.
도킹 패널은 주 콘텐츠를 방해하지 않도록 보조 정보용으로 배치하고, 상태바에는 과도한 텍스트를 넣지 말고 짧고 명확한 메시지를 유지합니다.
- 🧱centralWidget을 먼저 만들고 레이아웃을 적용했는지 확인
- 🧰메뉴바·툴바는 전역/빈번 동작만 올려 복잡도를 최소화
- 🧲도킹 위젯은 보조 패널로 설계하고 주 콘텐츠는 centralWidget에 집중
- 📶상태바 메시지는 짧게, 일시 메시지는 타이머를 활용
💡 TIP: QMainWindow.setDockOptions()로 탭 도킹, 그룹화 허용 등을 세밀하게 제어할 수 있습니다.
도킹이 많은 앱이라면 저장/복원에 QMainWindow.saveState()와 restoreState()를 사용해 사용자 레이아웃 환경을 기억시키면 만족도가 높습니다.
⚠️ 주의: centralWidget 없이 임시 위젯을 setCentralWidget으로 교체하는 패턴을 반복하면 시그널 연결과 상태 관리가 꼬일 수 있습니다.
항상 고정된 centralWidget 내부에서 페이지 전환(QStackedWidget 등)으로 화면을 바꾸는 구조가 안전합니다.
🧩 QWidget 중심의 일반 창 구성
PySide에서 QWidget은 모든 UI 구성 요소의 기본 클래스입니다.
QMainWindow나 QDialog도 결국 QWidget을 기반으로 하지만, 가장 단순한 형태의 창을 만들고 싶다면 QWidget을 직접 상속받는 것이 가장 효율적입니다.
메뉴바나 상태바가 필요 없는 도구형 유틸리티, 이미지 뷰어, 계산기, 입력 폼처럼 단일 레이아웃만 있으면 충분한 앱에 적합합니다.
🏗️ QWidget을 기본 윈도우로 사용하는 이유
QWidget은 불필요한 프레임워크 계층을 줄여 가벼운 실행 속도를 제공합니다.
특히 복잡한 UI 관리가 필요하지 않은 툴을 빠르게 제작할 때 유리합니다.
하나의 메인 레이아웃만 관리하면 되기 때문에 QMainWindow보다 코드가 단순하고, 다른 위젯을 포함하거나 내장할 때도 구조가 깔끔합니다.
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout
import sys
class SimpleWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("QWidget 기반 창 예제")
self.resize(400, 300)
layout = QVBoxLayout()
layout.addWidget(QLabel("QWidget은 가장 단순한 기본 창입니다."))
self.setLayout(layout)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = SimpleWindow()
w.show()
sys.exit(app.exec())
이 코드는 메뉴바나 상태바 없이 중앙에 위젯 하나를 표시하는 기본 구조를 보여줍니다.
위젯은 setLayout()으로 배치되며, QMainWindow처럼 centralWidget 개념이 필요 없습니다.
따라서 내부 구조가 단순하고, 학습 및 프로토타입 단계에서 빠르게 테스트할 수 있습니다.
🧮 QWidget과 QMainWindow 비교
| 구분 | QWidget | QMainWindow |
|---|---|---|
| 용도 | 단일 화면, 단순 앱 | 복합 레이아웃, 데스크톱 중심 앱 |
| 구조 | 하나의 레이아웃만 관리 | centralWidget, 메뉴바, 도킹 등 복합 구조 |
| 무게감 | 가벼움, 초기 로딩 빠름 | 기능 많지만 초기화 비용 큼 |
| 적합 예시 | 폼, 툴, 유틸리티 | IDE, 편집기, 분석 툴 |
💎 핵심 포인트:
QWidget은 시작이자 기본 단위입니다.
QMainWindow나 QDialog를 이해하기 전에 QWidget을 완전히 숙달해야 다른 구조를 쉽게 확장할 수 있습니다.
복잡한 창도 결국 QWidget들의 계층으로 이뤄져 있다는 점을 기억해 두면 좋습니다.
💡 TIP: QWidget 기반 앱에서도 QVBoxLayout과 QHBoxLayout을 적절히 섞으면 QMainWindow 수준의 구성감을 낼 수 있습니다.
필요 시 QFrame으로 구역을 구분하고, setStyleSheet()로 디자인 요소를 맞추면 완성도를 높일 수 있습니다.
🗂️ QDialog 모달 대화상자 패턴
PySide에서 QDialog는 사용자 입력을 잠시 받아야 하거나, 메인 프로세스를 잠시 멈추고 결과를 확인받는 상황에 자주 쓰입니다.
예를 들어 파일 저장 확인, 설정창, 로그인 창처럼 ‘사용자가 선택하기 전에는 진행되지 않는’ 모달 창이 대표적입니다.
QDialog는 독립적인 이벤트 루프를 실행하기 때문에 사용자의 응답을 기다리면서 메인 윈도우를 잠시 비활성화합니다.
🎯 QDialog의 핵심 동작 원리
QDialog는 exec() 메서드를 통해 모달 상태로 실행됩니다.
이 메서드는 대화상자가 닫히기 전까지 제어권을 반환하지 않으며, 사용자가 ‘확인’ 또는 ‘취소’를 선택한 후 Accepted 또는 Rejected 값을 리턴합니다.
이로써 메인 창의 로직은 사용자의 입력 결과에 따라 다음 단계를 자연스럽게 제어할 수 있습니다.
from PySide6.QtWidgets import QApplication, QDialog, QVBoxLayout, QLabel, QPushButton
import sys
class ConfirmDialog(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("저장 확인")
self.resize(300, 150)
layout = QVBoxLayout()
layout.addWidget(QLabel("변경 내용을 저장하시겠습니까?"))
ok_btn = QPushButton("확인")
cancel_btn = QPushButton("취소")
ok_btn.clicked.connect(self.accept)
cancel_btn.clicked.connect(self.reject)
layout.addWidget(ok_btn)
layout.addWidget(cancel_btn)
self.setLayout(layout)
if __name__ == "__main__":
app = QApplication(sys.argv)
dlg = ConfirmDialog()
result = dlg.exec()
if result:
print("사용자가 확인을 선택했습니다.")
else:
print("취소 또는 닫기 선택.")
sys.exit(app.exec())
이 구조는 GUI의 이벤트 흐름을 제어하기에 매우 유용합니다.
특히 사용자의 선택에 따라 파일 저장 여부나 설정 적용을 분기해야 할 때 명확하고 직관적인 코드 작성이 가능합니다.
🔒 모달과 모델리스의 차이
| 유형 | 설명 | 메서드 |
|---|---|---|
| 모달(Modal) | 다른 창의 입력을 막고, 대화상자가 닫힐 때까지 기다림 | exec() |
| 모델리스(Modeless) | 메인 창과 동시에 상호작용 가능 | show() |
💎 핵심 포인트:
모달 대화상자는 흐름 제어에 강력하지만 남용하면 UX가 답답해질 수 있습니다.
짧고 중요한 선택에만 사용하고, 설정처럼 자주 열고 닫는 화면은 모델리스로 유지하는 것이 좋습니다.
💡 TIP: QDialog에 setModal(True) 속성을 지정하면 exec() 대신 show()로도 모달 상태를 구현할 수 있습니다.
또한 QMessageBox는 QDialog의 단축 버전으로, 단순 알림에는 훨씬 간편하게 사용할 수 있습니다.
⚠️ 주의: exec()는 내부적으로 별도 이벤트 루프를 실행하므로, QThread나 async 작업과 함께 사용할 때는 교착 상태를 피하기 위해 주의가 필요합니다.
이벤트 블로킹이 발생하지 않도록 비동기 호출을 분리하거나, 시그널 기반 콜백으로 처리하세요.
🧱 centralWidget 설정과 레이아웃 배치
QMainWindow에서 콘텐츠는 반드시 centralWidget을 통해 배치합니다.
centralWidget은 하나만 존재하며, 내부에 루트 레이아웃을 설정하고 그 아래로 위젯과 하위 레이아웃을 중첩해 구조를 완성합니다.
비슷해 보이지만 QMainWindow 자체에는 레이아웃을 직접 지정하지 않습니다.
실무에서는 QVBoxLayout을 루트로 두고 상단 툴영역, 중앙 작업영역, 하단 보조영역 순으로 쌓거나, QSplitter와 QStackedWidget을 조합해 가변적인 작업 화면을 만듭니다.
또한 콘텐츠가 길어질 때는 centralWidget에 QScrollArea를 사용해 스크롤을 제공하는 패턴이 안정적입니다.
🧭 올바른 centralWidget 초기화 순서
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QSplitter, QListWidget, QTextEdit, QStackedWidget
)
from PySide6.QtCore import Qt
def build_central(parent_window):
# 1) centralWidget 생성
central = QWidget(parent_window)
# 2) 루트 레이아웃 지정
root = QVBoxLayout(central)
root.setContentsMargins(12, 12, 12, 12)
root.setSpacing(8)
# 3) 상단: 정보 바(예: 현재 파일/모드)
info_bar = QLabel("프로젝트: Demo / 모드: 편집")
info_bar.setObjectName("infoBar")
root.addWidget(info_bar)
# 4) 중앙: 좌측 목록 + 우측 에디터 (Splitter)
left = QListWidget(); left.addItems(["doc1", "doc2", "doc3"])
editor_stack = QStackedWidget()
editor_stack.addWidget(QTextEdit("문서 1"))
editor_stack.addWidget(QTextEdit("문서 2"))
split = QSplitter(Qt.Horizontal)
split.addWidget(left)
split.addWidget(editor_stack)
split.setSizes([240, 760])
root.addWidget(split, 1) # stretch로 중앙 영역 확장
# 5) 하단: 로그/상태 위젯
bottom = QWidget()
bottom_layout = QHBoxLayout(bottom)
bottom_layout.addWidget(QLabel("로그: 정상 동작"))
root.addWidget(bottom)
# 6) centralWidget 연결
parent_window.setCentralWidget(central)
핵심은 central → 루트 레이아웃 → 콘텐츠 순서를 지키는 것입니다.
stretch 계수와 사이즈 정책을 통해 중앙 영역이 윈도우 리사이즈에 맞춰 자연스럽게 확장되도록 제어합니다.
Splitter는 사용자가 직접 영역 비율을 조정할 수 있어 문서/도구형 앱에 특히 유용합니다.
🧮 레이아웃 선택 가이드
| 레이아웃 | 장점 | 적합한 화면 |
|---|---|---|
| QVBoxLayout | 세로 스택, 가독성 좋고 기본 구조에 적합 | 툴바/본문/푸터로 나뉜 화면 |
| QHBoxLayout | 가로 배치, 사이드바 + 본문 구성에 용이 | 사이드 메뉴, 속성 편집 |
| QGridLayout | 행·열 조합, 복잡한 폼/대시보드에 유연 | 컨트롤 패널, 카드형 UI |
| QFormLayout | 라벨-필드 쌍 자동 정렬 | 설정/가입/프로필 폼 |
📐 여백, 정렬, 사이즈 정책의 실전 팁
- 📏루트 레이아웃의 setContentsMargins와 setSpacing으로 기본 간격을 통일해 일관성을 확보
- 🧊QSpacerItem이나 stretch 인자를 활용해 빈 공간을 의도적으로 남겨 시각적 안정감을 부여
- 📐QSizePolicy로 가로/세로 확장 정책을 지정하면 리사이즈 시 비율 무너짐을 방지
- 🔀QStackedWidget을 사용해 centralWidget 내부에서 페이지 전환을 처리하면 구조가 깔끔
💬 QMainWindow는 레이아웃 컨테이너가 아니라 프레임 역할을 합니다.
항상 centralWidget에 레이아웃을 지정해야 하며, QMainWindow 자체에 setLayout을 호출하지 않습니다.
💎 핵심 포인트:
centralWidget은 단일 진입점입니다.
여기에만 레이아웃을 지정하고, 화면 전환은 교체가 아니라 내부 스택/탭/스플리터로 해결합니다.
이 패턴이 도킹, 상태바와 충돌 없이 안정적인 사용자 경험을 보장합니다.
⚠️ 주의: centralWidget을 빈번히 새 인스턴스로 교체하면 시그널/슬롯 누수와 메모리 관리 이슈가 발생할 수 있습니다.
가능하면 고정 centralWidget 내부에서 위젯만 보여 주기/숨기기 또는 스택 인덱스 변경으로 처리하세요.
💡 TIP: 폼이 길어질 때는 QScrollArea를 centralWidget의 자식으로 두고, 내부 위젯의 setMinimumWidth와 sizePolicy를 조정해 가로 스크롤이 생기지 않도록 디자인하세요.
🧲 도킹 위젯과 상태바 사용 팁
QMainWindow의 강력한 장점 중 하나는 도킹 위젯(QDockWidget)과 상태바(QStatusBar)를 동시에 관리할 수 있다는 점입니다.
이 두 요소는 앱의 정보 밀도를 높이면서도, 메인 콘텐츠를 방해하지 않는 선에서 부가 기능을 제공합니다.
IDE나 그래픽 편집기, 분석 툴처럼 ‘보조 패널 + 작업창’ 구조를 구성할 때 없어서는 안 될 존재입니다.
🧩 QDockWidget 기본 구조
도킹 위젯은 QDockWidget 클래스를 이용해 만들며, 독립된 작은 창처럼 행동합니다.
기본적으로 좌·우·상·하 네 영역 어디든 배치할 수 있으며, 사용자가 드래그로 분리하거나 다시 붙일 수 있습니다.
보통 탐색창, 속성 패널, 로그창, 히스토리 뷰 등에 활용됩니다.
from PySide6.QtWidgets import QDockWidget, QListWidget
dock = QDockWidget("탐색기", parent)
dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
list_widget = QListWidget()
list_widget.addItems(["파일1.py", "파일2.py", "파일3.py"])
dock.setWidget(list_widget)
parent.addDockWidget(Qt.LeftDockWidgetArea, dock)
도킹 위젯은 한 번에 여러 개를 추가할 수 있으며, setFeatures()로 이동, 닫기, 고정 기능을 제한할 수 있습니다.
또한 tabifyDockWidget()으로 여러 도킹창을 탭 형태로 묶는 것도 가능합니다.
📶 QStatusBar로 사용자 피드백 제공
상태바는 하단에 위치하며, 앱의 진행 상태나 실시간 정보를 간결하게 보여주는 역할을 합니다.
일시적인 메시지는 showMessage()로 표시하고, 특정 위젯(예: 진행바, 좌표 표시 등)을 영구적으로 추가할 수도 있습니다.
status = QStatusBar()
window.setStatusBar(status)
status.showMessage("데이터 로드 완료", 3000)
progress = QProgressBar()
progress.setValue(75)
status.addPermanentWidget(progress)
메시지와 위젯을 적절히 조합하면, 상태바가 단순 표시창을 넘어 미니 대시보드처럼 작동하게 됩니다.
그러나 너무 많은 정보를 넣으면 사용성이 떨어지므로 2~3개의 핵심 상태만 남겨두는 것이 좋습니다.
💎 핵심 포인트:
도킹 위젯은 항상 addDockWidget()으로 추가해야 하며, centralWidget 안에 직접 넣으면 기능이 제한됩니다.
상태바는 일시적 메시지(showMessage)와 영구적 위젯(addPermanentWidget)을 조합해 사용자 경험을 향상시키세요.
🧭 도킹 레이아웃의 유지와 복원
사용자가 도킹 패널의 위치를 조정할 수 있도록 허용하는 경우, 종료 시 레이아웃 상태를 저장해 두는 것이 좋습니다.
PySide는 saveState()와 restoreState() 메서드로 이 기능을 제공합니다.
# 저장
settings.setValue("layout/state", window.saveState())
# 복원
window.restoreState(settings.value("layout/state"))
이 방식은 사용자 맞춤 UI 구성을 유지하는 데 효과적이며, 전문 앱에서는 필수로 구현하는 기능입니다.
특히 도킹 구성이 많은 IDE류 앱에서 사용자 만족도를 높이는 핵심 요소로 작용합니다.
⚠️ 주의: 도킹 위젯 이름(objectName)을 지정하지 않으면 saveState()가 해당 위젯을 식별하지 못합니다.
반드시 dock.setObjectName(“DockName”)으로 고유 이름을 부여하세요.
💡 TIP: QMainWindow의 도킹 옵션은 setDockOptions()으로 제어할 수 있습니다.
예를 들어 탭 도킹, 닫기 버튼 숨김, 부동 허용 여부 등을 세밀하게 설정해 앱의 UX를 통제할 수 있습니다.
❓ 자주 묻는 질문 FAQ
QMainWindow 없이도 도킹 위젯을 쓸 수 있나요?
centralWidget을 여러 개 둘 수 있나요?
QDialog에서 다른 창을 열 수 있나요?
QWidget을 메인 창으로 쓰면 메뉴바를 못 쓰나요?
도킹 위젯 위치를 초기화하려면 어떻게 하나요?
QDialog와 QMessageBox는 어떤 차이가 있나요?
QMainWindow의 saveState()는 버전 호환이 되나요?
QWidget 안에서 또 다른 QWidget을 중첩해도 되나요?
🧭 PySide QMainWindow 구조 완벽 이해로 생산성 높이기
PySide(Qt for Python)에서 QMainWindow, QWidget, QDialog는 각각 명확한 역할과 구조를 갖습니다.
QMainWindow는 복합 앱의 중심 뼈대이며, centralWidget을 통해 모든 콘텐츠가 정리됩니다.
QWidget은 가장 기본적인 화면 단위로, 단일 창 앱이나 간단한 폼 구성에 유용합니다.
QDialog는 사용자 선택을 요구하는 팝업창 역할을 담당하며, exec()로 흐름 제어를 지원합니다.
특히 centralWidget은 QMainWindow의 핵심이며, 도킹 위젯과 상태바는 보조 기능을 통합적으로 제공해 완성도 높은 UI를 만듭니다.
이 세 가지를 올바로 구분하고 조합할 줄 알면, 유지보수성과 확장성이 뛰어난 GUI를 설계할 수 있습니다.
결국 ‘적절한 구조를 먼저 잡는 것’이 PySide 앱 개발의 가장 확실한 효율화 전략입니다.
🏷️ 관련 태그 : PySide, QtforPython, QMainWindow, QWidget, QDialog, centralWidget, 도킹위젯, 상태바, GUI레이아웃, 파이썬GUI