[Python] PySide6 QTabWidget 실습 / 파일 분리

이번 실습에서는 PySide6의 QTabWidget을 활용하여 여러 개의 화면을 탭 형태로 구성하는 방법을 학습했다.


1. QTabWidget 기본 구조 만들기

가장 먼저 탭 위젯을 생성하고 전체 레이아웃에 배치했다.

self.tab_widget = QTabWidget(self)

layout = QVBoxLayout()
layout.addWidget(self.tab_widget)
self.setLayout(layout)

QTabWidget은 여러 화면을 탭 버튼 형태로 구분하여 보여주는 위젯이다.


2. 첫 번째 탭 - 정보 입력 화면 만들기

첫 번째 탭에서는 이름을 입력하고 버튼을 눌러 값을 출력하는 기능을 구현했다.

widget_form = QWidget()

label_full_name = QLabel("이름 :")

self.line_edit_full_name = QLineEdit()
self.line_edit_full_name.setPlaceholderText("이름을 입력하세요")

button_check = QPushButton("이름 확인")
button_check.clicked.connect(self.button_check_clicked)

이름 입력창과 버튼을 생성한 뒤 슬롯 함수와 연결했다.


3. QHBoxLayout + QVBoxLayout 함께 사용하기

처음에는 이름 라벨과 입력창만 배치했지만, 버튼까지 함께 넣기 위해 레이아웃을 중첩해서 사용했다.

row_layout = QHBoxLayout()
row_layout.addWidget(label_full_name)
row_layout.addWidget(self.line_edit_full_name)

form_layout = QVBoxLayout()
form_layout.addLayout(row_layout)
form_layout.addWidget(button_check)

widget_form.setLayout(form_layout)

구조는 다음과 같다.

세로 레이아웃
 ├── 가로 레이아웃
 │    ├── 이름 라벨
 │    └── 입력창
 └── 이름 확인 버튼

4. 두 번째 탭 - 버튼 화면 만들기

두 번째 탭에서는 여러 개의 버튼을 배치하고 클릭 이벤트를 연결했다.

widget_buttons = QWidget()

button_1 = QPushButton("버튼 1")
button_2 = QPushButton("버튼 2")
button_3 = QPushButton("버튼 3")

button_1.clicked.connect(self.button_1_clicked)
button_2.clicked.connect(self.button_2_clicked)
button_3.clicked.connect(self.button_3_clicked)

세 버튼은 세로 방향으로 배치했다.

buttons_layout = QVBoxLayout()
buttons_layout.addWidget(button_1)
buttons_layout.addWidget(button_2)
buttons_layout.addWidget(button_3)

widget_buttons.setLayout(buttons_layout)

5. 세 번째 탭 - 로그인 화면 만들기

로그인 탭에서는 아이디, 비밀번호 입력과 로그인 버튼을 추가했다.

widget_login = QWidget()

label_id = QLabel("아이디 :")
line_edit_id = QLineEdit()

label_pw = QLabel("비밀번호 :")
line_edit_pw = QLineEdit()
line_edit_pw.setEchoMode(QLineEdit.Password)

login_button = QPushButton("로그인")

비밀번호 입력창은 QLineEdit.Password를 사용하여 입력값이 숨겨지도록 설정했다.


6. 탭 추가하기

생성한 각 화면을 탭 위젯에 추가했다.

self.tab_widget.addTab(widget_form, "정보 입력")
self.tab_widget.addTab(widget_buttons, "버튼들")
self.tab_widget.addTab(widget_login, "로그인")

7. 탭 변경 이벤트 처리하기

탭을 클릭했을 때 어떤 탭이 선택되었는지 출력하도록 구현했다.

self.tab_widget.currentChanged.connect(self.tab_changed)

슬롯 함수:

def tab_changed(self, index):
    tab_text = self.tab_widget.tabText(index)
    print(f"{tab_text} 탭을 클릭했습니다.")

실행 결과:

정보 입력 탭을 클릭했습니다.
버튼들 탭을 클릭했습니다.
로그인 탭을 클릭했습니다.

8. 버튼으로 탭 이동하기

버튼 클릭 시 특정 탭으로 이동하도록 구현했다.

button_next.clicked.connect(self.go_to_second_tab)

이동 함수:

def go_to_second_tab(self):
    self.tab_widget.setCurrentIndex(1)

첫 번째 탭으로 돌아가는 기능도 동일하게 구현했다.

def go_to_first_tab(self):
    self.tab_widget.setCurrentIndex(0)

9. self를 사용하는 이유

다른 함수에서도 사용할 위젯은 반드시 self를 붙여 저장하는 것이 좋다.

잘못된 예:

tab_widget = QTabWidget()

위 방식은 해당 함수 내부에서만 사용 가능한 지역 변수이다.

권장 방식:

self.tab_widget = QTabWidget()

이렇게 하면 클래스 내부 어디서든 접근 가능하다.


10. findChild()와 self 차이

findChild()를 사용하면 위젯을 검색해서 가져올 수 있다.

tab_widget = self.findChild(QTabWidget)

