본문 바로가기
목차
Python/PySide PyQt

[PySide6] QTreeView 와 QStandardItemModel, QStandardItem

by ds31x 2025. 6. 2.
728x90
반응형

0. Pre-requisites:

0-0. Model-View Architecture란?

  • QTreeView는 Qt의 Model-View Architecture 를 따름.
  • 이는 데이터(Model)와 그 표현(View)을 분리하는 디자인 패턴.

Qt에서 Model-View Architecture는 다음의 요소로 구성됨:

  • Model:
    • 데이터를 저장하고 관리: QStandardItemModel
    • QAbstractItemModel의 subclass임.
  • View:
    • 데이터를 사용자에게 시각적으로 표시: QTreeView
  • Delegate:
    • 개별 항목의 렌더링과 편집을 담당: QStyledItemDelegate

2025.04.14 - [CE] - [Programming] MVC, MVVM, and Qt's MV

 

[Programming] MVC, MVVM, and Qt's MV

MVC Architecture (or Pattern)란?MVC(Model-View-Controller)는 애플리케이션을 세 가지 주요 논리적 구성 요소인모델(Model),뷰(View),컨트롤러(Controller)로분리하는 아키텍처 패턴 . 비즈니스 로직과 UI를 분리 (or l

ds31x.tistory.com


0-1. QModelIndex 클래스.

  • QModelIndex는 모델 내의 특정 항목을 식별하는 Handle Object.
  • QTreeView 클래스를 이용하는 트리 구조에서 각 노드의 위치를 나타내는데 사용됨.

주요 메서드는 다음과 같음:

  • row(): 부모 내에서의 행 번호 반환
  • column(): 열 번호 반환
  • parent(): 부모 인덱스 반환
  • isValid(): 유효한 인덱스인지 확인
  • internalPointer(): 내부 포인터 반환 (커스텀 모델에서 사용)

0-2. QStandardItemModel 클래스.

QStandardItemModel은 Structured Data (표 형태의 데이터)를 다룰 수 있도록 설계된 범용 Model 클래스

  • 트리 구조, 테이블 구조, 리스트 구조 모두 표현 가능
  • 복잡한 모델을 직접 상속 구현하지 않고도 간편하게 Hierarchical Data Representation이 가능.
  • QStandardItem을 노드로 사용하여 아이템 기반(item-based) 모델링 가능

부모 클래스는 다음과 같음.

  • QAbstractItemModel, QObject

QAbstractItemModel을 상속하며, Model-View Architecture의 핵심 기능을 구현하고 있음.

내부적으로 QStandardItem 객체들을
2차원 배열처럼 관리함.

 

주요 메서드는 다음과 같음:

  • setItem(row, column, item): 특정 위치에 QStandardItem 설정.
  • item(row, colum): 해당 위치의 QStandardItem 반환.
  • 'appendRow(item | list)`: 하위 행 추가. 트리구조에선 자식 추가로 사용됨.
  • appendColumn(list): 열을 끝에 추가.
  • invisibleRootItem(): 내부적으로 최상위 root역할을 하는 invisible root item을 반환.
    • QTreeView 등에서 invisible root item 는 보이지 않으며,
    • invisible root item의 자식 item들이 최상위 노드인것처럼 보임.
    • model.appendRow(item)은 실제로 invisibleRootItem().appendRow(item)과 마찬가지임.
  • setHorizontalHeaderLabels(labels): 헤더라벨 설정 (수평)
  • setVerticalHeaderLabels(labels): 헤더라벨 설정 (수직)
  • clear(): 모든 데이터 제거
  • rowCount()/columnCount(): 행/열 개수 반환.인.

0-3. QStandardItem 클래스

  • QStandardItemModelQStandardItem을 사용하여 데이터를 구성.
  • QStandardItem은 트리 형태의 부모-자식 관계도 지원

주요 메서드는 다음과 같음:

  • setText(text): Item의 표시 텍스트 설정
  • text(): Item의 현재 텍스트 반환
  • appendRow(items): 하위 행 추가(=자식 Item추가)
  • child(row, column=0): 지정한 행과 열의 자식 Item을 반환
  • hasChildren(): True,False로 자식 Item 존재 여부 반환
  • setCheckable(checkable): True,False로 Item에 체크박스 를 표시할지 여부
  • setCheckState(state): Qt.CheckState를 통해 체크박스 상태 설정
    • Qt.Checked, Qt.Unchecked, Qt.PartiallyChecked 중 하나로 설정됨.
  • checkState(): Qt.CheckState를 반환하여 현재 체크박스 상태를 반환
  • setEditable(editable): True, False로 Item의 편집 여부 설정.
  • setDragEnabled(bool)/isDragEnabled(): 아이템의 드래그 가능 여부를 설정하거나 확인.
  • setDropEnabled(bool)/isDropEnabled(): 아이템이 드롭(다른 항목을 끌어다 놓기) 가능한지 설정하거나 확
  • setIcon(icon): QIcon 객체를 통해 icon 설정.

1. QTreeView 클래스

QTreeView아이템을 트리구조로 표시하는 대표적인 View Widget.

 

부모 클래스는 다음과 같음:

  • QAbstractItemView, QAbstractScrollArea, QWidget, QObject.

 

주요 메서드는 다음과 같음:


1-0. Model 관련

  • setModel(model): 모델을 뷰에 연결.
  • model(): 현재 연결된 모델을 반환.

1-1. Selection 과 Navigation 관련

  • currentIndex(): 현재 선택된 Index
  • setCurrentIndex(): 특정 Index 선택.
  • selectionModel(): selection 에 대한 모델 반환.
    • 반환되는 객체에 대한 자세한 건 아래에서 다룸: QItemSelectionModel 객체
  • setSelectionBehavior(behavior): 선택 동작 설정
    • SelectItems: item 단위로 선택됨.
    • SelectRows: 행 단위로 선택됨.
    • SelectColumns: 열 단위로 선택됨.
  • setSelectionMode(mode): Item의 선택 모드 설정
    • QAbstractItemView의 다음의 모드를 argument로 넘겨주어 설정 (QTreeViewQAbstractItemView의 subclass 임)
      • NoSelection: 선택 불가. 마우스 클릭해도 선택 안 됨.
      • SingleSelection: 하나만 선택 가능. 새 항목을 클릭하면 이전 선택은 해제됨.
      • MultiSelection: 여러 항목 선택 가능(항목별 독립선택). Ctrl+클릭 으로 항목 추가 선택 가능.
      • ExtendedSelection: 범위 및 다중 선택 가능. Shift+클릭: 범위선택. Ctrl+클릭: 개별선택/해제.
      • ContiguousSelectoin: 인접한 항목만 다중선택 가능. Shift+클릭 만 허용(Cltrl+클릭 은 동작안함)

1-2. Expand/Collapse 제어

  • expand(index): 특정 인덱스 확장
  • collapse(index):특정 인덱스 접기
  • expandAll(): 모든 항목 확장
  • collapseAll(): 모든 항목 접기
  • setExpanded(index, expanded): 확장 상태 설정
  • isExpanded(index): 확장 상태 확인

1-3. Header 관련

  • header(): 헤더 뷰 반환
  • setHeaderHidden(hide): 헤더 숨기기

1-4. 시각적 설정

  • setAlternatingRowColors(enable): 교대로 행 색상 변경 활성화 여부
  • setRootIsDecorated(show): 루트 레벨 장식 표시 여부
  • setColumnWidth(col_idx, width): col_idx에 해당하는 열의 넓이 설정.
  • setIndentation(indent): 들여쓰기 픽셀 수
  • setSortingEnabled(enable): 정렬 기능 활성화
  • setAnimated(enable): 확장/접기 애니메이션

2. Simple Example:

간단한 트리를 보여주는 기본적인 QTreeView 에 대한 예제임.

 

결과 이미지임:

 

다음은 코드임:

import sys
from PySide6.QtWidgets import (
    QApplication, 
    QTreeView, 
    QVBoxLayout, QWidget, QMainWindow,
    )

from PySide6.QtGui import (
    QStandardItemModel, 
    QStandardItem,
)


# 메인 위젯 클래스 정의
class MW(QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):

        # 창 제목 및 크기 설정
        self.setWindowTitle("Deep Learning Algorithms")
        self.setGeometry(100, 100, 600, 500)
        self.set_main_wnd()
        self.show()

    def set_main_wnd(self):

        # 수직 레이아웃 생성
        layout = QVBoxLayout()

        # QTreeView 위젯 생성: 트리 형태로 데이터를 보여주는 뷰
        self.tree_view = QTreeView()
        self.tree_view.setAlternatingRowColors(True)  # 줄마다 배경색 교차
        self.tree_view.setAnimated(True)              # 트리 확장/축소 시 애니메이션 적용
        self.tree_view.setSortingEnabled(False)       # 정렬 기능 비활성화

        # 데이터 모델 생성: 계층적 데이터를 담는 모델
        self.model = QStandardItemModel()
        self.model.setHorizontalHeaderLabels(["분류", "설명"])  # 헤더 라벨 지정

        # 트리 데이터 구성 및 model에 추가.
        root,root_info = self.populate_tree()

        # 최상위 루트 아이템을 모델에 등록
        self.model.appendRow([root,root_info])

        # 모델과 뷰 연결
        self.tree_view.setModel(self.model)

        # 모든 항목 기본적으로 확장
        self.tree_view.expandAll()
        self.tree_view.setColumnWidth(0, 250)
        self.tree_view.setColumnWidth(1, 500)

        # 레이아웃에 트리 뷰 추가하고 위젯에 설정
        layout.addWidget(self.tree_view)

        # dummy container
        container = QWidget()
        container.setLayout(layout)

        self.setCentralWidget(container)

    # QStandardItem 생성 도우미 메서드
    def create_item(self, text, editable=False):
        item = QStandardItem(text)
        item.setEditable(editable)  # 편집 가능 여부 설정
        return item

    # root, root_info에 트리 구조를 구성하는 메서드
    def populate_tree(self):
        """딥러닝 알고리즘 분류 트리 생성"""

        # 1. 최상위 루트 노드 (사용자에겐 보이는 루트)
        root = self.create_item("딥러닝")
        root_info = self.create_item("Deep Learning Algorithms")

        self.model.appendRow([root, root_info])

        # 1-1. 지도 학습 ─────────
        supervised = self.create_item("Supervised Learning")
        supervised_info = self.create_item("Labeled data 기반 학습")

        # 1-1. 루트 노드에 지도학습 추가
        root.appendRow([supervised, supervised_info])

        # 지도 학습 하위 알고리즘 추가.

        # 1-1-1. FCN 
        supervised.appendRow([
            self.create_item("FCN"),
            self.create_item("Fully Connected Network, MLP"),
            ])
        # 1-1-2. CNN
        supervised.appendRow([
            self.create_item("CNN"),
            self.create_item("이미지 처리에 특화된 구조"),
            ])
        # 1-1-3. RNN 및 하위 구조
        rnn = self.create_item("순환 신경망 (RNN)")
        rnn_info = self.create_item("시계열 데이터 처리")
        # 1-1-3-1. LSTM
        rnn.appendRow([
            self.create_item("LSTM"),
            self.create_item("Long Short-Term Memory"),
            ])
        # 1-1-3-2. GRU
        rnn.appendRow([
            self.create_item("GRU"),
            self.create_item("Gated Recurrent Unit"),
            ])
        supervised.appendRow([rnn, rnn_info])

        # 1-1-4. Transformer 및 하위 구조
        transformer = self.create_item("트랜스포머 (Transformer)")
        transformer_info = self.create_item("Self-Attention 기반 시퀀스 처리")

        # 1-1-4-1. BERT
        transformer.appendRow([
            self.create_item("BERT"),
            self.create_item("Bidirectional Encoder Representations")
        ])
        # 1-1-4-2. GPT
        transformer.appendRow([
            self.create_item("GPT"),
            self.create_item("Generative Pretrained Transformer")
        ])
        # 1-1-4-3. ViT
        transformer.appendRow([
            self.create_item("ViT"),
            self.create_item("Vision Transformer for Images")
        ])
        supervised.appendRow([transformer, transformer_info])

        # 2. 비지도 학습 
        unsupervised = self.create_item("Unsupervised Learning")
        unsupervised_info = self.create_item("레이블 없는 데이터")
        root.appendRow([unsupervised, unsupervised_info])

        # 2-1. AutoEncoder
        unsupervised.appendRow([
            self.create_item("AutoEncoder"),
            self.create_item("특징 추출 및 차원 축소"),
            ])
        # 2-2. GAN
        unsupervised.appendRow([
            self.create_item("생성적 적대 신경망 (GAN)"),
            self.create_item("Generative Adversarial Network"),
            ])

        # 3. 강화학습 (최상위 분류로) 
        reinforcement = self.create_item("강화학습")
        reinforcement_info = self.create_item("보상 기반 학습")
        root.appendRow([reinforcement, reinforcement_info])

        # 3-1. 값 기반 방법
        value_based = self.create_item("값 기반 방법 (Value-based)")
        value_based_info = self.create_item("Q 값을 추정하여 정책 도출")
        reinforcement.appendRow([value_based, value_based_info])

        # 3-1-1. DQN
        value_based.appendRow([
            self.create_item("DQN"),
            self.create_item("Deep Q-Network"),
            ])

        # 3-2.정책 기반 방법
        policy_based = self.create_item("정책 기반 방법 (Policy-based)")
        policy_based_info = self.create_item("정책 함수 직접 학습")
        reinforcement.appendRow([policy_based, policy_based_info])
        # 3-2-1. REINFORCE
        policy_based.appendRow([
            self.create_item("REINFORCE"),
            self.create_item("기초 정책 경사법"),
            ])

        # 3-3. 액터-크리틱 방법
        actor_critic = self.create_item("액터-크리틱 방법 (Actor-Critic)")
        actor_critic_info = self.create_item("정책과 가치 동시 학습")
        reinforcement.appendRow([actor_critic, actor_critic_info])

        # 3-3-1. A2C
        actor_critic.appendRow([
            self.create_item("A2C"),
            self.create_item("Advantage Actor-Critic"),
            ])
        # 3-3-2. PPO
        actor_critic.appendRow([
            self.create_item("PPO"),
            self.create_item("Proximal Policy Optimization"),
            ])

        return root,root_info


# 프로그램 실행 진입점
if __name__ == '__main__':
    app = QApplication(sys.argv)  # QApplication 객체 생성
    wnd = MW()                    # 메인 위젯 생성
    sys.exit(app.exec())          # 이벤트 루프 실행 및 정상 종료 처리

3. 편집 가능한 QTreeView Example

다음 이미지가 screeshot임:

동적인 Tree 구조를 변경하는 데 필요한 다음의 기능을 구현하는 방법을 보여줌:

  • Drag and Drop: 마우스를 통한 Item객체의 이동을 하는 대표적 방식.
  • QItemSelectionModel: 현재 선택이 된 Item에 대한 데이터를 가진 Model을 추상화한 클래스.
import sys
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QTreeView, QVBoxLayout,
    QHBoxLayout, QPushButton, QInputDialog, QMessageBox
)
from PySide6.QtGui import QStandardItemModel, QStandardItem
from PySide6.QtCore import Qt
from datetime import datetime

