0. 학습 목표

→ 하나의 Model을 여러 View가 함께 사용하는 구조를 실습합니다.

더보기

이번 4.2 단계에서는 <<하나의 Model모델 >>  <<여러 View>>가  함께 사용하는 구조를 실습합니다.

 

1개의 QFileSystemModel파일 시스템 모델 을 만들고, 이 Model을

2개의 QTreeView트리 뷰QListView리스트 뷰  View에 연결합니다.

 

이를 통해 같은 데이터를 여러 View가 서로 다른 방식으로 보여줄 수 있다는 점을 직접 확인합니다.

학습 목표 내용
1개의 Model과
2개의 View 구조 이해
QTreeView와 QListView가
하나의 QFileSystemModel을 함께 사용하는 구조를 이해합니다.
QFileSystemModel 역할 이해 QFileSystemModel이 파일과 폴더 정보를
Model 형태로 제공한다는 점을 이해합니다.
QTreeView와
QListView 역할 이해
두 View는 데이터를 직접 저장하지 않고,
Model의 데이터를 각자의 방식으로 보여준다는 점을 이해합니다.
setModel() 구조 이해 View에 Model을 연결할 때 setModel()모델 연결 메서드을 사용한다는 점을 이해합니다.
선택 변경에 따른 View 갱신 이해 트리에서 선택한 폴더에 따라 리스트의 기준 위치가 바뀌는 흐름을 이해합니다.

이번 단계의 핵심
파일/폴더 정보는 QFileSystemModel 하나를 통해 제공되고,

QTreeView와 QListView는 그 데이터를 각자의 방식으로 보여주는 View입니다.

 

즉, 데이터는 하나, 표현 방식은 여러 개입니다.

 

1. 이번 예제 구조 살펴보기

→ 왼쪽 TreeView와 오른쪽 ListView가 하나의 Model을 공유하는 화면을 만듭니다.

더보기

1.1 화면 구성

이번 예제에서는 하나의 창을 좌우로 나누어 사용합니다.

왼쪽에는 폴더 구조를 트리 형태로 보여주는 QTreeView를 배치합니다.

오른쪽에는 선택된 폴더 안의 파일과 폴더를 리스트 형태로 보여주는 QListView를 배치합니다.

중요한 점은 왼쪽과 오른쪽 View가 서로 다른 데이터를 가지는 것이 아니라는 점입니다.

두 View는 모두 하나의 QFileSystemModel을 공유합니다.

 

1.2 Model/View 관계

이번 예제의 Model/View 관계는 다음과 같습니다.

실제 파일/폴더 데이터
    ↓
QFileSystemModel
    ↓
QTreeView   QListView
구성 요소 역할
QFileSystemModel 파일과 폴더 정보를 Model 형태로 제공합니다.
QTreeView Model의 데이터를 트리 구조로 보여주는 View입니다.
QListView Model의 데이터를 리스트 구조로 보여주는 View입니다.
QSplitter분할 위젯 두 View를 좌우로 나누어 배치하는 위젯입니다.

중요한 점
QTreeView와 QListView는 데이터를 직접 저장하는 것이 아닙니다.

 

둘 다 QFileSystemModel이 제공하는 파일/폴더 정보를 화면에 보여주는 역할만 합니다.

 

2. 프로젝트 구조 만들기

→ main.py와 widget.py 두 파일로 예제를 구성합니다.

더보기

2.1 프로젝트 폴더 구성

먼저 다음과 같이 프로젝트 폴더를 만듭니다.

2View1Model_Demo/
 ├─ main.py        # 실행 진입점
 └─ widget.py      # QFileSystemModel과 두 개의 View를 만드는 위젯 코드

 

파일은 총 2개입니다.

파일 역할
main.py QApplication을 만들고 Widget을 실행하는 진입점입니다.
widget.py QFileSystemModel, QTreeView, QListView를 만들고 연결하는 메인 위젯 코드입니다.
main.py
0.00MB
widget.py
0.00MB

 

 