하지만 직접 만든 위젯은 검색해서 찾기보다 self로 저장하는 방식이 더 효율적이다.

  • 코드 가독성 향상
  • 검색 과정 불필요
  • 여러 위젯 관리가 쉬움

11. 최종적으로 파일 분리하기

처음에는 하나의 파일에서 전체 흐름을 이해하고, 코드가 길어지면 기능별로 분리하는 방식이 유지보수에 좋다.

PythonProject3/
├── main.py
├── widget.py
├── info_tab.py
├── button_tab.py
└── login_tab.py

11. 파일을 분리하는 이유와 실행 흐름

코드가 길어지면 하나의 파일에서 모든 기능을 관리하기 어려워진다. 그래서 역할별로 파일을 나누어 관리하면 코드 구조를 더 쉽게 이해할 수 있고, 수정할 때도 필요한 파일만 확인하면 된다.

1) 파일 구조

PythonProject3/
├── main.py
│   └─ 프로그램 실행 담당
│
├── widget.py
│   └─ 전체 창과 탭 연결 담당
│
├── form_tab.py
│   └─ 첫 번째 탭 화면 담당
│
└── buttons_tab.py
    └─ 두 번째 탭 화면 담당

2) 각 파일의 역할

파일명 역할
main.py 프로그램을 실행하는 시작 파일
widget.py 전체 창 생성, QTabWidget 생성, 각 탭 연결 담당
form_tab.py 첫 번째 탭 화면 구성 담당
buttons_tab.py 두 번째 탭 화면 구성 담당

3) widget.py에서 탭 파일 불러오기

탭 화면을 다른 파일로 분리하면, widget.py에서는 각 파일에 있는 클래스를 불러와야 한다.

from form_tab import FormTab
from buttons_tab import ButtonsTab

위 코드는 다음과 같은 의미이다.

from 파일명 import 클래스명

즉, form_tab.py 파일 안에 있는 FormTab 클래스를 가져오고, buttons_tab.py 파일 안에 있는 ButtonsTab 클래스를 가져온다는 뜻이다.

4) widget.py에서 탭 연결하기

불러온 탭 클래스는 객체로 만든 뒤 QTabWidget에 추가한다.

self.tab_widget = QTabWidget()

self.form_tab = FormTab()
self.buttons_tab = ButtonsTab()

self.tab_widget.addTab(self.form_tab, "정보 입력")
self.tab_widget.addTab(self.buttons_tab, "버튼들")

이렇게 하면 widget.py는 직접 화면 내용을 모두 작성하지 않고, 각 탭 파일에서 만든 화면을 불러와 연결하는 역할만 하게 된다.

5) main.py에서 프로그램 실행하기

main.py는 프로그램을 실제로 실행하는 파일이다. 전체 창을 담당하는 Widget 클래스를 불러와 실행한다.

import sys
from PySide6.QtWidgets import QApplication
from widget import Widget

if __name__ == "__main__":
    app = QApplication(sys.argv)

    w = Widget()
    w.show()

    app.exec()

6) if __name__ == "__main__": 를 사용하는 이유

if __name__ == "__main__":는 이 파일이 직접 실행될 때만 아래 코드를 실행하겠다는 의미이다.

예를 들어 main.py를 직접 실행하면 __name__ 값은 "__main__"이 된다.

if __name__ == "__main__":

따라서 이 조건문 안에 있는 실행 코드가 동작한다.

반대로 다른 파일에서 main.py를 불러오는 경우에는 실행 코드가 자동으로 실행되지 않는다. 그래서 파일을 분리한 프로젝트에서는 이 구조를 사용하는 것이 좋다.

7) QApplication이 필요한 이유

PySide6 프로그램은 GUI 프로그램이기 때문에 QApplication 객체가 필요하다. QApplication은 프로그램 전체 실행 환경을 관리한다.

app = QApplication(sys.argv)

여기서 sys.argv는 프로그램 실행 시 전달되는 값을 의미한다. 기본적으로 PySide6 앱을 실행할 때 함께 작성하는 구조라고 이해하면 된다.

8) Widget 객체를 생성하는 이유

Widget 클래스는 전체 창을 담당하는 클래스이다. 따라서 main.py에서는 이 클래스를 객체로 만들어야 실제 창이 생성된다.

w = Widget()

객체를 생성한 뒤에는 화면에 보이도록 show()를 호출한다.

w.show()

9) app.exec()가 필요한 이유

app.exec()는 GUI 프로그램이 종료되지 않고 계속 실행되도록 해주는 코드이다.

app.exec()

이 코드가 있어야 창이 바로 꺼지지 않고, 버튼 클릭이나 탭 이동 같은 사용자 이벤트를 계속 받을 수 있다.

10) 전체 실행 흐름 정리

main.py 실행
  ↓
QApplication 생성
  ↓
Widget 객체 생성
  ↓
widget.py에서 QTabWidget 생성
  ↓
form_tab.py, buttons_tab.py에서 탭 화면 불러오기
  ↓
QTabWidget에 각 탭 추가
  ↓
w.show()로 창 표시
  ↓
app.exec()로 프로그램 실행 유지

