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 클래스
QStandardItemModel은QStandardItem을 사용하여 데이터를 구성.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(): 현재 선택된 IndexsetCurrentIndex(): 특정 Index 선택.selectionModel(): selection 에 대한 모델 반환.- 반환되는 객체에 대한 자세한 건 아래에서 다룸: QItemSelectionModel 객체
setSelectionBehavior(behavior): 선택 동작 설정SelectItems: item 단위로 선택됨.SelectRows: 행 단위로 선택됨.SelectColumns: 열 단위로 선택됨.
setSelectionMode(mode): Item의 선택 모드 설정QAbstractItemView의 다음의 모드를 argument로 넘겨주어 설정 (QTreeView는QAbstractItemView의 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 이동만 허용.
- Qt의
- 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
'Python > PySide PyQt' 카테고리의 다른 글
| [PySide] QItemSelectionModel 살펴보기 - 작성중 (0) | 2025.06.04 |
|---|---|
| [PySide] CustomModel 구현을 통한 Model-View 이해 - 작성중 (2) | 2025.06.03 |
| [PySide6] QWidget.setFocusPolicy(policy: Qt.FocusPolicy) (0) | 2025.05.13 |
| [PySide6] Installing PySide6 (and Designer) on Windows (with Conda) (0) | 2025.02.11 |
| [PySide] Ex: Img Viewer. QListWidget and Matplotlib (0) | 2024.06.04 |