2.2 실행 흐름

프로그램 실행 흐름은 다음과 같습니다.

main.py 실행
    ↓
QApplication 생성
    ↓
Widget 생성
    ↓
QFileSystemModel 생성
    ↓
QTreeView와 QListView에 같은 Model 연결
    ↓
창 표시

참고
이번 예제의 핵심 코드는 widget.py에 있습니다.

 

main.py는 프로그램을 실행하기 위한 기본 진입점 역할만 합니다.

 

3. Widget

→ 하나의 Widget 내부에, QFileSystemModel 하나와, 두 개의 View 를 만들고, 연결합니다.

더보기

3.1 Widget 구조 

구조는 Model 생성, View 생성, 시그널 연결이 모두 Widget 안에 들어 있습니다.

Widget
 ├── QFileSystemModel 생성
 ├── QTreeView 생성
 ├── QListView 생성
 ├── View-Model 시그널 연결
 └── View 화면 배치

 

3.2 widget.py 전체 코드

먼저 widget.py 파일에 다음 코드를 작성합니다.

# widget.py

from PySide6.QtCore import QDir, QTimer, QModelIndex, Qt
from PySide6.QtWidgets import (
    QWidget,
    QSplitter,
    QFileSystemModel,
    QTreeView,
    QListView,
    QVBoxLayout,
)


class Widget(QWidget):
    """
    하나의 QFileSystemModel을
    - QTreeView
    - QListView
    두 개의 View가 함께 공유하는 예제 위젯입니다.
    """

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

        # [1] 기본 창 설정
        self.setWindowTitle("Model/View 데모 - QTreeView + QListView")
        self.resize(800, 400)

        # [2] 공통 모델 생성
        self.model = QFileSystemModel(self)

        # 현재 작업 디렉토리를 루트 경로로 사용
        root_path = QDir.currentPath()
        self.model.setRootPath(root_path)

        # [3] 스플리터 생성
        splitter = QSplitter(Qt.Horizontal, self)

        # [4] 왼쪽 TreeView 설정
        self.tree_view = QTreeView(splitter)
        self.tree_view.setModel(self.model)
        self.tree_view.setRootIndex(self.model.index(root_path))

        # [5] 오른쪽 ListView 설정
        self.list_view = QListView(splitter)
        self.list_view.setModel(self.model)
        self.list_view.setRootIndex(self.model.index(root_path))

        # [6] 트리에서 선택이 바뀌면 리스트 뷰의 루트도 변경
        self.tree_view.selectionModel().currentChanged.connect(
            self.on_tree_current_changed
        )

        # [7] 전체 레이아웃 설정
        layout = QVBoxLayout(self)
        layout.addWidget(splitter)

        # [8] 프로그램 시작 직후 초기 선택 해제
        QTimer.singleShot(0, self._clear_initial_selection)

    def on_tree_current_changed(self, current, previous):
        """
        트리에서 선택이 바뀔 때마다 호출됩니다.

        current  : 현재 선택된 QModelIndex
        previous : 이전에 선택되어 있던 QModelIndex
        """
        if self.model.isDir(current):
            self.list_view.setRootIndex(current)
        else:
            parent_index = current.parent()
            self.list_view.setRootIndex(parent_index)

    def _clear_initial_selection(self):
        """
        프로그램 시작 시 TreeView의 첫 항목이 자동 선택되어 보이는 것을 해제합니다.
        """
        selection_model = self.tree_view.selectionModel()

        if selection_model is not None:
            selection_model.clearSelection()

        self.tree_view.setCurrentIndex(QModelIndex())

이 코드는 하나의 QFileSystemModel을 만들고, 그 Model을 QTreeView와 QListView에 함께 연결합니다.

 

4. Model/View 구조 분석

→ TreeView에서 선택한 항목에 따라 ListView의 표시 기준을 바꿉니다.

더보기

4.1 모델 생성: QFileSystemModel