# 가상 파일 관리자를 위한 메인 윈도우 클래스 정의
class MW(QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()          # 윈도우 타이틀 및 크기 설정
        self.set_main_window() # UI 요소 및 이벤트 핸들러 구성

    def init_ui(self):
        """메인 윈도우 설정"""
        self.setWindowTitle("Virtual File Manger")     # 윈도우 상단 제목
        self.setGeometry(100, 100, 800, 600)           # 위치(x, y), 크기(width, height)
        self.show()                                    # 윈도우 표시

    def set_main_window(self):
        """트리 뷰와 버튼 UI 구성"""

        # 중앙 위젯과 레이아웃 생성
        central_widget = QWidget()
        self.setCentralWidget(central_widget)

        main_layout = QVBoxLayout()    # 메인 수직 레이아웃
        button_layout = QHBoxLayout()  # 상단 버튼 가로 레이아웃

        # 버튼들 생성
        self.add_folder_button = QPushButton("폴더 추가")
        self.add_file_button = QPushButton("파일 추가")
        self.remove_button = QPushButton("삭제")
        self.rename_button = QPushButton("이름 변경")
        self.expand_button = QPushButton("모두 펼치기")
        self.collapse_button = QPushButton("모두 접기")

        # 버튼 클릭 시 동작할 슬롯 연결
        self.add_folder_button.clicked.connect(self.add_folder)
        self.add_file_button.clicked.connect(self.add_file)
        self.remove_button.clicked.connect(self.remove_item)
        self.rename_button.clicked.connect(self.rename_item)
        self.expand_button.clicked.connect(self.tree_view_expand_all)
        self.collapse_button.clicked.connect(self.tree_view_collapse_all)

        # 버튼들을 버튼 레이아웃에 추가
        button_layout.addWidget(self.add_folder_button)
        button_layout.addWidget(self.add_file_button)
        button_layout.addWidget(self.remove_button)
        button_layout.addWidget(self.rename_button)
        button_layout.addStretch()  # 오른쪽 정렬용 stretch
        button_layout.addWidget(self.expand_button)
        button_layout.addWidget(self.collapse_button)

        # QTreeView 생성 및 설정
        self.tree_view = QTreeView()
        self.tree_view.setAlternatingRowColors(True)               # 줄마다 색상 교차
        self.tree_view.setSelectionBehavior(QTreeView.SelectRows)  # 행 단위로 선택. 행단위로 처리.
        # self.tree_view.setSelectionBehavior(QTreeView.SelectItems)  #  item 단위로 선택. 하지만 TreeView에선 의미 없음.
        # QTreeView에서 트리 구조상의 드래그앤드롭 이동은 항상 0번 열의 아이템을 기준으로 처리.
        # QTreeView에서는 0번 열의 Item만이 자식이나 부모가 될 수 있음: 
        # 즉, QStandardItemModel은 각 열(column)을 독립적인 계층 구조로 보지 않음.
        # 그래서 드래그앤드롭 대상이 0번 열의 아이템이 아닌 경우,
        # 해당 아이템은 자식(appendRow)이나 부모가 될 수 없고, 트리 내의 위치를 옮길 수도 없음

        self.tree_view.setSelectionMode(QTreeView.ExtendedSelection) # 클릭 등의 선택 모드
        self.tree_view.setAnimated(True)                           # 확장/축소 애니메이션
        self.tree_view.setSortingEnabled(True)                     # 정렬 기능

        self.tree_view.setDragDropMode(QTreeView.InternalMove) # 내부 항목 드래그 지원

        # 트리 뷰에 연결할 모델 생성 및 헤더 설정
        self.model = QStandardItemModel()
        self.model.setHorizontalHeaderLabels(['이름', '타입', '크기', '수정일'])
        self.tree_view.setModel(self.model)

        # 컬럼 너비 지정
        self.tree_view.setColumnWidth(0, 250)  # 이름
        self.tree_view.setColumnWidth(1, 100)  # 타입
        self.tree_view.setColumnWidth(2, 100)  # 크기
        self.tree_view.setColumnWidth(3, 150)  # 수정일

        # 선택이 변경될 때 버튼 상태 업데이트
        self.tree_view.selectionModel().selectionChanged.connect(
            self.update_button_states
        )

        # 초기 샘플 데이터 삽입
        self.populate_sample_data()

        # 전체 레이아웃 정리
        main_layout.addLayout(button_layout)
        main_layout.addWidget(self.tree_view)
        central_widget.setLayout(main_layout)

        # 버튼 활성화 여부 초기화
        self.update_button_states()

    def make_item(self, text):
        """QStandardItem 객체를 생성하는 헬퍼 메서드"""
        return QStandardItem(text)

    def create_folder_item(self, name, type_name):
        """폴더용 4열 항목 리스트 생성 (이름, 타입, 크기, 수정일)"""
        name_item = self.make_item(name)

        name_item.setDropEnabled(True)  # Drop을 허용 (폴더처럼): 
        # 사실 StandardItem은 기본이 Drag와 Drop이 가능하므로 위 라인은 주석처리해도 됨.

        type_item = self.make_item(type_name)
        size_item = self.make_item("")  # 폴더는 크기 없음
        date_item = self.make_item(datetime.now().strftime("%Y-%m-%d %H:%M"))
        return [name_item, type_item, size_item, date_item]

    def create_file_item(self, name, type_name, size):
        """파일용 4열 항목 리스트 생성"""
        name_item = self.make_item(name)
        type_item = self.make_item(type_name)
        size_item = self.make_item(size)
        date_item = self.make_item(datetime.now().strftime("%Y-%m-%d %H:%M"))
        return [name_item, type_item, size_item, date_item]

    def get_selected_item(self):
        """현재 선택된 트리 항목의 첫 열 항목 반환"""
        index = self.tree_view.currentIndex()
        if index.isValid():
            return self.model.itemFromIndex(index.siblingAtColumn(0))
        return None

    def populate_sample_data(self):
        """샘플 파일/폴더 구조를 트리에 추가"""
        try:
            # Documents 폴더 생성 및 파일 추가
            docs = self.create_folder_item("Documents", "폴더")
            docs[0].appendRow(
                self.create_file_item("report.pdf", "PDF 파일", "2.5 MB")
            )
            docs[0].appendRow(
                self.create_file_item("presentation.pptx", "PPT 파일", "5.1 MB")
            )
            self.model.appendRow(docs)

            # Pictures 폴더 > Vacation 폴더 > 사진 파일들
            pics = self.create_folder_item("Pictures", "폴더")
            vacation = self.create_folder_item("Vacation", "폴더")
            vacation[0].appendRow(
                self.create_file_item("beach.jpg", "JPEG 이미지", "3.2 MB")
            )
            vacation[0].appendRow(
                self.create_file_item("sunset.jpg", "JPEG 이미지", "2.8 MB")
            )
            pics[0].appendRow(vacation)
            self.model.appendRow(pics)

            # Downloads 폴더 생성
            downloads = self.create_folder_item("Downloads", "폴더")
            downloads[0].appendRow(
                self.create_file_item("setup.exe", "실행 파일", "45.2 MB")
            )
            self.model.appendRow(downloads)
        except Exception as e:
            QMessageBox.critical(self, "오류", f"샘플 데이터를 불러오는 중 오류 발생: {e}")

    def add_folder(self):
        """새 폴더 추가 다이얼로그"""
        folder_name, ok = QInputDialog.getText(
                            self, "폴더 추가", 
                            "폴더 이름:", text="새 폴더",
                          )
        if ok and folder_name:
            try:
                folder_items = self.create_folder_item(
                                        folder_name, 
                                        "폴더",
                                    )
                parent = self.get_selected_item()
                if parent:
                    parent.appendRow(folder_items)
                    self.tree_view.expand(parent.index())
                else:
                    self.model.appendRow(folder_items)
            except Exception as e:
                QMessageBox.critical(self, "오류", f"폴더 추가 중 오류 발생: {e}")

    def add_file(self):
        """새 파일 추가 다이얼로그"""
        file_name, ok = QInputDialog.getText(
                            self, "파일 추가", 
                            "파일 이름:", text="새파일.txt",
                        )
        if ok and file_name:
            try:
                extension = file_name.split('.')[-1] if '.' in file_name else 'txt'
                file_items = self.create_file_item(
                                file_name, 
                                f"{extension.upper()} 파일", 
                                "1.0 KB",
                             )
                parent = self.get_selected_item()
                if parent:
                    parent.appendRow(file_items)
                    self.tree_view.expand(parent.index())
                else:
                    self.model.appendRow(file_items)
            except Exception as e:
                QMessageBox.critical(self, "오류", f"파일 추가 중 오류 발생: {e}")

    def remove_item(self):
        """선택된 항목 삭제"""
        item = self.get_selected_item()
        if item:
            reply = QMessageBox.question(
                self, "삭제 확인", f"'{item.text()}' 항목을 삭제하시겠습니까?",
                QMessageBox.Yes | QMessageBox.No
            )
            if reply == QMessageBox.Yes:
                # 부모가 없으면 루트 아이템으로 간주
                parent = item.parent() or self.model.invisibleRootItem()
                parent.removeRow(item.row())
        else:
            QMessageBox.information(self, "선택 없음", "삭제할 항목을 선택하세요.")

    def rename_item(self):
        """선택된 항목의 이름 변경"""
        item = self.get_selected_item()
        if item:
            old_name = item.text()
            new_name, ok = QInputDialog.getText(self, "이름 변경", "새 이름:", text=old_name)
            if ok and new_name:
                item.setText(new_name)
                # 수정일도 현재 시각으로 갱신
                date_item = self.model.item(item.row(), 3)
                if date_item:
                    date_item.setText(datetime.now().strftime("%Y-%m-%d %H:%M"))
        else:
            QMessageBox.information(self, "선택 없음", "이름을 변경할 항목을 선택하세요.")

    def tree_view_expand_all(self):
        """모든 항목을 펼침"""
        self.tree_view.expandAll()

    def tree_view_collapse_all(self):
        """모든 항목을 접음"""
        self.tree_view.collapseAll()

    def update_button_states(self):
        """선택 상태에 따라 버튼의 활성화 여부를 조정"""
        # has_selection = self.tree_view.currentIndex().isValid()
        has_selection = self.tree_view.selectionModel().hasSelection()
        self.remove_button.setEnabled(has_selection)
        self.rename_button.setEnabled(has_selection)

# 프로그램 진입점
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MW()  # 윈도우 클래스 인스턴스 생성
    sys.exit(app.exec())  # 이벤트 루프 실행

3-1. Drag and Drop 관련: 

  • setDragDropMode(mode: QAbstractItemView.DragDropMode): view에서 설정.
    • Qt의 QAbstractItemView 기반 View 클래스에서 Drag and Drop 동작을 제어.
    • 예: QTreeView, QListView, QTableView
    • QAbstractItemView.DragDropMode 는 다음과 같음:
      • NoDragDrop: 드래그앤드롭 불가 (기본값)
      • DragOnly: 드래그만 허용.
      • DropOnly: 드롭만 허용.
      • DragDrop: 드래그와 드롭 모두 허용.
      • InternalMove: 동일한 View 내에서 Item 이동만 허용.
  • Drag and Drop 관련 item에서 설정 :
    • QTreeView 객체에서 setDragDropMode()를 통해 해당 뷰에서 Drag and Drop을 어떻게 사용할지를 설정.
    • 단, 해당 뷰와 연결된 모델 객체가 가지고 있는 QStandardItem객체 (Item) 자체에서 설정된 Drag와 Drop의 설정을 따름.
      • 기본은 모두 True로 사용가능함.
      • QStandardItem 객체의 setDragEnabled(False) 메서드 호출시 드래그 불가임.
      • setDropEnabled(False)는 드롭이 불가.

3-2. Selection관련

  • has_selection = self.tree_view.currentIndex().isValid()
    • QTreeView 객체에서 키보드 포커스를 가진 index를 반환.
  • has_selection = self.tree_view.selectionModel().hasSelection()
    • selectionModel()QAbstractItemView가 사용하는 selection model을 반환.
    • 반환된 QItemSelectionModel 객체를 통해
      • 뷰에서 어떤 item이 선택되었는지,
      • 선택을 추가하거나 제거할지,
      • 선택을 변경할지 등등을 제어할 수 있음.
    • QItemSelectionModel의 주요 메서드는 다음과 같음:
      • hasSelection(): 하나 이상의 Item이 선택되었는지 여부 (True/False)
      • selectedIndexes(): 선택된 모든 QModelIndex 객체 리스트를 반환.
      • selectedRows(column=0): 지정된 열의 선택된 rows만 반환 (Tree 구조에서 유용).
      • currentIndex(): 현재 포커스된(Cursor가 있는) 인덱스.
      • isSelected(index): 해당 인덱스가 선택되어 있는지 여부.
      • clearSelection(): 현재 선택된 항목 전체를 해제.
      • select(index or selection, flags): 선택 항목 추가/제거/토글 등등.
      • clear(): 모든 선택과 포서크 상태 초기화.
    • 이는 결국, 실제로 선택된 선택영역(다중 선택 모드에서도 사용가능)이 있는지를 반환.

2025.06.04 - [Python/PySide PyQt] - [PySide] QItemSelectionModel 살펴보기 - 작성중

 

[PySide] QItemSelectionModel 살펴보기 - 작성중

QItemSelectionModel은 Qt Model-View Architecture에서 selection 을 관리하는 클래스.QItemSelectionModel 개요from PySide6.QtCore import QItemSelectionModel, QModelIndexfrom PySide6.QtWidgets import QListView, QTableView, QTreeView# 모든 View는

ds31x.tistory.com


4. 파일 탐색기 구현 예제

4-1. QFileSystemModel 클래스

QFileSystemModel은 Qt에서 파일 시스템을 탐색하고 보여주기 위한 Model 클래스임.

  • 주로 QTreeView, QListView, QTableView 등과 함께 사용됨.
  • 실시간으로 디렉토리 변경 사항을 감지하며,
  • 메모리 사용을 최소화하기 위해 필요한 정보만 로딩 (lazy loading)
Qt5 당시에는 QtGui 모듈에 있었음(PySide2, PyQt5).
Qt6 기준인 PySide5에서는 QtWidgets 모듈로 이동됨. 

4-1-0. 간단한 예제.

다음은 간단한 사용법을 보여주는 예제 코드임.

# PySide6에서 필요한 모듈들을 임포트
from PySide6.QtWidgets import QApplication, QTreeView   # GUI 애플리케이션과 트리뷰 위젯
from PySide6.QtGui import QIcon                         # 아이콘 관련
from PySide6.QtCore import QDir                         # 디렉토리 관련 유틸리티
from PySide6.QtWidgets import QFileSystemModel          # 파일시스템 모델 (원본 코드 그대로)
import sys                                              # 시스템 관련 기능 (명령행 인수, 종료 등)

# QApplication 객체 생성 - GUI 애플리케이션의 메인 컨트롤러
# sys.argv를 전달하여 명령행 인수를 처리할 수 있도록 함
app = QApplication(sys.argv)

# QTreeView 위젯 생성 - 계층적 데이터를 트리 형태로 표시하는 뷰
view = QTreeView()

# QFileSystemModel 객체 생성 - 실제 파일 시스템의 데이터를 관리하는 모델
# 파일과 폴더의 정보(이름, 크기, 수정일 등)를 제공
model = QFileSystemModel()

# 모델의 루트 경로를 사용자의 홈 디렉토리로 설정
# QDir.homePath()는 현재 사용자의 홈 폴더 경로를 반환
# (Windows: C:/Users/사용자명, Linux/Mac: /home/사용자명)
model.setRootPath(QDir.homePath())

# 트리뷰에 파일시스템 모델을 연결
# Model-View 패턴: 뷰가 모델의 데이터를 표시하도록 연결
view.setModel(model)

# 트리뷰에서 표시할 루트 인덱스를 홈 디렉토리로 설정
# model.index()는 지정된 경로에 해당하는 모델 인덱스를 반환
# 이 설정으로 홈 디렉토리가 트리의 최상위 노드가 됨
view.setRootIndex(model.index(QDir.homePath()))

# 창의 제목 표시줄에 표시될 제목 설정
view.setWindowTitle("QFileSystemModel 예제")

# 창의 크기를 가로 800픽셀, 세로 600픽셀로 설정
view.resize(800, 600)

# 창을 화면에 표시 (기본적으로 창은 숨겨진 상태로 생성됨)
view.show()

# 애플리케이션의 메인 이벤트 루프 시작
# 사용자가 창을 닫거나 애플리케이션을 종료할 때까지 실행됨
# app.exec()는 이벤트 루프를 시작하고 종료 코드를 반환
# sys.exit()는 반환된 종료 코드로 Python 프로그램을 종료
sys.exit(app.exec())
  • 굳이 QDir을 쓰지 않고도 os.path.expanduser('~') 라던지 str(pathlib.Path.home()) 을 사용해도 됨.
  • expanduser는 "~"(tilde) 를 실제 절대 경로로 바꿔주는 역할을 수행함: 인자로 넘겨진 문자열에서 "~"를 실제 절대 경로로 바꿈.

4-1-1. 주요 메서드

4-1-1-0. setRootPath(path:str)-> PySide5.QtCore.QModelIndex

  • 모델이 관리하는 파일시스템 루트를 설정.
  • 모델에 데이터를 로딩할 범위의 루트로 QTreeView 등에서 표시되는 root는 아님(뷰는 setRootIndex()를 이용.)
model.setRootPath("/Users")  # 또는 QDir.homePath()

4-1-1-1. rootPath()->str

  • 모델이 관리하는 파일시스템 루트를 반환.
print("Root Path:", model.rootPath())

4-1-1-2. index(path:str)->QModelIndex

  • 특정 경로에 해당하는 QModelIndex 객체를 반환.
index = model.index(QDir.homePath())
view.setRootIndex(index) # view에서 보여지는 root.

4-1-1-3. filePath(index: QModelIndex)->str

  • index 객체에 해당하는 전체 경로를 반환.
def on_click(index):
    print("clicked file path:", model.filePath(index))
view.clicked.connect(on_click)

4-1-1-4. fileName(index: QModelIndex)->str

  • index 객체에 해당하는 file/directory 이름을 반환.
def on_click(index):
    print("clicked file name:", model.fileName(index))
view.clicked.connect(on_click)

4-1-1-5. isDir(index: QModelIndex)->bool

  • index가 디렉토리인지 여부를 반환.
def on_click(index):
    print("디렉토리인가?", model.isDir(index))

view.clicked.connect(on_click)

4-1-1-6. fileInfo(index: QModelIndex)->QFileInfo

  • QFileInfo 객체를 반환함.
from PySide6.QtCore import QFileInfo

def on_click(index):
    info: QFileInfo = model.fileInfo(index)
    print("크기:", info.size(), " bytes")
    print("마지막 수정일:", info.lastModified().toString())

view.clicked.connect(on_click)

4-1-1-7. nameFilters()->List[str] / setNameFilter(enable:bool)->None

  • 확장자 필터 등을 설정할 때 이용 가능한 메서드
model.setNameFilters(["*.py", "*.txt"])
model.setNameFilterDisables(False)  # 필터가 적용되지 않는 항목은 비활성화

4-1-1-8. columnCount(parent:QModelIndex=QModelIndex())->int

4-1-1-9. data(index:QModelIndex, role:int=Qt.DisplayRole)->Any

  • columnCount(): 열의 개수를 반환.
  • data(index): index에 해당하는 셀의 데이터를 반환.
def on_click(index):
    for col in range(model.columnCount(index)):
        print(f"열 {col}: {model.data(model.index(
                                       index.row(), 
                                       col, 
                                       index.parent(),
                                     ))}")

