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 관련 파일을 폴더로 묶어 관리합니다.

  1. DB 디렉토리 생성
  2. config.py, database_manager.py 파일 선택 
    오른쪽 클릭 >> Refactor >> Move File.. >> DB 디렉토리 선택
  3. 기존 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 에서 연결하지 않고, 오직 "프레임"만 구현합니다.

 

동작 흐름:

  1. MainWindow(QMainWindow)가 실행된다.
  2. QMainWindow 중앙에 container(QWidget)를 붙인다.
  3. container에 QHBoxLayout을 붙여 좌/우를 나눈다.
  4. 좌측에 사이드바(QFrame + QVBoxLayout + 버튼 3개)를 만든다.
  5. 우측에 QStackedWidget(페이지 3개)을 만든다.
  6. 버튼 클릭 시그널 → 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. 실행 테스트

더보기
  1. 창 확인
    • 메인 창 제목: "도서 관리 프로그램"
    • 좌측: 사이드바(짙은색)
    • 우측: 페이지 영역
  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 호출


단계별 완성 파일