먼저 파일 시스템 데이터를 표현할 Model을 만듭니다.

self.model = QFileSystemModel(self)

root_path = QDir.currentPath()
self.model.setRootPath(root_path)

QFileSystemModel은 디스크의 파일과 폴더 구조를 Model 형태로 제공하는 Qt의 준비된 Model 클래스입니다.

setRootPath()루트 경로 설정 메서드는 이 Model이 어느 경로부터 파일과 폴더 정보를 읽을지 지정합니다.

코드 의미
QFileSystemModel(self) 파일/폴더 정보를 제공하는 Model 객체를 만듭니다.
QDir.currentPath() 현재 작업 디렉토리 경로를 가져옵니다.
setRootPath(root_path) Model이 읽을 기준 경로를 설정합니다.
실제 파일/폴더
    ↓
QFileSystemModel
    ↓
파일/폴더 정보를 Model 형태로 제공

Model/View 개념 포인트
QFileSystemModel은 파일/폴더 정보를 View가 사용할 수 있는 Model 형태로 제공합니다.

 

나중에 여러 View가 이 Model을 공유하면서 같은 데이터를 서로 다른 방식으로 보여줄 수 있습니다.

 

4.2 왼쪽 View: QTreeView 설정

왼쪽에는 QTreeView를 배치합니다.

QTreeView는 Model의 데이터를 트리 구조로 보여주는 View입니다.

self.tree_view = QTreeView(splitter)
self.tree_view.setModel(self.model)
self.tree_view.setRootIndex(self.model.index(root_path))
코드 설명
QTreeView(splitter) 스플리터 안에 트리 형태의 View를 만듭니다.
setModel(self.model) 앞에서 만든 QFileSystemModel을 이 View에 연결합니다.
setRootIndex(...) 트리에서 어느 폴더부터 보여줄지 정합니다.
QTreeView
    ↓
setModel(self.model)
    ↓
파일 시스템 Model의 데이터를 트리 형태로 표시

즉, TreeView가 데이터를 가지는 것이 아닙니다.

TreeView는 QFileSystemModel이 제공하는 데이터를 트리 모양으로 보여줍니다.

 

4.3 오른쪽 View: QListView 설정

오른쪽에는 QListView를 배치합니다.

QListView는 Model의 데이터를 리스트 형태로 보여주는 View입니다.

self.list_view = QListView(splitter)
self.list_view.setModel(self.model)
self.list_view.setRootIndex(self.model.index(root_path))
코드 설명
QListView(splitter) 스플리터 안에 리스트 형태의 View를 만듭니다.
setModel(self.model) QTreeView와 같은 QFileSystemModel을 연결합니다.
setRootIndex(...) 리스트에서 어느 폴더 내용을 보여줄지 정합니다.
QListView
    ↓
setModel(self.model)
    ↓
파일 시스템 Model의 데이터를 리스트 형태로 표시

핵심
self.model 객체는 단 하나입니다.

 

QTreeView와 QListView는 둘 다 setModel(self.model)로 같은 Model을 연결합니다.

 

4.4 한개의 Model을 두 View가 공유하는 구조

이번 예제에서 가장 중요한 부분은 두 View가 같은 Model을 공유한다는 점입니다.

self.model = QFileSystemModel(self)

self.tree_view.setModel(self.model)
self.list_view.setModel(self.model)

이 구조를 그림으로 보면 다음과 같습니다.

파일/폴더 데이터
    ↓
QFileSystemModel
    ├── QTreeView  : 트리 형태로 표시
    └── QListView  : 리스트 형태로 표시

파일/폴더 정보는 QFileSystemModel 하나를 통해 제공됩니다.

QTreeView와 QListView는 그 데이터를 서로 다른 화면 형태로 보여줄 뿐입니다.

 

5. View 로직 분석 

→ TreeView에서 선택한 항목에 따라 ListView의 표시 기준을 바꿉니다.

더보기

5.1 currentChanged 시그널 연결