view.clicked.connect(on_click)

4-1-1-10. setFilter(QDir.Filters)->None

  • ., .., 숨김 파일 등을 포함하거나 제외할 수 있음
model.setFilter(QDir.AllDirs | QDir.Files | QDir.NoDotAndDotDot)
더보기

QDir.Filters의 종류는 다음과 같음:

 

파일 타입 필터

  • QDir.Dirs - 디렉토리만
  • QDir.Files - 파일만
  • QDir.Drives - 드라이브 (Windows C:, D: 등)
  • QDir.AllEntries - 모든 항목 (Dirs | Files)

접근성 필터

  • QDir.Readable - 읽기 가능
  • QDir.Writable - 쓰기 가능
  • QDir.Executable - 실행 가능

숨김/특수 파일 필터

  • QDir.Hidden - 숨김 파일 포함
  • QDir.System - 시스템 파일 포함 (Windows)
  • QDir.NoDot - 현재 디렉토리(.) 제외
  • QDir.NoDotDot - 상위 디렉토리(..) 제외
  • QDir.NoDotAndDotDot - . 과 .. 모두 제외

정렬 관련 (setSorting 권장)

  • QDir.Name, QDir.Time, QDir.Size, QDir.Type : 이름순, 수정시간순, 크기순, 타입순
  • QDir.DirsFirst, QDir.DirsLast : 디렉토리 먼저, 디렉토리 나중에
  • QDir.Reversed, QDir.IgnoreCase: 역순정렬, 대소문자 무시
  • QDir.Unsorted, QDir.LocalAware: 정렬안함, 로케일 기반 정렬

