6. 구현 - 메인 UI 프레임

1. 목표
더보기


이번 단계는 프로그램의 GUI 전체적인 외관(뼈대)을 만듭니다.
이전 단계에서 구축한 DB/CRUD 로직을 연결하지 않습니다.
프로그램은 사용자가 메뉴를 선택할 수 있는 왼쪽 사이드바와 실제 기능 화면이 표시될 오른쪽 콘텐츠 영역을 나누고,
왼쪽 사이드바 메뉴 버튼 클릭 시 화면이 전환되는 기본 구조를 완성합니다.
- QMainWindow 기반의 메인 창을 만든다.
- 2열 레이아웃으로 구성한다.
- 좌측 "사이드바(내비게이션)"
- 우측 "페이지 영역"
- QHBoxLayout(container)로 좌(사이드바) / 우(페이지 영역) 분할하는 레이아웃 전략을 익힙니다.
- QStackedWidget으로 여러 페이지를 "쌓아두고",
버튼 클릭으로 setCurrentIndex()로 여러 페이지를 전환하는 네비게이션 기능을 구현합니다. - 이후 6~9단계에서 페이지별 기능을 넣기 쉽도록, 페이지를 "독립 QWidget"으로 분리한다.
2. 전체 로직
더보기
(1) 이전 단계에서 구현한 파일에 이어 작업합니다.
libraryManagementSystem/
├─ config.py
├─ database_manager.py
├─ GUI_Test.py
├─ main.py
└─ requirements.txt
(2) DB 관련 파일을 폴더로 묶어 관리합니다.
- DB 디렉토리 생성
- config.py, database_manager.py 파일 선택
오른쪽 클릭 >> Refactor >> Move File.. >> DB 디렉토리 선택 - 기존 import 경로 변경 확인 or 수동 경로 변경
libraryManagementSystem/
├─ db/
│ ├─ config.py
│ └─ database_manager.py
├─ GUI_Test.py
├─ main.py
└─ requirements.txt
(3) 이전 단계에서 사용했던GUI_Test.py 를 아래 library_system.py 로 대체하여 사용합니다.
library_system.py
# library_system.py
from PySide6.QtWidgets import (
QMainWindow, QWidget,
QHBoxLayout, QVBoxLayout,
QPushButton, QLabel, QFrame, QStackedWidget
)
from PySide6.QtCore import Qt
from simple_page import SimplePage
class LibrarySystem(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("도서 관리 프로그램")
self.resize(1200, 800)
container = QWidget()
self.setCentralWidget(container)
main_layout = QHBoxLayout(container)
main_layout.setContentsMargins(0, 0, 0, 0)
# 사이드바
self.sidebar = QFrame()
self.sidebar.setStyleSheet("background-color: #2c3e50; min-width: 200px;")
sidebar_layout = QVBoxLayout(self.sidebar)
title_label = QLabel("도서관 시스템")
title_label.setStyleSheet("color: white; font-size: 24px; font-weight: bold; padding: 20px;")
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
sidebar_layout.addWidget(title_label)
self.btn_1 = QPushButton("대출 / 반납")
self.btn_2 = QPushButton("도서 관리")
self.btn_3 = QPushButton("회원 관리")
btn_style = "color: white; background-color: transparent; padding: 15px; text-align: left; font-size: 16px;"
self.btn_1.setStyleSheet(btn_style)
self.btn_2.setStyleSheet(btn_style)
self.btn_3.setStyleSheet(btn_style)
sidebar_layout.addWidget(self.btn_1)
sidebar_layout.addWidget(self.btn_2)
sidebar_layout.addWidget(self.btn_3)
sidebar_layout.addStretch()
self.pages = QStackedWidget()
page1 = SimplePage(
"대출 / 반납 (뼈대)",
"이 단계에서는 DB 없이 페이지 전환 구조만 학습합니다.\n"
"다음 단계에서 회원 조회/대출/반납 로직을 추가합니다."
)
page2 = SimplePage(
"도서 관리 (뼈대)",
"도서 등록/검색/삭제 UI 및 DB 연동은 다음 단계에서 확장합니다."
)
page3 = SimplePage(
"회원 관리 (뼈대)",
"회원 등록/검색/삭제 UI 및 DB 연동은 다음 단계에서 확장합니다."
)
self.pages.addWidget(page1) # index 0
self.pages.addWidget(page2) # index 1
self.pages.addWidget(page3) # index 2
main_layout.addWidget(self.sidebar)
main_layout.addWidget(self.pages)
self.btn_1.clicked.connect(lambda: self.pages.setCurrentIndex(0))
self.btn_2.clicked.connect(lambda: self.pages.setCurrentIndex(1))
self.btn_3.clicked.connect(lambda: self.pages.setCurrentIndex(2))
(4) 테스트용 GUI 임시 사용
# simple_page.py
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel
class SimplePage(QWidget):
def __init__(self, title: str, description: str, parent=None):
super().__init__(parent)
layout = QVBoxLayout(self)
layout.setContentsMargins(30, 30, 30, 30)
layout.setSpacing(12)
lbl_title = QLabel(title)
from PySide6.QtCore import Qt
lbl_title.setAlignment(Qt.AlignmentFlag.AlignLeft)
lbl_title.setStyleSheet("font-size: 22px; font-weight: bold;")
lbl_desc = QLabel(description)
lbl_desc.setWordWrap(True)
lbl_desc.setStyleSheet("font-size: 14px; color: #444;")
layout.addWidget(lbl_title)
layout.addWidget(lbl_desc)
layout.addStretch()
(5) main.py 수정
libraryManagementSystem/
├─ db/
│ ├─ config.py
│ └─ database_manager.py
├─ library_system.py
├─ main.py
└─ requirements.txt
# main.py
import sys
from PySide6.QtGui import QFont
from PySide6.QtWidgets import QApplication
from library_system import LibrarySystem #GUI_Test.py 에서 library_system.py로 변경
if __name__ == "__main__":
app = QApplication(sys.argv)
font = QFont("Malgun Gothic", 12)
app.setFont(font)
window = LibrarySystem()
window.show()
sys.exit(app.exec())
(6) 핵심 포인트
이 단계의 앱은 DB 가 있지만, GUI 에서 연결하지 않고, 오직 "프레임"만 구현합니다.
동작 흐름:
- MainWindow(QMainWindow)가 실행된다.
- QMainWindow 중앙에 container(QWidget)를 붙인다.
- container에 QHBoxLayout을 붙여 좌/우를 나눈다.
- 좌측에 사이드바(QFrame + QVBoxLayout + 버튼 3개)를 만든다.
- 우측에 QStackedWidget(페이지 3개)을 만든다.
- 버튼 클릭 시그널 → pages.setCurrentIndex(n) 실행 → 페이지 전환
ㄴ 2.1 import + 주석
더보기
import sys # [1] 파이썬 실행/종료(sys.argv, sys.exit) 등에 사용
from PySide6.QtWidgets import ( # [2] Qt 위젯(화면 구성 요소) 모음
QApplication, # [3] Qt 앱 객체(이벤트 루프 실행/전역 설정 담당)
QMainWindow, # [4] 메인 창 프레임(중앙 위젯/메뉴/상태바 확장에 유리)
QWidget, # [5] 가장 기본 컨테이너 위젯(다른 위젯을 담는 바탕)
QHBoxLayout, # [6] 좌우(가로) 배치 레이아웃
QVBoxLayout, # [7] 상하(세로) 배치 레이아웃
QPushButton, # [8] 클릭 가능한 버튼 위젯
QLabel, # [9] 텍스트/이미지 표시용 라벨 위젯
QStackedWidget, # [10] 페이지 전환 컨테이너(여러 화면을 겹쳐 보관 후 하나만 표시)
QFrame # [11] 패널/구획 나누기 용도(스타일 적용에 유리한 컨테이너)
)
from PySide6.QtCore import Qt # [12] 정렬/플래그/상수(AlignCenter 등) 모음
from PySide6.QtGui import QFont # [13] 앱/위젯 글꼴 설정에 사용(예: Malgun Gothic)
ㄴ 2.2 GUI + 주석
더보기
# PySide6에서 메인 윈도우와 레이아웃, UI 컴포넌트를 가져옵니다.
from PySide6.QtWidgets import (
QMainWindow, QWidget,
QHBoxLayout, QVBoxLayout,
QPushButton, QLabel, QFrame, QStackedWidget
)
# 정렬(Alignment) 관련 상수를 사용하기 위해 QtCore에서 Qt를 import 합니다.
from PySide6.QtCore import Qt
# 각 페이지의 "뼈대 UI"를 담당하는 SimplePage 클래스
# 실제 DB 연동 전, 화면 전환 구조만 학습하기 위한 페이지입니다.
from simple_page import SimplePage
class LibrarySystem(QMainWindow):
"""
LibrarySystem 클래스
- 전체 도서 관리 프로그램의 메인 윈도우
- 좌측 사이드바 + 우측 페이지 영역 구조
- QStackedWidget을 이용한 페이지 전환 구조 학습 목적
"""
def __init__(self):
super().__init__()
# --------------------------------------------------
# 1. 메인 윈도우 기본 설정
# --------------------------------------------------
# 윈도우 제목 설정
self.setWindowTitle("도서 관리 프로그램")
# 초기 윈도우 크기 설정
self.resize(1200, 800)
# --------------------------------------------------
# 2. 중앙 컨테이너 위젯 설정
# --------------------------------------------------
# QMainWindow는 반드시 중앙 위젯이 필요하므로
# container(QWidget)를 생성하여 setCentralWidget()으로 설정합니다.
container = QWidget()
self.setCentralWidget(container)
# 메인 레이아웃은 좌우 배치이므로 QHBoxLayout 사용
main_layout = QHBoxLayout(container)
# 윈도우 가장자리 여백 제거
main_layout.setContentsMargins(0, 0, 0, 0)
# --------------------------------------------------
# 3. 사이드바 영역 구성
# --------------------------------------------------
# 사이드바는 하나의 프레임(QFrame)으로 구성
self.sidebar = QFrame()
# 사이드바 배경색과 최소 너비 설정
self.sidebar.setStyleSheet(
"background-color: #2c3e50; min-width: 200px;"
)
# 사이드바 내부는 세로 배치이므로 QVBoxLayout 사용
sidebar_layout = QVBoxLayout(self.sidebar)
# --------------------------------------------------
# 3-1. 사이드바 상단 타이틀
# --------------------------------------------------
title_label = QLabel("도서관 시스템")
# 글자 색상, 크기, 굵기, 패딩 설정
title_label.setStyleSheet(
"color: white; font-size: 24px; font-weight: bold; padding: 20px;"
)
# 가운데 정렬
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
# 사이드바 레이아웃에 타이틀 추가
sidebar_layout.addWidget(title_label)
# --------------------------------------------------
# 3-2. 사이드바 메뉴 버튼 생성
# --------------------------------------------------
# 각 버튼은 페이지 전환용 메뉴 역할만 수행
self.btn_1 = QPushButton("대출 / 반납")
self.btn_2 = QPushButton("도서 관리")
self.btn_3 = QPushButton("회원 관리")
# 공통 버튼 스타일 정의
btn_style = (
"color: white; "
"background-color: transparent; "
"padding: 15px; "
"text-align: left; "
"font-size: 16px;"
)
# 모든 버튼에 동일한 스타일 적용
self.btn_1.setStyleSheet(btn_style)
self.btn_2.setStyleSheet(btn_style)
self.btn_3.setStyleSheet(btn_style)
# 사이드바 레이아웃에 버튼 추가
sidebar_layout.addWidget(self.btn_1)
sidebar_layout.addWidget(self.btn_2)
sidebar_layout.addWidget(self.btn_3)
# 아래 공간을 비워 버튼들이 위쪽에 정렬되도록 처리
sidebar_layout.addStretch()
# --------------------------------------------------
# 4. 페이지 영역(QStackedWidget) 구성
# --------------------------------------------------
# 여러 페이지를 겹쳐두고 index로 전환하기 위한 컨테이너
self.pages = QStackedWidget()
# --------------------------------------------------
# 4-1. 각 페이지(현재는 뼈대 페이지) 생성
# --------------------------------------------------
# 대출 / 반납 페이지 (아직 DB 로직 없음)
page1 = SimplePage(
"대출 / 반납 (뼈대)",
"이 단계에서는 DB 없이 페이지 전환 구조만 학습합니다.\n"
"다음 단계에서 회원 조회/대출/반납 로직을 추가합니다."
)
# 도서 관리 페이지 (UI 구조만 표시)
page2 = SimplePage(
"도서 관리 (뼈대)",
"도서 등록/검색/삭제 UI 및 DB 연동은 다음 단계에서 확장합니다."
)
# 회원 관리 페이지 (UI 구조만 표시)
page3 = SimplePage(
"회원 관리 (뼈대)",
"회원 등록/검색/삭제 UI 및 DB 연동은 다음 단계에서 확장합니다."
)
# --------------------------------------------------
# 4-2. QStackedWidget에 페이지 추가
# --------------------------------------------------
self.pages.addWidget(page1) # index 0
self.pages.addWidget(page2) # index 1
self.pages.addWidget(page3) # index 2
# --------------------------------------------------
# 5. 메인 레이아웃에 사이드바 + 페이지 영역 추가
# --------------------------------------------------
main_layout.addWidget(self.sidebar)
main_layout.addWidget(self.pages)
# --------------------------------------------------
# 6. 버튼과 페이지 전환 연결 (Signal / Slot)
# --------------------------------------------------
# 버튼 클릭 시 QStackedWidget의 현재 페이지 index 변경
self.btn_1.clicked.connect(lambda: self.pages.setCurrentIndex(0))
self.btn_2.clicked.connect(lambda: self.pages.setCurrentIndex(1))
self.btn_3.clicked.connect(lambda: self.pages.setCurrentIndex(2))
ㄴ 2.3 Signal & Slot + 주석
더보기
# [1] 버튼 클릭 시, QStackedWidget의 현재 페이지(index)를 변경한다.
self.btn_1.clicked.connect(lambda: self.pages.setCurrentIndex(0))
self.btn_2.clicked.connect(lambda: self.pages.setCurrentIndex(1))
self.btn_3.clicked.connect(lambda: self.pages.setCurrentIndex(2))
- clicked는 "시그널"
- setCurrentIndex(n)는 pages에 등록된 n번째 페이지를 화면에 표시
- 이 구조가 잡히면 이후 단계에서 "각 페이지 내부 UI/DB 로직"만 채우면 됩니다.
ㄴ 2.4 <메인 UI 프레임> 요약
더보기
LibrarySystem(QMainWindow)
└─ CentralWidget: container(QWidget) <- setCentralWidget(container)
└─ main_layout(QHBoxLayout) <- 좌(사이드바) / 우(페이지) 2열 배치
│
├─ sidebar(QFrame)
│ └─ sidebar_layout(QVBoxLayout)
│ ├─ title_label(QLabel: "도서관 시스템")
│ ├─ btn_1(QPushButton: "대출 / 반납") -> pages.setCurrentIndex(0)
│ ├─ btn_2(QPushButton: "도서 관리") -> pages.setCurrentIndex(1)
│ ├─ btn_3(QPushButton: "회원 관리") -> pages.setCurrentIndex(2)
│ └─ addStretch() <- 하단 여백(버튼 위로 정렬)
│
└─ pages(QStackedWidget) <- "페이지를 교체"가 아니라 "표시 전환"
├─ index 0: CirculationPage(QWidget) <- addWidget 순서 = index
├─ index 1: BookManagerPage(QWidget)
└─ index 2: MemberManagerPage(QWidget)
ㄴ 2.4 <메인 UI 프레임> 상세 주석
더보기
현재 단계의 기능은 "페이지 전환 메커니즘" 1개입니다.
이 기능이 완성되면, 다음 단계에서 각 페이지에 DB 로직을 추가해도 전체 구조가 흔들리지 않습니다.
버튼 클릭 → QStackedWidget.setCurrentIndex() → 페이지 전환
# [1] 페이지 컨테이너 생성
self.pages = QStackedWidget()
# [2] 페이지 3개 생성(현재는 Placeholder)
self.page_circulation = CirculationPage()
self.page_book = BookManagerPage()
self.page_member = MemberManagerPage()
# [3] 컨테이너에 페이지 등록(등록 순서가 index가 됨)
self.pages.addWidget(self.page_circulation) # 0
self.pages.addWidget(self.page_book) # 1
self.pages.addWidget(self.page_member) # 2
# [4] 버튼 클릭 -> index 변경 -> 화면 전환
self.btn_1.clicked.connect(lambda: self.pages.setCurrentIndex(0))
self.btn_2.clicked.connect(lambda: self.pages.setCurrentIndex(1))
self.btn_3.clicked.connect(lambda: self.pages.setCurrentIndex(2))
3. 실행 테스트
더보기
- 창 확인
- 메인 창 제목: "도서 관리 프로그램"
- 좌측: 사이드바(짙은색)
- 우측: 페이지 영역
- 화면 전환 테스트
- "대출 / 반납" 클릭 -> 대출/반납 페이지 라벨 확인
- "도서 관리" 클릭 -> 도서 관리 페이지 라벨 확인
- "회원 관리" 클릭 -> 회원 관리 페이지 라벨 확인
- 스타일/레이아웃 체크
- 사이드바 폭이 너무 줄어들지 않는지(min-width 적용)
- 메인 레이아웃 여백이 0으로 붙는지 확인
4.학습 주요 포인트
더보기
- "메인 창(QMainWindow)"은 프로그램의 껍데기다.
- "사이드바(QFrame)"는 내비게이션 영역으로 분리하면 스타일/유지보수가 쉬워진다.
- "페이지 컨테이너(QStackedWidget)"는 화면 전환을 가장 단순하게 구현하는 방법 중 하나다.
- 페이지는 "독립 QWidget"으로 분리하면 SRP 관점에서 코드가 깔끔해진다.
- 6~9단계는 이 Shell 구조를 유지한 채로, 각 페이지 내부를 채우는 작업이다.
다음 단계 연결 포인트
- CirculationPage / BookManagerPage / MemberManagerPage를 SimplePage 대신 실제 페이지 클래스로 교체
- DatabaseManager를 추가하여 각 페이지에서 db.execute_query / db.fetch_data 호출
단계별 완성 파일