이번 예제에서는 왼쪽 트리에서 선택한 폴더에 따라 오른쪽 리스트의 내용이 바뀌도록 만듭니다.

이를 위해 TreeView의 선택 상태를 관리하는 selectionModel()을 사용합니다.

self.tree_view.selectionModel().currentChanged.connect(
    self.on_tree_current_changed
)
코드 설명
selectionModel() View에서 어떤 항목이 선택되었는지 관리하는 객체를 가져옵니다.
currentChanged현재 항목 변경 시그널 현재 선택된 항목이 바뀔 때 발생하는 시그널입니다.
connect(...) 시그널이 발생했을 때 실행할 함수를 연결합니다.
트리에서 항목 클릭
    ↓
currentChanged 시그널 발생
    ↓
on_tree_current_changed() 실행
    ↓
리스트 뷰의 표시 기준 변경

즉, TreeView에서 선택이 바뀌면 오른쪽 ListView도 그 선택에 맞춰 바뀌게 됩니다.

 

5.2 슬롯 함수: on_tree_current_changed()

트리에서 선택이 바뀌면 다음 함수가 실행됩니다.

def on_tree_current_changed(self, current, previous):
    if self.model.isDir(current):
        self.list_view.setRootIndex(current)
    else:
        parent_index = current.parent()
        self.list_view.setRootIndex(parent_index)

current는 새로 선택된 항목의 QModelIndex모델 인덱스입니다.

previous는 이전에 선택되어 있던 QModelIndex입니다.

코드 의미
self.model.isDir(current) 현재 선택된 항목이 폴더인지 확인합니다.
setRootIndex(current) 선택한 폴더를 QListView의 표시 기준으로 설정합니다.
current.parent() 파일을 선택했을 때 그 파일이 들어 있는 부모 폴더를 가져옵니다.
트리에서 폴더 클릭
    ↓
self.model.isDir(current) == True
    ↓
리스트 뷰가 그 폴더 안의 내용을 표시


트리에서 파일 클릭
    ↓
self.model.isDir(current) == False
    ↓
리스트 뷰가 그 파일의 부모 폴더 내용을 표시

파일은 그 자체가 폴더처럼 내부 내용을 가질 수 없습니다.

그래서 파일을 클릭한 경우에는 해당 파일이 들어 있는 부모 폴더를 리스트의 기준 위치로 사용합니다.

중요한 점
리스트 뷰의 데이터가 새로 만들어지는 것이 아닙니다.

 

같은 QFileSystemModel을 사용하되, QListView가 보여줄 시작 위치인 Root Index루트 인덱스만 바뀌는 것입니다.

 

5.3 초기 선택 해제 코드

프로그램을 실행하면 TreeView의 첫 항목이 자동으로 선택되어 보일 수 있습니다.

처음 실행 상태를 깔끔하게 보여주고 싶다면 다음 코드를 사용할 수 있습니다.

QTimer.singleShot(0, self._clear_initial_selection)

def _clear_initial_selection(self):
    selection_model = self.tree_view.selectionModel()

    if selection_model is not None:
        selection_model.clearSelection()

    self.tree_view.setCurrentIndex(QModelIndex())

QTimer.singleShot(0, ...)은 화면 구성이 끝난 직후 한 번만 함수를 실행하도록 예약합니다.

그 안에서 clearSelection()으로 선택 상태를 지우고, setCurrentIndex(QModelIndex())로 현재 선택 인덱스도 비웁니다.

참고
이 코드는 Model/View 구조를 이해하는 데 필수는 아닙니다.

 

처음 실행했을 때 선택 표시를 깔끔하게 없애기 위한 UI 보정 코드입니다.

 

6. main 구현

→ QApplication을 만들고 Widget을 화면에 표시합니다.

더보기

6.1 main.py 전체 코드

이제 main.py 파일을 작성합니다.

main.py는 QApplication을 만들고, 앞에서 작성한 Widget을 화면에 표시하는 역할을 합니다.

