[Python] QScrollArea

QTabWidget을 사용하여 여러 개의 탭 화면을 만들고, 각 탭의 코드를 파일별로 분리해보았다.

(QScrollArea )

1. 프로젝트 파일 구조

PythonProject5/
├── main.py
│   └─ 프로그램 실행 담당
│
├── main_window.py
│   └─ 전체 창과 탭 연결 담당
│
├── tab_scroll_area.py
│   └─ 스크롤 영역 탭 담당
│
├── tab_list_widget.py
│   └─ 리스트 스크롤 탭 담당
│
└── tab_log_textedit.py
    └─ 로그 스크롤 탭 담당

2. main.py - 프로그램 실행 파일

main.py는 프로그램을 실제로 실행하는 시작 파일이다. QApplication을 만들고, main_window.py에 있는 Widget 클래스를 불러와 화면에 띄운다.

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

# 이 파일이 직접 실행될 때만 아래 코드 실행
if __name__ == "__main__":
    # PySide6 앱 실행 환경 생성
    app = QApplication(sys.argv)

    # 전체 창 객체 생성
    w = Widget()

    # 창 화면 표시
    w.show()

    # 이벤트 루프 실행
    app.exec()

오류 1. IndentationError

IndentationError: expected an indented block after 'if' statement

이 오류는 if문 아래 코드가 들여쓰기 되어 있지 않을 때 발생한다. Python은 중괄호 대신 들여쓰기로 코드 영역을 구분하기 때문에, if문 아래 코드는 반드시 한 단계 안쪽으로 들어가야 한다.

# 잘못된 예
if __name__ == "__main__":
app = QApplication(sys.argv)

# 올바른 예
if __name__ == "__main__":
    app = QApplication(sys.argv)

오류 2. ModuleNotFoundError

ModuleNotFoundError: No module named 'widget'

이 오류는 import하려는 파일명을 Python이 찾지 못할 때 발생한다. 예를 들어 현재 파일명이 main_window.py인데 main.py에서 아래처럼 작성하면 오류가 난다.

from widget import Widget

현재 구조에서는 main_window.py 안에 Widget 클래스가 있으므로 다음처럼 작성해야 한다.

from main_window import Widget

3. main_window.py - 전체 창과 탭 연결

main_window.py는 전체 창을 만들고, 각 탭 파일에서 만든 클래스를 불러와 QTabWidget에 연결하는 역할을 한다.

from PySide6.QtWidgets import QWidget, QVBoxLayout, QTabWidget

# 각 탭 파일에서 클래스 불러오기
from tab_log_textedit import LogTextEditTab
from tab_list_widget import ListWidgetTab
from tab_scroll_area import ScrollAreaTab


class Widget(QWidget):
    def __init__(self, parent=None):
        # QWidget 부모 클래스 초기화
        super().__init__(parent)

        # 창 제목 설정
        self.setWindowTitle("스크롤 예제 통합 (QTabWidget)")

        # 창 크기 설정
        self.resize(420, 340)

        # 전체 세로 레이아웃 생성
        layout = QVBoxLayout(self)

        # 탭 위젯 생성
        self.tab_widget = QTabWidget()

        # 전체 레이아웃에 탭 위젯 추가
        layout.addWidget(self.tab_widget)

        # 각 탭 객체 생성
        scroll_tab = ScrollAreaTab(self)
        list_tab = ListWidgetTab(self)
        log_tab = LogTextEditTab(self)

        # QTabWidget에 탭 추가
        self.tab_widget.addTab(scroll_tab, "스크롤 영역")
        self.tab_widget.addTab(list_tab, "리스트 스크롤")
        self.tab_widget.addTab(log_tab, "로그 스크롤")

오류 3. RuntimeError: super().__init__() 누락

RuntimeError: libshiboken: '__init__' method of object's base class not called

이 오류는 QWidget을 상속받은 클래스에서 부모 클래스 초기화를 하지 않았을 때 발생한다.

# 잘못된 예
class Widget(QWidget):
    def __init__(self):
        self.setWindowTitle("로그인")

# 올바른 예
class Widget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("로그인")

오류 4. __init__ 안에 __init__을 또 만든 경우

실습 중에 아래처럼 __init__ 함수 안에 또 다른 __init__ 함수를 작성하면서 오류가 발생했다.

# 잘못된 예
class Widget(QWidget):
    def __init__(self):
        def __init__(self, parent=None):
            super().__init__(parent)

이렇게 작성하면 바깥쪽 __init__에서는 super().__init__()이 실행되지 않는다. __init__ 함수는 클래스 안에 하나만 작성해야 한다.

# 올바른 예
class Widget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

4. tab_scroll_area.py - QScrollArea 탭

이 파일은 스크롤 영역을 담당한다. 버튼을 누르면 QLabel 항목이 추가되고, 스크롤이 자동으로 아래로 이동한다.

from PySide6.QtWidgets import (
    QWidget, QVBoxLayout, QScrollArea,
    QLabel, QPushButton
)
from PySide6.QtCore import Qt