4-1-2. 예제

주요 메서드의 활용을 점검할 수 있는 예제는 다음과 같음:

# PySide6의 GUI 구성 요소 중 QTreeView와 QApplication을 import
from PySide6.QtWidgets import QApplication, QTreeView

# QDir은 디렉토리 경로 관련 기능, QFileInfo는 파일 정보 추출을 위한 클래스
from PySide6.QtCore import QDir, QFileInfo

# 파일 시스템 모델 클래스를 import (모델-뷰 구조에서 모델 역할)
from PySide6.QtWidgets import QFileSystemModel

# 시스템 인자를 가져오기 위한 모듈 (e.g., QApplication에 전달할 인자용)
import sys

# QApplication 인스턴스 생성
# Qt 어플리케이션의 실행 환경을 구성하는 객체로, 이벤트 루프와 GUI 자원 관리 등의 역할
app = QApplication(sys.argv)

# QTreeView 인스턴스 생성
# 트리 형태로 디렉토리 및 파일 구조를 보여주는 뷰 컴포넌트
tree = QTreeView()

# QFileSystemModel 인스턴스 생성
# 파일 시스템의 디렉토리 및 파일 정보를 제공하는 모델
model = QFileSystemModel()

# 모델의 루트 경로를 시스템 사용자 홈 디렉토리로 설정
# 해당 경로를 기준으로 하위 디렉토리/파일을 로딩함
model.setRootPath(QDir.homePath())