# main.py

import sys
from PySide6.QtWidgets import QApplication

from widget import Widget


def main():
    # [1] QApplication 생성
    app = QApplication(sys.argv)

    # [2] 메인 위젯 생성
    window = Widget()
    window.show()

    # [3] 이벤트 루프 실행
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
코드 역할
QApplication(sys.argv) Qt GUI 프로그램 전체 실행을 관리하는 객체를 만듭니다.
Widget() QFileSystemModel, QTreeView, QListView가 들어 있는 창을 만듭니다.
window.show() 창을 화면에 표시합니다.
app.exec() Qt 이벤트 루프를 시작합니다.

 

6.2 실행 결과 구조

프로그램을 실행하면 다음과 같은 형태의 창이 나타납니다.

왼쪽 트리에서 폴더를 선택하면 오른쪽 리스트는 선택된 폴더의 내용을 보여줍니다.

하지만 데이터는 여전히 하나의 QFileSystemModel에서 가져옵니다.

이번 단계의 핵심
QTreeView와 QListView는 같은 QFileSystemModel을 사용합니다.

 

다른 것은 데이터를 보여주는 화면 방식과 Root Index입니다.

 

7. Model/View 방식 비교

→ 기존 스크롤 UI 방식과 이번 Model/View 방식을 비교합니다.

더보기

7.1 이전 스크롤 UI 방식과 비교

이전 스크롤 UI 예제에서는 QLabel, QListWidget, QTextEdit 같은 View 안에 데이터를 직접 넣는 방식이 많았습니다.

이번 예제에서는 데이터를 QFileSystemModel을 통해 제공하고, View는 그 데이터를 보여주기만 합니다.

구분 기존 방식 4.2 Model/View 방식
데이터 위치 View 내부 또는 위젯 내부 QFileSystemModel을 통해 제공
View 역할 데이터 저장과 화면 표시를 함께 담당 Model 데이터를 화면에 표시
데이터 공유 다른 View와 공유하기 어려움 여러 View가 같은 Model을 공유 가능
표현 방식 각 위젯의 내부 구조에 의존 같은 Model을 트리, 리스트 등으로 표현 가능
확장성 View가 늘어나면 데이터 처리도 중복될 수 있음 View만 추가하고 같은 Model을 연결할 수 있음
기존 방식

QScrollArea  ← QLabel 안에 데이터
QListWidget  ← QListWidget 안에 데이터
QTextEdit    ← QTextEdit 안에 데이터

View마다 데이터가 따로 있음
4.2 Model/View 방식

QTreeView ←─┐
QListView ←── QFileSystemModel ←── 실제 파일/폴더 데이터

Model 하나를 여러 View가 함께 사용

 

7.2 Root Index 관점에서 보기

이번 예제에서는 QTreeView와 QListView가 같은 Model을 사용하지만, 보여주는 범위는 다를 수 있습니다.

그 차이를 만드는 것이 setRootIndex()루트 인덱스 설정 메서드입니다.

self.tree_view.setRootIndex(self.model.index(root_path))
self.list_view.setRootIndex(self.model.index(root_path))

처음에는 두 View 모두 같은 root_path를 기준으로 데이터를 보여줍니다.

하지만 트리에서 다른 폴더를 선택하면 QListView의 Root Index가 바뀝니다.

같은 Model
    ↓
QTreeView root index  : 현재 작업 폴더
QListView root index  : 트리에서 선택한 폴더

즉, 데이터는 같지만 View마다 보여주는 시작 위치를 다르게 설정할 수 있습니다.

 

7.3 같은 Model, 다른 View, 다른 표현

이번 예제의 핵심 문장은 다음과 같습니다.

같은 Model
    ↓
QTreeView  : 트리 형태
QListView  : 리스트 형태

데이터는 하나
표현 방식은 두 개

이 구조를 이해하면 나중에 QTableView를 추가해도 어렵지 않습니다.