11) 정리

  • main.py는 프로그램 실행을 담당한다.
  • widget.py는 전체 창과 탭 연결을 담당한다.
  • form_tab.py는 첫 번째 탭 화면을 담당한다.
  • buttons_tab.py는 두 번째 탭 화면을 담당한다.
  • from 파일명 import 클래스명 형태로 다른 파일의 클래스를 불러온다.
  • if __name__ == "__main__":는 파일이 직접 실행될 때만 실행되도록 하는 조건문이다.
  • app.exec()는 PySide6 프로그램이 계속 실행되도록 유지하는 역할을 한다.

각 탭을 별도의 파일로 분리하면 코드 관리가 훨씬 편리해진다.

 

 

main

import sys
from PySide6.QtWidgets import QApplication
from widget import Widget

app = QApplication(sys.argv)

w = Widget()
w.show()

app.exec()

 

widget

from PySide6.QtWidgets import QWidget, QVBoxLayout, QTabWidget

from info_tab import InfoTab
from button_tab import ButtonTab
from login_tab import LoginTab


class Widget(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("로그인")

        self.tab_widget = QTabWidget()

        self.info_tab = InfoTab(self.tab_widget)
        self.button_tab = ButtonTab(self.tab_widget)
        self.login_tab = LoginTab()

        self.tab_widget.addTab(self.info_tab, "정보 입력")
        self.tab_widget.addTab(self.button_tab, "버튼들")
        self.tab_widget.addTab(self.login_tab, "로그인")

        self.tab_widget.currentChanged.connect(self.tab_changed)

        layout = QVBoxLayout()
        layout.addWidget(self.tab_widget)
        self.setLayout(layout)

    def tab_changed(self, index):
        tab_text = self.tab_widget.tabText(index)
        print(f"{tab_text} 탭을 클릭했습니다.")

 

 

info_tab

from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QPushButton, QLabel, QLineEdit


class InfoTab(QWidget):
    def __init__(self, tab_widget):
        super().__init__()

        self.tab_widget = tab_widget

        label_full_name = QLabel("이름 :")

        self.line_edit_full_name = QLineEdit()
        self.line_edit_full_name.setPlaceholderText("이름을 입력하세요")

        button_check = QPushButton("이름 확인")
        button_check.clicked.connect(self.button_check_clicked)

        button_next = QPushButton("두 번째 탭으로 이동")
        button_next.clicked.connect(self.go_to_second_tab)

        row_layout = QHBoxLayout()
        row_layout.addWidget(label_full_name)
        row_layout.addWidget(self.line_edit_full_name)

        form_layout = QVBoxLayout()
        form_layout.addLayout(row_layout)
        form_layout.addWidget(button_check)
        form_layout.addWidget(button_next)

        self.setLayout(form_layout)

    def button_check_clicked(self):
        name = self.line_edit_full_name.text()

        if not name:
            print("이름을 먼저 입력해주세요")
        else:
            print(name, "님 버튼 1을 클릭했습니다.")

    def go_to_second_tab(self):
        self.tab_widget.setCurrentIndex(1)

 

button_tab

from PySide6.QtWidgets import QWidget, QVBoxLayout, QPushButton


class ButtonTab(QWidget):
    def __init__(self, tab_widget):
        super().__init__()

        self.tab_widget = tab_widget

        button_1 = QPushButton("버튼 1")
        button_2 = QPushButton("버튼 2")
        button_3 = QPushButton("첫 번째 탭으로 이동")

        button_1.clicked.connect(self.button_1_clicked)
        button_2.clicked.connect(self.button_2_clicked)
        button_3.clicked.connect(self.go_to_first_tab)

        layout = QVBoxLayout()
        layout.addWidget(button_1)
        layout.addWidget(button_2)
        layout.addWidget(button_3)

        self.setLayout(layout)

    def button_1_clicked(self):
        print("버튼 1이 클릭되었습니다.")

    def button_2_clicked(self):
        print("버튼 2가 클릭되었습니다.")

    def go_to_first_tab(self):
        self.tab_widget.setCurrentIndex(0)

 

 

login_tab

from PySide6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QLineEdit


class LoginTab(QWidget):
    def __init__(self):
        super().__init__()

        label_id = QLabel("아이디 :")
        self.line_edit_id = QLineEdit()
        self.line_edit_id.setPlaceholderText("아이디를 입력하세요")

        label_pw = QLabel("비밀번호 :")
        self.line_edit_pw = QLineEdit()
        self.line_edit_pw.setPlaceholderText("비밀번호를 입력하세요")
        self.line_edit_pw.setEchoMode(QLineEdit.Password)

        login_button = QPushButton("로그인")
        login_button.clicked.connect(self.login_button_clicked)

        layout = QVBoxLayout()
        layout.addWidget(label_id)
        layout.addWidget(self.line_edit_id)
        layout.addWidget(label_pw)
        layout.addWidget(self.line_edit_pw)
        layout.addWidget(login_button)

        self.setLayout(layout)

    def login_button_clicked(self):
        print("로그인 되었습니다.")

 

 

스크린캐스트 2026-05-22 17-10-19.webm
0.17MB