# 모델에 표시할 항목을 필터링: 디렉토리, 파일 포함 / .(현재 디렉토리), ..(상위 디렉토리)는 제외
model.setFilter(QDir.AllDirs | QDir.Files | QDir.NoDotAndDotDot)

# 생성한 파일 시스템 모델을 QTreeView에 연결
tree.setModel(model)

# QTreeView의 루트 인덱스를 사용자의 홈 디렉토리로 설정
# 이것은 사용자가 처음 볼 수 있는 디렉토리의 위치를 지정하는 것
tree.setRootIndex(model.index(QDir.homePath()))

# 항목이 클릭될 때 호출될 콜백 함수 정의
def on_click(index):
    # 클릭된 항목의 파일 또는 디렉토리 이름 출력
    print("파일 이름:", model.fileName(index))

    # 클릭된 항목의 전체 경로 출력
    print("전체 경로:", model.filePath(index))

    # 해당 항목이 디렉토리인지 여부 출력 (True / False)
    print("디렉토리 여부:", model.isDir(index))

    # QFileInfo 객체를 사용해 해당 항목의 상세 정보 추출
    info: QFileInfo = model.fileInfo(index)

    # 항목의 파일 크기 출력 (바이트 단위)
    print("크기:", info.size())

    # 마지막으로 수정된 날짜와 시간을 문자열로 변환하여 출력
    print("수정일:", info.lastModified().toString())

    # 클릭한 항목의 각 열(이름, 크기, 타입, 수정일 등)에 대해 데이터 출력
    for col in range(model.columnCount(index)):
        # index.row(): 클릭된 행 번호
        # col: 열 번호
        # index.parent(): 부모 인덱스를 기준으로 자식 인덱스를 얻기 위해 필요
        print(f"열 {col}: {model.data(model.index(index.row(), col, index.parent()))}")