새 View를 만들고 같은 Model을 연결하면 되기 때문입니다.

기억할 문장
Model은 하나이고, View는 여러 개가 될 수 있습니다.

 

8. 정리

→ 하나의 Model을 여러 View가 공유하는 흐름을 마지막으로 정리합니다.

더보기

이번 단계에서는 하나의 QFileSystemModel을 QTreeView와 QListView가 함께 사용하는 구조를 살펴보았습니다.

핵심은 Model은 하나이고, View는 여러 개가 될 수 있다는 점입니다.

실제 파일/폴더 데이터
    ↓
QFileSystemModel
    ↓
QTreeView   QListView
    ↓          ↓
트리 표현    리스트 표현

이번 단계의 핵심 개념을 다시 정리하면 다음과 같습니다.

개념 정리
QFileSystemModel 파일과 폴더 정보를 Model 형태로 제공합니다.
QTreeView Model의 데이터를 트리 형태로 보여주는 View입니다.
QListView Model의 데이터를 리스트 형태로 보여주는 View입니다.
setModel() View에 Model을 연결하는 메서드입니다.
setRootIndex() View가 Model의 어느 위치부터 보여줄지 정합니다.
currentChanged View에서 현재 선택 항목이 바뀔 때 발생하는 시그널입니다.

기억할 문장
데이터는 Model에 있고, View는 그 데이터를 각자의 방식으로 보여줍니다.

 

 

참고. 공식 문서로 확인하기

→ 공식 문서에서 이번 예제와 연결되는 기준을 확인합니다.

더보기

1. 공식 문서 참고 표

이번 예제에서 사용한 QFileSystemModel, QTreeView, QListView, QTableView는 Qt for Python 공식 문서에서 확인할 수 있습니다.

공식 문서는 실제 클래스의 역할과 사용 가능한 메서드를 확인할 때 가장 정확한 기준이 됩니다.

구분 공식 문서 확인할 내용
QFileSystemModel PySide6.QtWidgets.QFileSystemModel 로컬 파일 시스템을 Model로 제공하는 클래스와 index(), isDir(), setRootPath() 같은 메서드를 확인할 수 있습니다.
QTreeView PySide6.QtWidgets.QTreeView Model 데이터를 트리 형태로 보여주는 View 클래스임을 확인할 수 있습니다.
QListView PySide6.QtWidgets.QListView Model 데이터를 리스트 형태로 보여주는 View 클래스임을 확인할 수 있습니다.
QTableView PySide6.QtWidgets.QTableView Model 데이터를 표 형태로 보여주는 View 클래스임을 확인할 수 있습니다.
Model/View
Programming
Qt for Python - Model/View Programming Qt의 Model/View 구조가 데이터와 표현 방식을 분리하는 구조임을 확인할 수 있습니다.

 

2. 이번 예제와 공식 문서 연결

이번 예제 코드 또는 개념 공식 문서와 연결되는 의미
QFileSystemModel(self) 파일 시스템 데이터를 Model로 다루기 위한 객체를 생성합니다.
self.model.setRootPath(root_path) Model이 사용할 파일 시스템 기준 경로를 설정합니다.
self.tree_view.setModel(self.model) QTreeView가 QFileSystemModel의 데이터를 트리 형태로 표시하도록 연결합니다.
self.list_view.setModel(self.model) QListView가 같은 QFileSystemModel의 데이터를 리스트 형태로 표시하도록 연결합니다.
setRootIndex(current) View가 Model의 어느 위치부터 보여줄지 지정합니다.

 

3. 정리 흐름도

공식 문서 기준으로 정리하면 다음과 같습니다.

로컬 파일 시스템
    ↓
QFileSystemModel
    ↓
QTreeView / QListView / QTableView
    ↓
같은 데이터를 서로 다른 화면 형태로 표시

즉, 이번 예제는 Model/View 구조에서 하나의 Model을 여러 View가 공유하는 가장 기본적인 흐름을 보여줍니다.