I. PySide6 입문 로드맵 개요
1.1. PySide6의 가치 및 시리즈 목표 설정
PySide6는 Python 언어의 높은 접근성과 강력한 크로스 플랫폼 GUI 프레임워크인 Qt의 기능을 결합하여, 개발자들이 효율적으로 복잡한 데스크톱 애플리케이션을 구축할 수 있게 해주는 핵심 도구입니다. Qt는 오랜 기간 동안 산업 표준으로 인정받아왔으며, PySide6는 이러한 안정적이고 풍부한 기능을 Python 환경으로 가져옵니다. 본 보고서에서 제시하는 7가지 튜토리얼은 PySide6를 이용한 GUI 개발을 시작하는 초보자를 위해 필수적인 지식을 순차적으로 제공하는 완벽한 입문 경로를 구성합니다.
이 학습 경로는 단순한 기능 구현을 넘어, Qt/PySide6 프레임워크 아키텍처의 근본적인 원리를 이해하는 데 중점을 둡니다. 독자는 가장 기본적인 창 생성(1단계)을 시작으로 사용자 상호작용 메커니즘(2단계), 유연한 레이아웃 설계(4단계), 그리고 다이얼로그 및 다중 창 관리(6, 7단계)와 같은 핵심 기능을 단계별로 익히게 됩니다. 이러한 체계적인 접근 방식은 학습자가 프레임워크의 작동 구조를 명확히 파악하여, 향후 복잡하고 유지보수가 용이한 GUI 애플리케이션을 독자적으로 설계할 수 있는 견고한 기반을 마련해 줍니다.
1.2. 보고서 활용 가이드 및 스타일 설정
본 보고서의 각 섹션은 요청된 7개의 개별 블로그 게시물로 변환될 수 있도록 상세한 이론적 배경과 코드 분석을 포함합니다. 모든 글은 초보 개발자의 이해를 돕기 위한 교육적이고 대화적인 기술 보고서의 톤을 유지합니다. 코드는 실행 가능한 형태로 제시되며, 핵심 개념과 복잡한 구조를 정리한 표를 통해 시각적 이해도를 높입니다. 모든 코드 해설은 해당 기능의 이론적 배경과 구조적 중요성을 먼저 설명한 후에 제시됩니다.
II: Creating your first app (첫 번째 PySide6 창 띄우기)
2.1. 최소 실행 환경의 핵심 구성 요소
PySide6 기반의 "Hello World!" 애플리케이션을 실행하기 위해서는 필수적으로 QApplication과 QMainWindow라는 두 가지 핵심 클래스가 필요합니다.
첫째, QApplication은 전체 PySide6 애플리케이션의 제어 흐름을 관리하는 단 하나의 전역 인스턴스입니다. 이 인스턴스는 애플리케이션의 설정, 스타일, 그리고 가장 중요한 이벤트 루프를 관리합니다. QApplication은 초기화 시 sys.argv를 인자로 받아 시스템 및 환경 관련 정보를 처리합니다. 둘째, QMainWindow는 PySide6 GUI의 표준 컨테이너 역할을 수행합니다. 이 창은 메뉴 바, 툴바, 상태 표시줄과 같은 구조적 요소들을 내부에 포함할 수 있도록 설계되었으며, 대부분의 GUI 애플리케이션에서 사용자에게 보여지는 주된 창의 뼈대가 됩니다.
2.2. 기본 코드 분석 및 실행
최소한의 PySide6 창을 띄우는 코드는 다음과 같은 표준 구조를 따릅니다. 이 구조에서는 QMainWindow를 상속받는 MainWindow 클래스를 정의하고, 이 클래스의 생성자에서 부모 클래스를 초기화(super().__init__())한 후 setWindowTitle()을 통해 창 제목을 설정합니다.
import sys
from PySide6.QtWidgets import QApplication, QMainWindow
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
2.3. GUI 생명 주기와 Event Loop (app.exec())의 필수성
많은 프로그래밍 환경에서 코드는 위에서 아래로 순차적으로 실행되지만, GUI 애플리케이션은 사용자 상호작용에 따라 비동기적으로 작동하는 이벤트 중심적(Event-Driven) 모델을 따릅니다. window.show() 메서드는 창을 화면에 표시하는 역할만 수행합니다. 그러나 실제로 이 창을 화면에 유지하고, 사용자의 마우스 클릭이나 키 입력과 같은 이벤트를 지속적으로 감지하고 처리하는 것은 app.exec() 메서드입니다.
app.exec()가 호출되면 파이썬 스크립트의 실행은 일시적으로 멈추고 제어권이 Qt 프레임워크의 이벤트 루프(Event Loop)로 넘어갑니다. 이 루프는 애플리케이션이 종료될 때까지 사용자 이벤트를 기다리고, 이를 적절한 위젯으로 전달하는 역할을 수행합니다. 만약 app.exec()가 생략된다면, window.show() 이후 스크립트는 남은 코드가 없다고 판단하고 즉시 종료되어, 사용자에게 창이 나타나자마자 사라지는 경험을 제공하게 됩니다. 따라서 app.exec()는 GUI 애플리케이션의 생명 주기를 관장하며, 애플리케이션이 사용자 상호작용에 반응할 수 있도록 만드는 가장 핵심적인 아키텍처적 요소입니다.
III: PySide6 Signals, Slots & Events (시그널, 슬롯, 그리고 이벤트)
3.1. Signals, Slots, Events 핵심 개념 정의
PySide6에서 상호작용을 처리하는 기본 방식은 시그널(Signals)과 슬롯(Slots)입니다. 이 메커니즘은 위젯이 특정 동작이 발생했음을 알리고, 그 알림을 수신하는 함수가 해당 동작에 반응하도록 하는 구조입니다.
PySide6 상호작용 메커니즘
개념 | 설명 | 예시 (함수/메서드) | 출처 |
시그널 (Signal) | 위젯에서 버튼 클릭, 값 변경 등 특정 사건이 발생했을 때 방출되는 알림. 발생한 사건에 대한 추가 데이터를 포함할 수 있습니다. |
| 2 |
슬롯 (Slot) | 시그널이 연결되어 실행되는 리시버(함수 또는 메서드). 시그널로부터 데이터를 인자로 받을 수 있습니다. |
| 2 |
이벤트 (Event) | 마우스 움직임, 키 입력과 같은 저수준 상호작용을 위젯의 특정 메서드를 오버라이드하여 직접 처리하는 방식. |
| 2 |
3.2. 시그널-슬롯 기본 연결 및 데이터 수신
시그널을 슬롯에 연결하는 과정은 매우 직관적입니다: widget.signal.connect(slot_method). 예를 들어, QPushButton의 clicked 시그널을 MainWindow의 the_button_was_clicked 메서드에 연결하면, 버튼 클릭 시 해당 메서드가 실행됩니다.
import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press Me!")
button.setCheckable(True)
# 시그널과 슬롯 연결
button.clicked.connect(self.the_button_was_clicked)
self.setCentralWidget(button)
def the_button_was_clicked(self):
print("Clicked!") # 버튼 클릭 시 실행되는 슬롯
#... (애플리케이션 실행 코드 생략)
일부 시그널은 발생 시점에 유용한 데이터를 슬롯으로 전송합니다. 예를 들어, setCheckable(True)로 설정된 버튼의 clicked 시그널은 버튼의 새로운 체크 상태(부울 값)를 슬롯으로 전달합니다. 이 데이터를 받기 위해서는 슬롯 메서드를 def slot(self, checked):와 같이 시그널이 전송하는 인자를 받을 수 있도록 정의해야 합니다. 이 기능을 통해 슬롯은 버튼의 상태 변화에 따라 조건부 로직을 수행할 수 있습니다.
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
#... (창 설정 및 버튼 생성)
button = QPushButton("Press Me!")
button.setCheckable(True)
# clicked 시그널을 두 개의 슬롯에 연결
button.clicked.connect(self.the_button_was_clicked)
button.clicked.connect(self.the_button_was_toggled) # 데이터 수신 슬롯
self.setCentralWidget(button)
def the_button_was_clicked(self):
print("Clicked!")
def the_button_was_toggled(self, checked):
# 'checked' 인자로 부울 상태를 수신
print("Checked?", checked)
3.3. 상태 관리 패턴 비교 (데이터 수신 vs. 직접 조회)
위젯의 상태를 파악하는 방식은 두 가지가 있습니다. 첫째는 시그널 인자로 상태를 직접 수신하는 것입니다. 둘째는 시그널이 상태 정보를 전달하지 않을 때(예: released 시그널), 슬롯 내부에서 self.widget.isChecked()와 같이 위젯의 메서드를 호출하여 상태를 직접 조회하는 것입니다.
이 두 패턴은 애플리케이션의 상태 관리 방식을 결정합니다. 시그널 인자를 사용하는 방식은 위젯의 상태를 외부 변수(self.button_is_checked)에 저장하여 관리할 수 있게 하며, 비동기 환경에서 상태 불일치 위험을 줄일 수 있습니다. 반면, 직접 조회 방식은 위젯 객체에 대한 참조를 유지해야 하지만, 해당 위젯이 제공하는 다양한 속성(텍스트, 위치 등)을 슬롯에서 유연하게 활용할 수 있도록 합니다.
3.4. 시그널 체이닝을 통한 동적 로직 구현
시그널과 슬롯의 연결 구조는 복잡한 이벤트 체인(Chaining)을 구현하는 데 매우 효과적입니다. 예를 들어, 버튼 클릭 이벤트가 발생했을 때 self.setWindowTitle(new_title)을 호출하여 창 제목을 변경하면, 이 행위는 곧 QMainWindow가 내장하고 있는 windowTitleChanged 시그널을 방출하게 됩니다. 이 시그널에 다른 슬롯을 연결하면, 최초의 버튼 클릭 동작이 다른 추가적인 반응을 간접적으로 유발할 수 있습니다.
import sys
from random import choice
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton
# 랜덤 제목 목록
window_titles =
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
self.button = QPushButton("Press Me!")
self.button.clicked.connect(self.the_button_was_clicked)
# (1) QMainWindow 내장 시그널 연결
self.windowTitleChanged.connect(self.the_window_title_changed)
self.setCentralWidget(self.button)
def the_button_was_clicked(self):
new_window_title = choice(window_titles)
# (2) 제목 변경 -> windowTitleChanged 시그널 방출
self.setWindowTitle(new_window_title)
def the_window_title_changed(self, window_title):
# (3) 시그널이 방출되면 이 슬롯이 실행됨
print("Window title changed: %s" % window_title)
if window_title == "Something went wrong":
self.button.setDisabled(True) # 버튼 비활성화로 반응
#...
이 구조는 '버튼 클릭 슬롯'이 직접 '버튼 비활성화' 함수를 호출하는 대신, '제목 변경'이라는 공통 시그널을 중간 매개체로 사용하여 기능을 연결합니다. 이러한 방식은 컴포넌트 간의 직접적인 종속성을 제거하고 결합도를 낮춤으로써(느슨한 결합), 애플리케이션의 유지보수 용이성과 기능 확장에 대한 유연성을 크게 향상시키는 객체지향 설계의 중요한 특징을 반영합니다.
3.5. 저수준 이벤트 핸들링 (마우스 이벤트)
시그널/슬롯이 고수준의 상호작용을 처리한다면, 이벤트 핸들링은 마우스 클릭 위치나 키 입력과 같은 저수준의 상호작용을 처리합니다. 이는 QMainWindow와 같은 위젯 클래스에 정의된 특정 메서드(예: mousePressEvent, mouseMoveEvent)를 오버라이드(재정의)하여 구현됩니다.
이벤트 처리 메서드에는 QMouseEvent 객체 e가 전달됩니다. 이 객체는 발생한 이벤트에 대한 모든 세부 정보를 담고 있으며, .button(), .pos() (위젯 상대 좌표), .globalPos() (스크린 상대 좌표)와 같은 메서드를 통해 접근할 수 있습니다.2 특히, Qt.MouseButton 네임스페이스를 활용하여 if e.button() == Qt.MouseButton.LeftButton:과 같이 특정 마우스 버튼의 입력을 구분하여 처리할 수 있습니다.2 또한, setMouseTracking(True)를 창이나 위젯에 적용하면 마우스 버튼이 눌려 있지 않은 상태에서도 마우스 이동 이벤트를 지속적으로 감지할 수 있습니다.
import sys
from PySide6.QtWidgets import QApplication, QLabel, QMainWindow
from PySide6.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# 마우스 트래킹 활성화 (버튼을 누르지 않아도 이동 감지)
self.setMouseTracking(True)
self.label = QLabel("Click in this window")
self.label.setMouseTracking(True)
self.setCentralWidget(self.label)
def mousePressEvent(self, e):
# 마우스 왼쪽 버튼 클릭 여부 확인
if e.button() == Qt.MouseButton.LeftButton:
self.label.setText(f"Left Click at: {e.pos().x()}, {e.pos().y()}")
# 부모 클래스의 메서드를 호출하여 기본 동작 유지
super().mousePressEvent(e)
def mouseReleaseEvent(self, e):
self.label.setText("mouseReleaseEvent")
#...
IV: PySide6 Widgets (핵심 위젯 활용법)
4.1. 정보 출력 및 기본 입력 위젯
위젯은 사용자 인터페이스를 구성하는 기본적인 시각적 요소입니다. QLabel은 사용자에게 텍스트, 이미지 등의 정보를 표시하는 데 사용되며, QPushButton은 사용자로부터 클릭이라는 가장 일반적인 명령 입력을 받는 데 사용됩니다.
다음 코드는 PySide6에서 자주 사용되는 다양한 기본 위젯(QCheckBox, QComboBox, QSlider 등)을 수직으로 나열하여, 각 위젯이 어떤 형태로 표시되는지 시각적으로 보여줍니다.
import sys
from PySide6.QtWidgets import (
QApplication, QCheckBox, QComboBox, QDateEdit, QDial,
QLabel, QLineEdit, QMainWindow, QPushButton, QSlider,
QSpinBox, QVBoxLayout, QWidget
)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Widgets App")
layout = QVBoxLayout()
# 자주 사용되는 위젯 인스턴스 생성 및 추가
layout.addWidget(QLabel("Label: Hello World"))
layout.addWidget(QPushButton("Button"))
layout.addWidget(QCheckBox("Checkbox"))
layout.addWidget(QLineEdit("Text Input"))
layout.addWidget(QComboBox()) # 드롭다운
layout.addWidget(QSlider())
layout.addWidget(QSpinBox()) # 숫자 입력
layout.addWidget(QDateEdit())
layout.addWidget(QDial())
central_widget = QWidget()
central_widget.setLayout(layout)
self.setCentralWidget(central_widget)
#... (애플리케이션 실행 코드 생략)
4.2. QComboBox (드롭다운 목록) 심층 분석
QComboBox는 현재 선택된 항목을 표시하고, 클릭 시 선택 가능한 항목 목록을 팝업 형태로 제공하는 선택 위젯입니다.3 항목을 추가할 때는 addItem() 메서드를 사용합니다.
QComboBox는 목록의 동적인 관리를 위해 다양한 항목 삽입 정책을 제공합니다. 개발자는 QComboBox.InsertPolicy 상수를 사용하여 항목을 삽입할 때의 위치와 순서를 정밀하게 제어할 수 있습니다. 예를 들어, InsertAlphabetically 정책을 사용하면 새로운 항목이 자동으로 알파벳 순서에 맞춰 삽입되므로, 복잡한 정렬 로직을 수동으로 구현할 필요 없이 사용자에게 일관되고 정렬된 목록을 제공할 수 있습니다. 이는 사용자 경험을 개선하고 개발 효율성을 높이는 중요한 기능입니다.4
QComboBox 항목 삽입 정책
상수 (InsertPolicy) | 동작 | 출처 |
| 목록의 첫 번째 항목으로 삽입 | 4 |
| 목록의 마지막 항목 뒤에 삽입 | 4 |
| 알파벳 순으로 정렬하여 삽입 | 4 |
| 현재 선택된 항목을 새로운 항목으로 대체 | 4 |
4.3. QSlider 및 QCheckBox
QSlider는 지정된 범위 내에서 값을 시각적으로 선택할 수 있도록 해주는 위젯입니다. setRange()를 통해 최소값과 최대값을 설정할 수 있으며, 사용자가 값을 변경할 때 valueChanged 시그널이 방출되어 실시간으로 값을 처리할 수 있습니다. QCheckBox는 두 가지 상태(체크됨/체크되지 않음) 중 하나를 선택할 수 있게 하며, Qt.CheckState 열거형을 통해 현재 상태를 확인하고 프로그램적으로 변경할 수 있습니다.
V: PySide6 Layouts (레이아웃을 사용한 깔끔한 배치)
레이아웃은 PySide6 애플리케이션이 다양한 화면 크기나 해상도에 유연하게 대응할 수 있도록 위젯의 위치와 크기를 자동으로 관리하는 핵심 시스템입니다.
시각화 헬퍼 위젯 (Color 클래스): 레이아웃 시각화를 위해 배경색이 지정된 간단한 QWidget를 사용합니다.
from PySide6.QtGui import QColor, QPalette
from PySide6.QtWidgets import QWidget
class Color(QWidget):
def __init__(self, color):
super().__init__()
self.setAutoFillBackground(True) # 배경 채우기 활성화
palette = self.palette()
palette.setColor(QPalette.ColorRole.Window, QColor(color))
self.setPalette(palette)
5.1. 레이아웃의 종류와 역할
Qt는 기본적인 위젯 배치 요구 사항을 충족하는 네 가지 주요 레이아웃 클래스를 제공합니다.
PySide6 기본 레이아웃 유형
레이아웃 클래스 | 행동 양식 | 주요 용도 | 출처 |
| 선형 수평 배치 | 툴바, 가로 버튼 그룹 | 5 |
| 선형 수직 배치 | 폼 입력 필드, 세로 목록 | 5 |
| 행/열 격자 배치 | 계산기 인터페이스, 테이블 형태 UI | 5 |
| 스택형 배치 (한 번에 하나만 표시) | 탭/페이지 전환 | 5 |
5.2. 레이아웃 적용의 '골든 룰' (QMainWindow 제약 회피)
QMainWindow는 자체적으로 메뉴 바나 툴바와 같은 특정 구조를 관리하도록 설계되어 있기 때문에, 창의 중앙 영역에는 오직 하나의 위젯(setCentralWidget)만 설정될 수 있습니다. 레이아웃 클래스 자체는 위젯이 아니므로, QMainWindow에 직접 적용할 수 없습니다.
이러한 제약을 극복하기 위해서는 반드시 QWidget 인스턴스를 생성하여 이를 레이아웃의 컨테이너(Container) 역할을 하도록 만들어야 합니다. 즉, 원하는 레이아웃을 생성하고 위젯들을 추가한 후, 이 레이아웃을 새로운 QWidget에 적용(widget.setLayout(layout))해야 합니다. 최종적으로 이 컨테이너 QWidget를 메인 창의 중앙 위젯으로 설정함으로써 레이아웃이 화면에 표시됩니다. 이 QWidget 컨테이너 패턴은 PySide6 레이아웃 설계의 가장 기본적인 원칙입니다.
5.3. 선형 레이아웃 (H/V) 및 중첩
QVBoxLayout과 QHBoxLayout은 위젯을 한 방향으로 선형적으로 나열합니다. 복잡한 UI는 이러한 선형 레이아웃을 중첩하여 구성합니다. .addLayout() 메서드를 사용하면 하나의 레이아웃 객체 안에 다른 레이아웃 객체를 삽입할 수 있습니다.5 예를 들어, QHBoxLayout에 여러 개의 QVBoxLayout을 추가하여 여러 개의 수직 열로 이루어진 구조를 만들 수 있습니다.5 레이아웃의 시각적 조정은 레이아웃 내부의 간격을 조정하는 .setSpacing()과 레이아웃 주변의 여백을 설정하는 .setContentsMargins()를 통해 수행됩니다.5
수평 및 수직 레이아웃 (H/VBoxLayout) 코드 예시:
import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QHBoxLayout, QVBoxLayout, QWidget
# from layout_colorwidget import Color (Color 클래스 정의 필요)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("HBoxLayout & VBoxLayout Demo")
h_layout = QHBoxLayout() # 수평 레이아웃 (최상위 컨테이너)
v_layout_left = QVBoxLayout() # 수직 레이아웃 (왼쪽 열)
v_layout_left.addWidget(Color('red'))
v_layout_left.addWidget(Color('yellow'))
v_layout_right = QVBoxLayout() # 수직 레이아웃 (오른쪽 열)
v_layout_right.addWidget(Color('green'))
v_layout_right.addWidget(Color('blue'))
h_layout.addLayout(v_layout_left) # 수직 레이아웃을 수평 컨테이너에 중첩
h_layout.addLayout(v_layout_right)
# QMainWindow의 중앙 위젯으로 설정하기 위한 컨테이너 위젯
widget = QWidget()
widget.setLayout(h_layout)
self.setCentralWidget(widget)
#...
5.4. QGridLayout 및 좌표 기반 배치
QGridLayout은 위젯을 2차원 격자 시스템에 배치하며, layout.addWidget(widget, row, column) 형태로 위젯의 정확한 행과 열 위치를 지정합니다.5 이 레이아웃은 모든 격자를 채울 필요가 없으며, 위젯을 유연하게 배치할 수 있도록 설계되어, 계산기 인터페이스나 표 형태의 데이터를 시각화할 때 매우 유용합니다.
QGridLayout 코드 예시:
from PySide6.QtWidgets import QGridLayout
# from layout_colorwidget import Color
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("QGridLayout Demo")
layout = QGridLayout()
# (위젯, 행, 열)
layout.addWidget(Color('red'), 0, 0)
layout.addWidget(Color('green'), 1, 0)
layout.addWidget(Color('blue'), 1, 1)
layout.addWidget(Color('purple'), 2, 1) # 빈칸 건너뛰기 가능
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
#...
5.5. QStackedLayout을 이용한 페이지 전환 (동적 UI)
QStackedLayout은 여러 위젯을 동일한 공간에 겹쳐 쌓아두고, 한 번에 하나의 위젯만 보이도록 관리하는 레이아웃입니다. 이는 탭 인터페이스나 다단계 양식과 같이 페이지 전환이 필요한 동적 UI를 구현하는 데 필수적입니다.5 위젯을 추가한 후, layout.setCurrentIndex(index) 또는 layout.setCurrentWidget(widget) 메서드를 호출하여 현재 활성화된 위젯을 변경합니다. 일반적으로 상단의 버튼들의 시그널을 연결하여 이 인덱스를 제어함으로써 탭 전환 기능을 구현합니다.
QStackedLayout 및 버튼 제어 코드 예시:
import sys
from PySide6.QtWidgets import (
QApplication, QHBoxLayout, QMainWindow, QPushButton,
QStackedLayout, QVBoxLayout, QWidget,
)
# from layout_colorwidget import Color
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("QStackedLayout Demo")
pagelayout = QVBoxLayout()
button_layout = QHBoxLayout()
self.stacklayout = QStackedLayout()
pagelayout.addLayout(button_layout)
pagelayout.addLayout(self.stacklayout)
# 버튼 1: 인덱스 0 활성화
btn1 = QPushButton("Red Page")
btn1.pressed.connect(lambda: self.stacklayout.setCurrentIndex(0))
button_layout.addWidget(btn1)
self.stacklayout.addWidget(Color("red"))
# 버튼 2: 인덱스 1 활성화
btn2 = QPushButton("Green Page")
btn2.pressed.connect(lambda: self.stacklayout.setCurrentIndex(1))
button_layout.addWidget(btn2)
self.stacklayout.addWidget(Color("green"))
widget = QWidget()
widget.setLayout(pagelayout)
self.setCentralWidget(widget)
#...
VI: PySide6 Toolbars & Menus — QAction
6.1. QAction: 논리적 명령의 추상화
QAction은 PySide6의 UI 요소를 통합적으로 관리하는 클래스입니다. 이는 '저장'이나 '인쇄'와 같은 애플리케이션의 단일 논리적 명령을 정의하고, 이 명령을 툴바 버튼, 메뉴 항목, 키보드 단축키 등 여러 UI 요소에 연결하여 사용 중복을 최소화합니다.
QAction은 아이콘, 레이블, 상태 팁, 그리고 triggered 시그널을 포함합니다. QAction 객체는 setCheckable(True)를 통해 토글 버튼처럼 사용할 수 있으며, 이때 triggered 시그널은 버튼의 새로운 체크 상태(부울)를 데이터로 전달합니다.
6.2. 툴바 (QToolBar) 구현 및 커스터마이징
QToolBar 인스턴스를 생성하고 addToolBar()를 통해 QMainWindow에 추가하여 툴바를 생성합니다. 툴바는 setIconSize()로 아이콘 크기를 조정할 수 있으며, toolbar.addAction()을 사용하여 정의된 QAction을 버튼 형태로 추가합니다.
QToolBar는 높은 유연성을 자랑합니다. toolbar.addWidget(QWidget)를 사용하면 표준화된 버튼 외에도 QLabel이나 QCheckBox와 같은 임의의 위젯을 툴바에 포함시킬 수 있습니다. 이 기능은 툴바를 단순히 명령어 집합으로 사용하는 것을 넘어, 실시간 상태 표시기나 특정 설정을 통합하는 데 유용합니다.6 요소들 간의 시각적 구분을 위해 toolbar.addSeparator()를 사용합니다.
툴바 및 QAction 코드 예시:
from PySide6.QtCore import QSize
from PySide6.QtGui import QAction, QIcon
from PySide6.QtWidgets import QMainWindow, QStatusBar, QToolBar, QLabel
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("QAction & Toolbar Demo")
label = QLabel("Hello!")
self.setCentralWidget(label)
# 1. 툴바 설정 및 추가
toolbar = QToolBar("My main toolbar")
toolbar.setIconSize(QSize(16, 16))
self.addToolBar(toolbar)
# 2. QAction 정의
# QIcon("bug.png")는 예시 아이콘 경로. 실제 파일 필요
button_action = QAction(QIcon("bug.png"), "Your button", self)
button_action.setStatusTip("This is your button")
button_action.triggered.connect(self.toolbar_button_clicked)
button_action.setCheckable(True) # 토글 가능 설정
# 3. 툴바에 QAction 추가
toolbar.addAction(button_action)
# 4. 상태 표시줄 설정
self.setStatusBar(QStatusBar(self))
def toolbar_button_clicked(self, s):
# s는 checked 상태 (True/False)
print("Action triggered. Checked state:", s)
#...
6.3. 메뉴 바 (QMenuBar) 및 단축키 통합
메뉴 바는 self.menuBar() 메서드를 통해 접근합니다. addMenu("&MenuName")을 사용하여 "File"과 같은 최상위 메뉴를 정의하는데, 이때 메뉴 이름 앞에 붙이는 앰퍼샌드()는 Alt 키와 함께 사용할 수 있는 가속 키(Accelerator Key)를 지정하여 키보드 접근성을 향상시킵니다.
메뉴 항목은 file_menu.addAction(qaction)을 통해 QAction을 추가하는 방식으로 구성됩니다. QAction은 setShortcut(QKeySequence("Ctrl+p"))와 같은 메서드를 통해 키보드 단축키를 할당받을 수 있으며, 이 단축키는 해당 액션이 툴바에 있든, 메뉴에 있든, 심지어 숨겨져 있든 관계없이 전역적으로 작동하여 사용자 편의성을 높입니다.
메뉴, 단축키, 툴바 통합 코드 예시:
from PySide6.QtGui import QAction, QKeySequence, QIcon
from PySide6.QtWidgets import QCheckBox, QLabel, QMainWindow, QToolBar
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
#... (중앙 위젯 및 툴바 설정)
toolbar = QToolBar("My main toolbar")
self.addToolBar(toolbar)
# QAction 정의 (아이콘, 레이블, 부모)
# 단축키 설정: Ctrl+P
action_print = QAction(QIcon("print.png"), "&Print", self)
action_print.setShortcut(QKeySequence("Ctrl+p"))
action_print.setStatusTip("Print the current document")
action_print.triggered.connect(lambda: print("Printing..."))
toolbar.addAction(action_print)
toolbar.addSeparator()
# 툴바에 위젯 추가
toolbar.addWidget(QLabel("Option:"))
toolbar.addWidget(QCheckBox())
# 1. 메뉴 바 설정
menu = self.menuBar()
file_menu = menu.addMenu("&File") # File 메뉴 (Alt+F로 접근 가능)
file_menu.addAction(action_print) # 메뉴에 Action 추가
file_menu.addSeparator()
# 2. 하위 메뉴 (Submenu)
edit_menu = menu.addMenu("&Edit")
sub_menu = edit_menu.addMenu("Sub &Options")
sub_menu.addAction(QAction("Setting 1", self))
#...
6.4. QStatusBar를 통한 사용자 피드백
QStatusBar는 창의 가장 아래쪽에 위치하며, setStatusBar(QStatusBar(self))를 통해 메인 창에 추가됩니다.6 이 상태 표시줄은 사용자에게 애플리케이션 상태나 임시적인 정보를 전달하는 데 사용됩니다. QAction에 setStatusTip("ToolTip Text")을 설정하면, 사용자가 해당 액션 위에 마우스를 올릴 때 이 텍스트가 상태 표시줄에 자동으로 나타나므로, 직관적인 사용자 피드백 메커니즘을 구축할 수 있습니다.
VII: PySide6 Dialogs and Alerts
7.1. QDialog의 모달 동작과 실행 차단
다이얼로그(Dialogs)는 사용자로부터 필수적인 정보를 얻거나 중요한 알림을 제공하기 위한 보조 창입니다. QDialog 클래스는 이러한 다이얼로그 박스를 처리하는 데 사용되며, 주로 모달(Modal) 창으로 작동합니다. 모달 창은 다이얼로그가 닫히기 전까지 메인 애플리케이션의 다른 창과의 상호작용을 일시적으로 차단합니다.
다이얼로그를 실행하는 dlg.exec() 메서드는 실행되는 동안 메인 애플리케이션의 실행을 차단하고, 다이얼로그 전용 이벤트 루프를 시작합니다. 다이얼로그를 생성할 때 QDialog(self)와 같이 메인 창(self)을 부모(Parent)로 지정하는 것은 중요합니다. 이는 다이얼로그를 부모 창에 종속시켜 부모 창 중앙에 위치하도록 하고, 모달 창으로서의 올바른 동작을 보장합니다.
7.2. Custom QDialog 구현 및 QDialogButtonBox 활용
복잡한 사용자 입력을 처리하기 위해 QDialog를 상속받아 커스텀 다이얼로그를 구현합니다. 다이얼로그의 레이아웃을 설정한 후, 버튼을 배치하기 위해 QDialogButtonBox를 사용합니다. 이 버튼 박스는 데스크톱 UI 표준에 따라 버튼을 배치해줍니다.
버튼은 QDialogButtonBox.Ok | QDialogButtonBox.Cancel과 같이 비트 OR 연산자를 사용하여 조합할 수 있으며, 버튼 박스의 accepted 및 rejected 시그널을 QDialog의 내장 슬롯인 accept() 및 reject()에 연결합니다.7 dlg.exec()의 반환 값을 확인하여 사용자가 다이얼로그를 수락했는지(True) 또는 거부했는지(False)에 따라 로직을 분기 처리할 수 있습니다.
Custom QDialog 및 QDialogButtonBox 코드 예시:
from PySide6.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel
class CustomDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent) # 부모 전달 (중앙 정렬 및 모달 동작)
self.setWindowTitle("Custom Dialog")
# 표준 Ok/Cancel 버튼 정의
QBtn = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
self.buttonBox = QDialogButtonBox(QBtn)
# 버튼 시그널을 QDialog 내장 슬롯에 연결
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
layout = QVBoxLayout()
layout.addWidget(QLabel("Is everything OK?"))
layout.addWidget(self.buttonBox)
self.setLayout(layout)
# 메인 창에서 다이얼로그 실행 및 결과 확인
class MainWindow(QMainWindow):
#... (생략)
def button_clicked(self, s):
dlg = CustomDialog(self)
if dlg.exec(): # exec() 호출 시 실행 차단
print("Dialog Success!")
else:
print("Dialog Cancel!")
QDialogButtonBox 표준 버튼 목록
버튼 상수 | 용도 및 의미 | 출처 |
| 동작 승인 및 계속 | 7 |
| 동작 취소 | 7 |
| 긍정 응답 | 7 |
| 부정 응답 | 7 |
| 변경 사항 폐기 | 7 |
7.3. QMessageBox를 이용한 표준 알림창
QMessageBox는 Qt에서 제공하는 표준 알림창으로, 정보, 경고, 질문 또는 치명적인 오류를 사용자에게 신속하게 전달할 때 사용됩니다. QMessageBox는 자체적으로 아이콘과 표준 버튼 세트를 제공합니다.
개발자는 QMessageBox.information(), QMessageBox.question(), QMessageBox.critical()과 같은 내장 간편 메서드를 사용하여 코드를 간결하게 작성할 수 있습니다. 예를 들어, .question() 메서드는 질문을 표시하고, 사용자가 누른 버튼(예: QMessageBox.Yes 또는 QMessageBox.No)을 즉시 반환합니다. 또한, setStandardButtons() 메서드를 통해 QMessageBox.Discard | QMessageBox.NoToAll | QMessageBox.Ignore와 같이 커스텀 버튼 세트와 기본 선택 버튼을 지정할 수도 있습니다.
QMessageBox 간편 메서드 코드 예시 (.question):
from PySide6.QtWidgets import QMessageBox
class MainWindow(QMainWindow):
#... (생략)
def button_clicked(self, s):
# QMessageBox.question() 간편 메서드 사용
button = QMessageBox.question(
self,
"Question Dialog",
"Do you want to continue with the process?",
# 기본적으로 Yes와 No 버튼을 포함
)
if button == QMessageBox.Yes:
print("User chose Yes.")
else:
print("User chose No.")
QMessageBox 표준 아이콘
아이콘 상수 | 용도 | 출처 |
| 일반 정보 알림 | 7 |
| 사용자에게 확인을 요구하는 질문 | 7 |
| 잠재적인 문제에 대한 경고 | 7 |
| 치명적인 오류나 시스템 문제 알림 | 7 |
VIII: Creating Additional Windows in PySide6
8.1. 보조 창 생성의 기본 원칙
Qt의 아키텍처에서, 부모 위젯이 지정되지 않은 QWidget 또는 QMainWindow 인스턴스는 모두 독립적인 최상위 창(Top-level window)으로 간주되어 화면에 별도로 표시됩니다.
그러나 보조 창을 생성하고 관리할 때 파이썬의 메모리 관리 방식을 고려해야 합니다. 만약 새로운 창 인스턴스가 메인 창의 메서드 내에서 지역 변수로만 생성될 경우, 해당 메서드가 종료되는 순간 파이썬의 가비지 컬렉터(GC)가 해당 객체에 대한 참조가 사라졌다고 판단하여 창을 파괴하고 닫아버릴 수 있습니다. 따라서 개발자는 창 객체를 메인 창 클래스의 속성(예: self.w)으로 저장하여 참조를 명시적으로 유지해야 하며, 이는 창이 애플리케이션 실행 내내 지속적으로 유지되도록 보장하는 필수적인 개발 기법입니다.
8.2. 윈도우 생명 주기 관리 3가지 전략
보조 창의 생명 주기를 관리하는 세 가지 주요 전략이 있습니다. 실무에서는 창의 필요성과 상태 보존 요구에 따라 전략을 선택합니다.
임시 유지 (Initial Check):
창을 최초 한 번만 생성하고 self.w에 참조를 저장한 후 .show()를 호출하는 방식입니다. 이 방식은 창이 닫히면 참조가 유지되지 않아 재개방 시 문제가 발생할 수 있습니다.8
토글 (Create/Close):
창이 없을 때 생성하여 보여주고, 버튼을 다시 클릭하면 .close()를 호출한 후 self.w = None으로 참조를 제거하는 방식입니다. 이 방식은 창이 닫힐 때마다 메모리가 완전히 해제되지만, 재개방 시 창을 다시 생성하는 데 자원이 소모됩니다.
영구 유지 (Show/Hide) - 권장:
가장 효율적인 방법으로, 창 객체를 MainWindow의 init 메서드에서 미리 생성하여 영구적인 참조를 유지합니다(self.w = AnotherWindow()). 버튼 클릭 시에는 self.w.isVisible() 상태를 확인하여 창이 보이지 않으면 .show()를, 보이면 .hide()를 호출하여 가시성만 전환합니다.8 이 방법은 창을 매번 생성하는 오버헤드를 피하고, 창이 숨겨져 있는 동안에도 내부 위젯의 상태(예: 입력된 텍스트)를 완벽하게 보존할 수 있다는 강력한 이점을 제공합니다.
영구 유지 (Show/Hide) 전략 코드 예시:
import sys
from PySide6.QtWidgets import QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget
class AnotherWindow(QWidget):
# 부모가 없는 QWidget은 독립적인 창으로 작동
def __init__(self):
super().__init__()
layout = QVBoxLayout()
layout.addWidget(QLabel("This is the auxiliary window."))
self.setLayout(layout)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.w = AnotherWindow() # (1) 창을 __init__에서 생성하여 영구 참조 유지
self.button = QPushButton("Toggle Auxiliary Window")
self.button.clicked.connect(self.toggle_window)
self.setCentralWidget(self.button)
def toggle_window(self, checked):
# (2).isVisible()을 사용하여 상태 토글
if self.w.isVisible():
self.w.hide()
else:
self.w.show()
#...
8.3. 다중 창 효율적 제어
여러 개의 보조 창을 관리해야 할 경우, 각 창 인스턴스에 대해 self.window1, self.window2와 같이 별도의 속성을 할당하여 참조를 유지해야 합니다. 이들을 효율적으로 제어하기 위해, 하나의 범용적인 toggle_window(self, window) 메서드를 정의하고, QPushButton의 clicked.connect에 lambda 함수를 사용하여 특정 창 인스턴스를 인수로 전달하는 방식이 일반적입니다.8 이 방식은 창의 개수와 관계없이 하나의 메서드로 모든 창의 가시성을 관리할 수 있게 하여 코드의 중복을 최소화합니다.
다중 영구 창 관리 코드 예시:
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.window1 = AnotherWindow() # 창 1
self.window2 = AnotherWindow() # 창 2
layout = QVBoxLayout()
button1 = QPushButton("Push for Window 1")
# lambda를 사용하여 toggle_window에 window1 객체 전달
button1.clicked.connect(
lambda checked: self.toggle_window(self.window1),
)
layout.addWidget(button1)
button2 = QPushButton("Push for Window 2")
button2.clicked.connect(
lambda checked: self.toggle_window(self.window2),
)
layout.addWidget(button2)
w = QWidget()
w.setLayout(layout)
self.setCentralWidget(w)
# 범용 토글 메서드
def toggle_window(self, window):
if window.isVisible():
window.hide()
else:
window.show()
#...
IX. 결론 및 작성 가이드라인
이 7단계의 PySide6 입문 로드맵은 파이썬 개발자가 Qt 프레임워크를 활용하여 데스크톱 GUI 애플리케이션을 구축하는 데 필요한 핵심 기술과 구조적 이해를 제공합니다. 창의 생명 주기 관리, 이벤트 처리, 그리고 반응형 UI 설계에 대한 지식은 PySide6 개발의 필수적인 기초입니다.
이 과정을 통해 독자는 PySide6의 이벤트 중심적 아키텍처와 시그널-슬롯 메커니즘의 중요성을 이해하게 되며, 특히 창 객체의 참조를 유지하여 파이썬 가비지 컬렉션을 방지하는 실무적 지식을 습득할 수 있습니다.