# QTreeView에서 항목을 클릭했을 때 on_click 함수가 호출되도록 시그널 연결
tree.clicked.connect(on_click)

# QTreeView 윈도우의 제목 설정
tree.setWindowTitle("QFileSystemModel 전체 예제")

# 윈도우 크기 설정
tree.resize(800, 600)

# 트리 뷰 표시
tree.show()

# 이벤트 루프 실행 (프로그램이 종료될 때까지 GUI 응답을 유지)
sys.exit(app.exec())

4-2. 예제

다음은 결과 이미지임:


다음은 코드임:

# 시스템 관련 표준 모듈
import sys
import os

# PySide6에서 필요한 GUI 관련 클래스들 가져오기
from PySide6.QtWidgets import (
    QApplication,         # Qt 애플리케이션 객체
    QMainWindow,          # 메인 윈도우 프레임
    QTreeView,            # 트리 형태의 뷰 (파일 탐색용)
    QFileSystemModel,     # 파일 시스템을 모델로 사용 (MVC의 M)
    QLabel,               # 텍스트 표시용 위젯
    QPushButton,          # 클릭 가능한 버튼
    QMessageBox,          # 팝업 메시지 박스 (경고/정보)
    QWidget,              # 일반적인 위젯 (컨테이너용)
    QVBoxLayout,          # 수직 방향 레이아웃 관리자
    QHBoxLayout,          # 수평 방향 레이아웃 관리자
    QInputDialog,         # 입력 다이알로그
    QStatusBar,           # 하단 상태 표시줄
)