class ScrollAreaTab(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        # 전체 세로 레이아웃
        layout = QVBoxLayout(self)

        # 항목 추가 버튼
        self.add_button = QPushButton("항목 추가")
        self.add_button.clicked.connect(self.add_item)
        layout.addWidget(self.add_button)

        # 스크롤 영역 생성
        self.scroll_area = QScrollArea()

        # 내부 위젯 크기에 맞게 조절
        self.scroll_area.setWidgetResizable(True)
        layout.addWidget(self.scroll_area)

        # 스크롤 영역 안에 들어갈 실제 내용 위젯
        self.content_widget = QWidget()

        # 내용 위젯 안에 들어갈 세로 레이아웃
        self.content_layout = QVBoxLayout(self.content_widget)

        # 항목을 위쪽부터 정렬
        self.content_layout.setAlignment(Qt.AlignTop)

        # 스크롤 영역에 내용 위젯 연결
        self.scroll_area.setWidget(self.content_widget)

        # 초기 항목 생성
        self.item_count = 0
        for i in range(5):
            self.add_label(f"초기 항목 {i + 1}")

        self.item_count = 5

    # QLabel을 만들어 레이아웃에 추가하는 함수
    def add_label(self, text: str):
        label = QLabel(text)
        self.content_layout.addWidget(label)

    # 버튼 클릭 시 새 항목 추가
    def add_item(self):
        self.item_count += 1
        new_text = f"추가된 항목 {self.item_count}"

        self.add_label(new_text)
        print(new_text)

        # 스크롤바를 가장 아래로 이동
        bar = self.scroll_area.verticalScrollBar()
        bar.setValue(bar.maximum())

5. tab_list_widget.py - QListWidget 탭

이 파일은 QListWidget을 이용해 리스트 항목을 보여주는 탭이다. 버튼을 누르면 리스트에 항목이 추가되고 자동으로 마지막 항목 위치로 이동한다.

from PySide6.QtWidgets import (
    QWidget, QVBoxLayout,
    QPushButton, QListWidget
)


class ListWidgetTab(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        # 전체 세로 레이아웃
        layout = QVBoxLayout(self)

        # 항목 추가 버튼
        self.add_button = QPushButton("항목 추가")
        self.add_button.clicked.connect(self.add_item)
        layout.addWidget(self.add_button)

        # 리스트 위젯 생성
        self.list_widget = QListWidget()
        layout.addWidget(self.list_widget)

        # 초기 항목 추가
        self.item_count = 0
        for i in range(10):
            self.list_widget.addItem(f"초기 항목 {i + 1}")

        self.item_count = 10

    # 버튼 클릭 시 리스트 항목 추가
    def add_item(self):
        self.item_count += 1
        text = f"추가 항목 {self.item_count}"

        self.list_widget.addItem(text)
        print(text)

        # 리스트를 가장 아래로 이동
        self.list_widget.scrollToBottom()

6. tab_log_textedit.py - QTextEdit 로그 탭

이 파일은 QTextEdit을 이용해 로그 메시지를 누적해서 보여주는 탭이다. QTextEdit은 읽기 전용으로 설정하여 사용자가 직접 수정하지 못하게 했다.

from PySide6.QtWidgets import (
    QWidget, QVBoxLayout,
    QPushButton, QTextEdit
)
from PySide6.QtGui import QTextCursor


class LogTextEditTab(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        # 전체 세로 레이아웃
        layout = QVBoxLayout(self)

        # 로그 출력용 QTextEdit
        self.log_textedit = QTextEdit()

        # 사용자가 직접 수정하지 못하도록 읽기 전용 설정
        self.log_textedit.setReadOnly(True)
        layout.addWidget(self.log_textedit)

        # 로그 추가 버튼
        self.log_button = QPushButton("로그 추가")
        self.log_button.clicked.connect(self.add_log)
        layout.addWidget(self.log_button)

        # 로그 개수 저장 변수
        self.log_count = 0

    # 버튼 클릭 시 로그 추가
    def add_log(self):
        self.log_count += 1
        message = f"[로그] {self.log_count}번째 메시지입니다."

        # QTextEdit에 메시지 추가
        self.log_textedit.append(message)
        print(message)

        # 커서를 맨 아래로 이동
        cursor = self.log_textedit.textCursor()
        cursor.movePosition(QTextCursor.End)
        self.log_textedit.setTextCursor(cursor)

        # 커서 위치가 화면에 보이도록 설정
        self.log_textedit.ensureCursorVisible()

7. 오늘 발생했던 오류 정리

오류 원인 해결 방법
IndentationError if문 아래 코드 들여쓰기 누락 if 아래 실행 코드를 공백 4칸 들여쓰기
ModuleNotFoundError import한 파일명을 Python이 찾지 못함 파일명과 import 이름을 일치시키기
RuntimeError: base class __init__ not called QWidget 부모 초기화 누락 super().__init__(parent) 작성
__init__ 중첩 오류 __init__ 안에 또 다른 __init__ 작성 __init__ 함수는 클래스 안에 하나만 작성

8. 실행 흐름 정리

main.py 실행
  ↓
QApplication 생성
  ↓
Widget 객체 생성
  ↓
main_window.py에서 QTabWidget 생성
  ↓
tab_scroll_area.py / tab_list_widget.py / tab_log_textedit.py 불러오기
  ↓
각 탭 객체 생성
  ↓
QTabWidget에 탭 추가
  ↓
w.show()로 화면 표시
  ↓
app.exec()로 프로그램 실행 유지

9. 정리

  • main.py는 프로그램 실행을 담당한다.
  • main_window.py는 전체 창과 QTabWidget 연결을 담당한다.
  • 각 탭 화면은 별도의 파일로 분리하여 관리할 수 있다.
  • QWidget을 상속받으면 반드시 super().__init__()을 호출해야 한다.
  • Python은 들여쓰기가 문법이기 때문에 if문 아래 코드는 반드시 들여쓰기해야 한다.
  • 파일을 분리할 때는 from 파일명 import 클래스명 구조를 정확히 맞춰야 한다.
0123