# 파일 경로 및 URL 관련 클래스
from PySide6.QtCore import (
    QDir, 
    QUrl, 
    Qt,
)    

# 폰트, URL 열기 기능 포함
from PySide6.QtGui import (
    QFont, 
    QDesktopServices,
)

# QMainWindow 기반의 파일 탐색기 메인 클래스 정의
class FileExplorerMainWindow(QMainWindow):
    def __init__(self):
        super().__init__()  # 부모 생성자 호출
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("파일 탐색기 (QMainWindow 기반)")  # 윈도우 제목 설정
        self.setGeometry(100, 100, 1000, 700)  # 윈도우 위치(x, y) 및 크기(width, height)
        self.set_main_wnd()
        self.show()

    def set_main_wnd(self):
        # 중앙 위젯과 레이아웃 설정
        central_widget = QWidget()                # 중앙 콘텐츠용 위젯 생성
        self.setCentralWidget(central_widget)     # QMainWindow의 중앙 위젯으로 설정

        # -----------------------
        # central_widget의 layout manager.
        main_layout = QVBoxLayout(central_widget) # 수직 박스 레이아웃을 중앙 위젯에 설정

        # -----------------------
        # 파일 시스템 모델 생성 및 설정
        self.model = QFileSystemModel()                # 실제 파일 시스템을 반영하는 모델 생성
        self.model.setRootPath(QDir.rootPath())        # 모델의 루트 경로를 파일 시스템 전체로 설정
        self.model.setFilter(
            QDir.AllDirs | QDir.NoDotAndDotDot | QDir.Files
        )  # '.' 및 '..' 제외, 디렉토리 + 파일 표시
        self.model.setNameFilters(
            ["*.txt", "*.py", "*.md", "*.png", "*.jpg",]
        )  # 필터된 확장자만 표시
        self.model.setNameFilterDisables(False)        # 필터링을 활성화하여 필터된 파일만 보여줌

        # -----------------------
        # QTreeView 생성 및 설정
        self.tree = QTreeView()
        self.tree.setModel(self.model)    # 위에서 만든 파일 시스템 모델과 연결
        self.tree.setRootIndex(
            self.model.index(QDir.homePath())
        ) # 홈 디렉토리를 기본 표시 위치로 설정

        self.tree.setSortingEnabled(True)  # 정렬 허용
        self.tree.setAnimated(True)        # 트리 확장 시 애니메이션 적용
        self.tree.setColumnWidth(0, 300)   # 첫 번째 열(파일/폴더 이름)의 너비 설정

        # -----------------------
        # Signals and Slots

        # 더블 클릭 시 파일 열기 함수 연결
        self.tree.doubleClicked.connect(
            self.open_file_on_double_click
        )

        # 선택 변경 시 하단 라벨 갱신 함수 연결
        # selectionChanged 시그널은 2개의 QItemSelection객체를 인자로 넘김:
        # - selected : 새롭게 선택된 항목들의 집합.
        # - deselected : 이전 선택에서 해제된 항목들의 집합
        self.tree.selectionModel().selectionChanged.connect(
            self.update_path_label
        )

        # 현재 선택된 경로를 표시하는 라벨 생성
        self.path_label = QLabel("선택된 파일/폴더 경로:")
        self.path_label.setFont(QFont("Arial", 11))  # 라벨 폰트 설정

        # 버튼 레이아웃 및 버튼 생성
        button_layout = QHBoxLayout()

        self.btn_add_folder = QPushButton("새 폴더 만들기")
        self.btn_add_folder.clicked.connect(self.create_new_folder)  # 클릭 시 새 폴더 생성 함수 실행
        button_layout.addWidget(self.btn_add_folder)

        self.btn_add_file   = QPushButton("빈 파일 만들기")
        self.btn_add_file.clicked.connect(self.create_new_file)      # 클릭 시 빈 파일 생성 함수 실행
        button_layout.addWidget(self.btn_add_file)

        self.btn_rename   = QPushButton("이름 바꾸기")
        self.btn_rename.clicked.connect(self.rename_item)      # 클릭 시 이름 변경 함수 실행
        button_layout.addWidget(self.btn_rename)

        self.btn_delete   = QPushButton("삭제")
        self.btn_delete.clicked.connect(self.delete_item)      # 클릭 시 삭제 함수 실행
        button_layout.addWidget(self.btn_delete)

        # 메인 레이아웃에 트리 뷰, 라벨, 버튼 레이아웃 추가
        main_layout.addWidget(self.tree)
        main_layout.addWidget(self.path_label)
        main_layout.addLayout(button_layout)

        # 상태 바 설정
        # self.status_bar = QStatusBar()
        # self.setStatusBar(self.status_bar)  # QMainWindow 하단에 상태바 부착
        self.status_bar = self.statusBar()
        self.status_bar.showMessage("파일 탐색기 실행 중")  # 초기 메시지

    def get_selected_path(self):
        """
        현재 트리뷰에서 선택된 항목의 전체 경로 반환
        """
        index = self.tree.currentIndex()  # 현재 선택된 QModelIndex 객체
        if not index.isValid():
            return None
        return self.model.filePath(index)

    def get_selected_directory(self):
        """
        현재 트리뷰에서 선택된 항목의 디렉터리 경로를 반환
        - 폴더 선택 시: 해당 폴더 경로 반환
        - 파일 선택 시: 해당 파일이 포함된 상위 폴더 경로 반환
        """
        path = self.get_selected_path()
        if path is None:
            return None
        return path if os.path.isdir(path) else os.path.dirname(path)

    def create_new_folder(self):
        """
        현재 선택된 폴더 안에 새 폴더를 생성하는 함수
        - 중복된 이름이 있을 경우 NewFolder_1, NewFolder_2 등의 형식으로 생성
        """
        base_path = self.get_selected_directory()
        if not base_path:
            QMessageBox.warning(self, "경고", "디렉터리를 먼저 선택하세요.")
            return

        name, ok = QInputDialog.getText(
                        self, "새 폴더 이름", 
                        "폴더 이름을 입력하세요:", 
                        text="NewFolder",
                    )
        if not ok or not name.strip():
            return
        name = name.strip()
        new_path = os.path.join(base_path, name)

        # # 중복된 이름이 존재하면 번호 증가
        # while os.path.exists(new_path):
        #     new_path = os.path.join(base_path, f"{name}_{counter}")
        #     counter += 1
        if os.path.exists(new_path):
            QMessageBox.warning(
                self, "경고", 
                "같은 이름의 폴더가 이미 존재합니다.",
            )
            return

        try:
            os.mkdir(new_path)  # 새 폴더 생성
        except Exception as e:
            QMessageBox.critical(
                self, "오류", 
                f"폴더 생성 실패:\n{e}",
            )
        else:
            # 트리 뷰 강제 갱신
            self.tree.setRootIndex(self.tree.rootIndex())
            parent_index = self.model.index(base_path)
            new_index = self.model.index(new_path)
            self.tree.expand(parent_index)
            self.tree.setCurrentIndex(new_index)
            self.tree.scrollTo(new_index)

            self.status_bar.showMessage(f"폴더 생성됨: {new_path}", 3000)  # 3초간 상태 메시지 표시

    def create_new_file(self):
        """
        현재 선택된 폴더 안에 새 텍스트 파일을 생성
        - 중복된 경우 NewFile_1.txt, NewFile_2.txt 등으로 생성
        """
        base_path = self.get_selected_directory()
        if not base_path:
            QMessageBox.warning(self, "경고", "디렉터리를 먼저 선택하세요.")
            return

        name, ok = QInputDialog.getText(
            self, "새 파일이름",
            "파일 이름을 입력하세요.",
            text="NewFile.txt",
            )
        if not ok or not name.strip():
            return # 취소하거나 빈 이름이면 그냥 반환.
        counter = 1
        name = name.strip()
        new_file_path = os.path.join(base_path, name)

        # 중복된 파일이 있다면 번호를 증가시키며 파일명 수정
        while os.path.exists(new_file_path):
            new_file_path = os.path.join(base_path, f"NewFile_{counter}.txt")
            counter += 1

        try:
            with open(new_file_path, "w", encoding="utf-8") as f:
                pass  # 내용 없는 빈 파일 생성
        except Exception as e:
            QMessageBox.critical(self, "오류", f"파일 생성 실패:\n{e}")
        else:
            parent_index = self.model.index(base_path)
            self.tree.expand(parent_index) # 디렉토리 펼치기.
            self.tree.scrollTo(parent_index) # 자동 스크롤.
            self.status_bar.showMessage(f"파일 생성됨: {new_file_path}", 3000)

    def rename_item(self):
        """
        선택된 item 의 이름 변경.
        """
        path = self.get_selected_path()
        if not path:
            QMessageBox.warning(
                self, "Warning",
                "먼저 item을 선택하세요.",
            )
            return
        new_name, ok = QInputDialog.getText(
            self, "rename",
            "새 이름:",
            text=os.path.basename(path),
        )
        if ok and new_name:
            new_path = os.path.join(
                os.path.dirname(path),
                new_name,
            )
            try:
                os.rename(path, new_path)
            except Exception as e:
                QMessageBox.critical(
                    self, "Critical Error",
                    f"이름 변경 실패:\n{e}"
                )
            else:
                self.status_bar.showMessage(f"이름 변경됨: {new_path}", 3000)

    def delete_item(self):
        """선택된 파일 또는 폴더 삭제"""
        path = self.get_selected_path()
        if not path:
            QMessageBox.warning(self, "경고", "삭제할 항목을 선택하세요.")
            return

        reply = QMessageBox.question(self, "삭제 확인", f"정말 삭제하시겠습니까?\n{path}",
                                     QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes:
            try:
                if os.path.isdir(path):
                    os.rmdir(path)  # 비어있을 경우만 삭제됨
                    # shutil.rmtree(path) # import shutil 필요.
                else:
                    os.remove(path)
            except Exception as e:
                QMessageBox.critical(self, "오류", f"삭제 실패:\n{e}")
            else:
                self.status_bar.showMessage(f"삭제됨: {path}", 3000)

    def update_path_label(self, selected, _):
        """
        선택된 항목이 변경될 때 라벨을 갱신하여 선택 경로를 표시

        - selected: QItemSelection
        - deselected: QItemSelection
        """
        if indexes := selected.indexes():  # 선택된 인덱스가 있을 경우
            path = self.model.filePath(indexes[0])
            self.path_label.setText(f"선택된 경로: {path}")

    def open_file_on_double_click(self, index):
        """
        파일을 더블 클릭했을 때 시스템 기본 프로그램으로 열기
        - 폴더일 경우 아무 작업도 하지 않음
        """
        if not index.isValid():
            return

        file_path = self.model.filePath(index)

        # 파일일 경우에만 열기
        if not self.model.isDir(index) and os.path.exists(file_path):
            QDesktopServices.openUrl(QUrl.fromLocalFile(file_path))  # 시스템 기본 앱으로 열기


# 프로그램의 진입점: 애플리케이션 실행
if __name__ == "__main__":
    app = QApplication(sys.argv)         # QApplication 객체 생성
    wnd = FileExplorerMainWindow()    # 메인 윈도우 인스턴스 생성
    sys.exit(app.exec())                 # 이벤트 루프 실행 및 종료 처리

같이보면 좋은 자료들

2025.06.03 - [Python/PySide PyQt] - [PySide] CustomModel 구현을 통한 Model-View 이해 - 작성중

 

[PySide] CustomModel 구현을 통한 Model-View 이해 - 작성중

Qt Model-View Tutorial: QListView + QAbstractListModel 이해Qt에서는 복잡한 데이터 구조를 UI에 효율적으로 표현하고 조작하기 위해 모델-뷰(Model-View) Architecture를 채택함.이 문서에서는 그 개념을 정리하고, Q

ds31x.tistory.com

 


 

728x90