QItemSelectionModel은 Qt Model-View Architecture에서 selection 을 관리하는 클래스.
QItemSelectionModel 개요
from PySide6.QtCore import QItemSelectionModel, QModelIndex
from PySide6.QtWidgets import QListView, QTableView, QTreeView
# 모든 View는 자동으로 QItemSelectionModel을 가집니다
view = QListView()
selection_model = view.selectionModel() # QItemSelectionModel 인스턴스
print(type(selection_model)) # <class 'PySide6.QtCore.QItemSelectionModel'>
역할:
- 현재 선택된 항목(current item) 추적
- 선택 영역(selection range) 관리
- 선택 변경 시 자동으로 signal 발생: slot 연결을 통한 처리.
- 프로그래밍을 통한 선택(selection) 조작 제공
다음 URL의 글에서 사용된 방식도 같이 확인해 볼 것.
2025.06.02 - [Python/PySide PyQt] - [PySide6] QTreeView 와 QStandardItemModel, QStandardItem
[PySide6] QTreeView 와 QStandardItemModel, QStandardItem
0. Pre-requisites:0-0. Model-View Architecture란?QTreeView는 Qt의 Model-View Architecture 를 따름.이는 데이터(Model)와 그 표현(View)을 분리하는 디자인 패턴.Qt에서 Model-View Architecture는 다음의 요소로 구성됨:Model:데
ds31x.tistory.com
1. 주요 시그널들 (Signals)
1-1. currentChanged-Signal
1-1-1. Signature:
currentChanged(current: QModelIndex, previous: QModelIndex)
1-1-2. 용도:
- 현재 selection(선택된 items)이 바뀔 때 발생
- QAbstractItemView.SingleSelection(단일 선택, setSelectionMode메서드로 설정)에서 가장 중요한 signal
- 키보드 커서나 마우스 클릭으로 다른 항목을 선택할 때 발생
1-1-3. 매개변수:
current: 새로 선택된 항목의 QModelIndexprevious: 이전에 선택되었던 항목의 QModelIndex
QModelIndex 의 주요 메서드는 다음과 같음:
- row(): 부모 내에서의 행 번호 반환
- column(): 열 번호 반환
- parent(): 부모 인덱스 반환
- isValid(): 유효한 인덱스인지 확인
1-1-4. 예제:
def on_current_changed(self, current, previous):
if current.isValid():
item_text = self.model.data(current, Qt.DisplayRole)
print(f"새 선택: {item_text} (행 {current.row()})")
if previous.isValid():
prev_text = self.model.data(previous, Qt.DisplayRole)
print(f"이전 선택: {prev_text} (행 {previous.row()})")
# 연결
selection_model.currentChanged.connect(on_current_changed)
1-2. selectionChanged 시그널
1-2-1. 시그니처:
selectionChanged(selected: QItemSelection, deselected: QItemSelection)
1-2-2. 용도:
- 현재 selection(선택된 items)이 바뀔 때 발생
- QAbstractItemView.MultipleSelection(다중 선택)에서 주로 사용
Ctrl+<click>,Shift+<click>으로 여러 항목을 선택/해제할 때- 프로그래밍 코드 상에서 selection(선택)을 추가/제거할 때
1-2-3. 매개변수:
selected: 새로 선택된 영역들 (QItemSelection)deselected: 선택 해제된 영역들 (QItemSelection)
참고: QItemSelection
- select(topLeft: QModelIndex, bottomRight: QModelIndex)
주어진 인덱스 범위를 선택 영역에 추가 - merge(other: QItemSelection, command: QItemSelectionModel.SelectionFlags)
다른 선택 객체와 병합 (선택 방식 지정 가능) - indexes(): List[QModelIndex]
선택된 모든 인덱스를 평탄화하여 리스트로 반환 - ranges(): List[QItemSelectionRange]
선택된 범위(QItemSelectionRange 객체)들의 list 객체 반환 - clear()
선택 영역 초기화 - isEmpty(): bool
선택 영역이 비어 있는지 확인 - size(): int
포함된 선택 범위의 개수 반환 - append(range: QItemSelectionRange)
선택 범위 하나를 추가
1-2-4. 예제:
def on_selection_changed(self, selected, deselected):
total_selected = len(self.selection_model.selectedIndexes())
print(f"현재 총 선택된 항목: {total_selected}개")
# 새로 선택된 범위 정보
if not selected.isEmpty():
for selection_range in selected:
print(f"추가 선택: {selection_range.top()}-{selection_range.bottom()}행")
# 선택 해제된 범위 정보
if not deselected.isEmpty():
for selection_range in deselected:
print(f"선택 해제: {selection_range.top()}-{selection_range.bottom()}행")
# 연결
selection_model.selectionChanged.connect(on_selection_changed)
1-3 currentRowChanged 시그널
1-3-1. 시그니처:
currentRowChanged(current: QModelIndex, previous: QModelIndex)
1-3-2. 용도:
- 현재 선택된 행이 바뀔 때 발생
currentChanged와 유사하지만 행(row) 변경에만 특화- 테이블 뷰에서 같은 행의 다른 열로 이동할 때는 발생하지 않음
1-3-3. 예제:
def on_current_row_changed(self, current, previous):
if current.isValid():
print(f"현재 행: {current.row()}")
if previous.isValid():
print(f"이전 행: {previous.row()}")
# 연결
selection_model.currentRowChanged.connect(on_current_row_changed)
1-4. currentColumnChanged 시그널
1-4-1. 시그니처:
currentColumnChanged(current: QModelIndex, previous: QModelIndex)
1-4-2. 용도:
- 현재 선택된 열이 바뀔 때 발생
- 주로 테이블 뷰에서 사용
- 리스트 뷰에서는 거의 사용하지 않음 (열이 1개뿐이므로)
1-4-3. 예제:
def on_current_column_changed(self, current, previous):
if current.isValid():
print(f"현재 열: {current.column()}")
# 연결 (주로 QTableView에서)
table_view.selectionModel().currentColumnChanged.connect(on_current_column_changed)
2. 주요 메서드들 (Methods)
2-1. 선택 상태 조회 메서드
2-1-1. currentIndex() 메서드
2-1-1-1. 시그니처:
currentIndex() -> QModelIndex
2-1-1-2. 용도:
- 현재 포커스가 있는 항목의 인덱스를 반환
- 키보드 입력을 받을 항목을 나타냄
- 다중 선택 시에도 하나의 항목만 "current"가 됨
2-1-1-3. 예제:
current = selection_model.currentIndex()
if current.isValid():
item_text = model.data(current, Qt.DisplayRole)
print(f"현재 항목: {item_text} (행: {current.row()}, 열: {current.column()})")
else:
print("현재 선택된 항목 없음")
2-1-2. selectedIndexes() 메서드
2-1-2-1. 시그니처:
selectedIndexes() -> List[QModelIndex]
2-1-2-2. 용도:
- 선택된 모든 인덱스들을 리스트로 반환
- 다중 선택에서 모든 선택된 항목을 확인할 때 사용
- 빈 리스트면 선택된 항목이 없음
2-1-2-3. 예제:
selected = selection_model.selectedIndexes()
print(f"선택된 항목 수: {len(selected)}")
for index in selected:
item_text = model.data(index, Qt.DisplayRole)
print(f"선택됨: {item_text} (행 {index.row()})")
2-1-3. selectedRows() 메서드
2-1-3-1. 시그니처:
selectedRows(column: int = 0) -> List[QModelIndex]
2-1-3-2. 용도:
- 선택된 행들의 인덱스를 반환
- 테이블에서 전체 행이 선택된 경우 사용
column매개변수로 어느 열의 인덱스를 반환할지 지정
2-1-3-3. 예제:
selected_rows = selection_model.selectedRows()
print(f"선택된 행 수: {len(selected_rows)}")
for row_index in selected_rows:
print(f"선택된 행: {row_index.row()}")
2-1-4. hasSelection() 메서드
2-1-4-1. 시그니처:
hasSelection() -> bool
2-1-4-2. 용도:
- 선택된 항목이 하나라도 있는지 확인
- GUI 업데이트나 메뉴 활성화 판단에 사용
2-1-4-3. 예제:
if selection_model.hasSelection():
print("선택된 항목이 있습니다")
# 삭제 버튼 활성화 등
delete_button.setEnabled(True)
else:
print("선택된 항목이 없습니다")
delete_button.setEnabled(False)
2-1-5. isSelected() 메서드
2-1-5-1. 시그니처:
isSelected(index: QModelIndex) -> bool
2-1-5-2. 용도:
- 특정 인덱스가 선택되었는지 확인
- 조건부 처리나 상태 확인에 사용
2-1-5-3. 예제:
test_index = model.index(5, 0) # 5번째 행
if selection_model.isSelected(test_index):
print("5번째 항목이 선택되어 있습니다")
else:
print("5번째 항목이 선택되어 있지 않습니다")
2-2. 선택 상태 설정 메서드
2-2-1. setCurrentIndex() 메서드
2-2-1-1. 시그니처:
setCurrentIndex(index: QModelIndex, command: QItemSelectionModel.SelectionFlags)
2-2-1-2. 용도:
- 현재 선택을 프로그래밍으로 변경
- 두 번째 매개변수로 선택 동작을 제어
2-2-1-3. SelectionFlags:
NoUpdate: 아무것도 하지 않음Clear: 기존 선택 모두 해제Select: 해당 항목 선택ClearAndSelect: 기존 해제 후 해당 항목만 선택Current: 현재 항목으로 설정 (포커스만)
2-2-1-4. 예제:
# 3번째 행을 선택하고 기존 선택은 모두 해제
new_index = model.index(3, 0)
selection_model.setCurrentIndex(new_index, QItemSelectionModel.ClearAndSelect)
# 포커스만 이동 (선택 상태 변경 없음)
focus_index = model.index(5, 0)
selection_model.setCurrentIndex(focus_index, QItemSelectionModel.Current)
2-2-2. select() 메서드
2-2-2-1. 시그니처:
select(index: QModelIndex, command: QItemSelectionModel.SelectionFlags)
select(selection: QItemSelection, command: QItemSelectionModel.SelectionFlags)
2-2-2-2. 용도:
- 특정 인덱스나 선택 영역을 선택/해제
- 다중 선택을 프로그래밍으로 조작할 때 사용
2-2-2-3. 예제:
# 기존 선택에 추가
new_index = model.index(7, 0)
selection_model.select(new_index, QItemSelectionModel.Select)
# 특정 항목의 선택을 토글
toggle_index = model.index(2, 0)
selection_model.select(toggle_index, QItemSelectionModel.Toggle)
# 특정 항목만 선택 해제
deselect_index = model.index(4, 0)
selection_model.select(deselect_index, QItemSelectionModel.Deselect)
2-2-3. selectAll() 메서드 - (View 용)
QItemSelectionModel의 메서드가 아니며, 연결된 View 객체에서 호출해야 한다.
자주 이용되는터라 여기에 넣어두었지만, 주의할 것.
2-2-3-1. 시그니처:
selectAll() -> None
2-2-3-2.용도:
- 모든 항목을 선택
- Ctrl+A 기능 구현에 사용
2-2-3-3.예제:
# 전체 선택
# selection_model.selectAll() # not working!
self.view.selectAll()
print(f"전체 선택됨: {len(selection_model.selectedIndexes())}개 항목")
2-2-4. clearSelection() 메서드
2-2-4-1. 시그니처:
clearSelection() -> None
2-2-4-2. 용도:
- 모든 선택을 해제
- 현재 항목(current)은 유지됨
2-2-4-3. 예제:
# 모든 선택 해제
selection_model.clearSelection()
print("모든 선택이 해제되었습니다")
# 현재 항목 확인 (여전히 유효할 수 있음)
current = selection_model.currentIndex()
if current.isValid():
print(f"현재 항목은 여전히 유효: 행 {current.row()}")
2-2-5. clearCurrentIndex() 메서드
2-2-5-1. 시그니처:
clearCurrentIndex() -> None
2-2-5-2. 용도:
- 현재 항목(current) 설정을 해제
- 포커스를 완전히 제거
2-2-5-3. 예제:
# 현재 항목과 선택을 모두 해제
selection_model.clearSelection()
selection_model.clearCurrentIndex()
# 이제 아무것도 선택되지 않음
print(f"현재 항목 유효: {selection_model.currentIndex().isValid()}") # False
print(f"선택 항목 있음: {selection_model.hasSelection()}") # False
3. 종합 예제

from PySide6.QtCore import (
Qt,
QAbstractListModel,
QModelIndex,
QItemSelectionModel,
QItemSelection,
)
from PySide6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QListView,
QPushButton, QLabel, QTextEdit, QSpinBox, QComboBox, QGroupBox,
QAbstractItemView, QCheckBox,
)
import sys
class NumberListModel(QAbstractListModel):
"""숫자 리스트 모델"""
def __init__(self, numbers=None):
super().__init__()
self._numbers = numbers or list(range(1, 21)) # 1~20
def rowCount(self, parent=QModelIndex()):
return len(self._numbers)
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
if role == Qt.DisplayRole:
return f"항목 {self._numbers[index.row()]}"
return None
class SelectionModelDemo(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("QItemSelectionModel 종합 데모")
self.resize(1000, 800)
# 모델과 뷰 생성
self.model = NumberListModel()
self.view = QListView()
self.view.setModel(self.model)
self.view.setSelectionMode(QAbstractItemView.ExtendedSelection)
# 선택 모델 참조
self.selection_model = self.view.selectionModel()
# 로그 영역
self.log = QTextEdit()
self.log.setMaximumHeight(250)
self.setup_ui()
self.connect_all_signals()
def setup_ui(self):
main_layout = QHBoxLayout(self)
# 왼쪽: 뷰와 시그널 정보
left_layout = QVBoxLayout()
# 리스트 뷰
left_layout.addWidget(QLabel("숫자 리스트 (Ctrl/Shift로 다중 선택)"))
left_layout.addWidget(self.view)
# 시그널 정보 그룹
signal_group = self.create_signal_info_group()
left_layout.addWidget(signal_group)
main_layout.addWidget(self.create_group("뷰 & 시그널", left_layout))
# 오른쪽: 메서드 테스트
right_layout = QVBoxLayout()
# 조회 메서드 그룹
query_group = self.create_query_methods_group()
right_layout.addWidget(query_group)
# 설정 메서드 그룹
control_group = self.create_control_methods_group()
right_layout.addWidget(control_group)
main_layout.addWidget(self.create_group("메서드 테스트", right_layout))
# 하단: 로그
bottom_layout = QVBoxLayout()
bottom_layout.addWidget(QLabel("시그널 & 메서드 실행 로그:"))
bottom_layout.addWidget(self.log)
main_widget = QWidget()
main_widget.setLayout(main_layout)
container_layout = QVBoxLayout(self)
container_layout.addWidget(main_widget)
container_layout.addLayout(bottom_layout)
def create_group(self, title, layout):
"""그룹박스 생성"""
group = QGroupBox(title)
group.setLayout(layout)
return group
def create_signal_info_group(self):
"""시그널 정보 표시 그룹"""
layout = QVBoxLayout()
# 시그널 활성화 체크박스들
self.current_changed_cb = QCheckBox("currentChanged 시그널")
self.current_changed_cb.setChecked(True)
self.current_changed_cb.toggled.connect(self.toggle_current_changed)
layout.addWidget(self.current_changed_cb)
self.selection_changed_cb = QCheckBox("selectionChanged 시그널")
self.selection_changed_cb.setChecked(True)
self.selection_changed_cb.toggled.connect(self.toggle_selection_changed)
layout.addWidget(self.selection_changed_cb)
self.current_row_changed_cb = QCheckBox("currentRowChanged 시그널")
self.current_row_changed_cb.setChecked(False)
self.current_row_changed_cb.toggled.connect(self.toggle_current_row_changed)
layout.addWidget(self.current_row_changed_cb)
layout.addWidget(QLabel("시그널을 체크/해제하여 로그 출력을 제어하세요"))
return self.create_group("시그널 모니터링", layout)
def create_query_methods_group(self):
"""조회 메서드 테스트 그룹"""
layout = QVBoxLayout()
# currentIndex() 테스트
btn_current = QPushButton("currentIndex() 실행")
btn_current.clicked.connect(self.test_current_index)
layout.addWidget(btn_current)
# selectedIndexes() 테스트
btn_selected = QPushButton("selectedIndexes() 실행")
btn_selected.clicked.connect(self.test_selected_indexes)
layout.addWidget(btn_selected)
# selectedRows() 테스트
btn_rows = QPushButton("selectedRows() 실행")
btn_rows.clicked.connect(self.test_selected_rows)
layout.addWidget(btn_rows)
# hasSelection() 테스트
btn_has = QPushButton("hasSelection() 실행")
btn_has.clicked.connect(self.test_has_selection)
layout.addWidget(btn_has)
# isSelected() 테스트
test_layout = QHBoxLayout()
self.test_index_spin = QSpinBox()
self.test_index_spin.setRange(0, 19)
self.test_index_spin.setValue(5)
test_layout.addWidget(QLabel("인덱스:"))
test_layout.addWidget(self.test_index_spin)
btn_is_selected = QPushButton("isSelected() 실행")
btn_is_selected.clicked.connect(self.test_is_selected)
test_layout.addWidget(btn_is_selected)
layout.addLayout(test_layout)
return self.create_group("조회 메서드", layout)
def create_control_methods_group(self):
"""제어 메서드 테스트 그룹"""
layout = QVBoxLayout()
# setCurrentIndex() 테스트
current_layout = QHBoxLayout()
self.set_index_spin = QSpinBox()
self.set_index_spin.setRange(0, 19)
self.set_index_spin.setValue(10)
self.selection_flag_combo = QComboBox()
self.selection_flag_combo.addItems([
"ClearAndSelect", "Select", "Current", "Clear"
])
current_layout.addWidget(QLabel("인덱스:"))
current_layout.addWidget(self.set_index_spin)
current_layout.addWidget(QLabel("플래그:"))
current_layout.addWidget(self.selection_flag_combo)
btn_set_current = QPushButton("setCurrentIndex() 실행")
btn_set_current.clicked.connect(self.test_set_current_index)
current_layout.addWidget(btn_set_current)
layout.addLayout(current_layout)
# select() 테스트
select_layout = QHBoxLayout()
self.select_index_spin = QSpinBox()
self.select_index_spin.setRange(0, 19)
self.select_index_spin.setValue(7)
self.select_flag_combo = QComboBox()
self.select_flag_combo.addItems([
"Select", "Deselect", "Toggle"
])
select_layout.addWidget(QLabel("인덱스:"))
select_layout.addWidget(self.select_index_spin)
select_layout.addWidget(QLabel("플래그:"))
select_layout.addWidget(self.select_flag_combo)
btn_select = QPushButton("select() 실행")
btn_select.clicked.connect(self.test_select)
select_layout.addWidget(btn_select)
layout.addLayout(select_layout)
# 기타 제어 메서드들
other_layout = QHBoxLayout()
btn_select_all = QPushButton("view.selectAll()")
btn_select_all.clicked.connect(self.test_select_all)
other_layout.addWidget(btn_select_all)
btn_clear_selection = QPushButton("clearSelection()")
btn_clear_selection.clicked.connect(self.test_clear_selection)
other_layout.addWidget(btn_clear_selection)
btn_clear_current = QPushButton("clearCurrentIndex()")
btn_clear_current.clicked.connect(self.test_clear_current)
other_layout.addWidget(btn_clear_current)
layout.addLayout(other_layout)
# 범위 선택 테스트
range_layout = QHBoxLayout()
range_layout.addWidget(QLabel("범위 선택 (5-10행):"))
btn_select_range = QPushButton("범위 선택 실행")
btn_select_range.clicked.connect(self.test_select_range)
range_layout.addWidget(btn_select_range)
layout.addLayout(range_layout)
return self.create_group("제어 메서드", layout)
def connect_all_signals(self):
"""모든 시그널 연결"""
# 기본적으로 연결
self.selection_model.currentChanged.connect(self.on_current_changed)
self.selection_model.selectionChanged.connect(self.on_selection_changed)
def log_message(self, message, color="black"):
"""로그 메시지 추가"""
self.log.append(f'<span style="color: {color};">{message}</span>')
# =============================================================================
# 시그널 핸들러들
# =============================================================================
def on_current_changed(self, current, previous):
"""currentChanged 시그널 핸들러"""
if not self.current_changed_cb.isChecked():
return
msg = "- currentChanged 시그널 발생"
if current.isValid():
item_text = self.model.data(current, Qt.DisplayRole)
msg += f" → 새 선택: {item_text} (행 {current.row()})"
else:
msg += " → 새 선택: 없음"
if previous.isValid():
prev_text = self.model.data(previous, Qt.DisplayRole)
msg += f", 이전: {prev_text} (행 {previous.row()})"
else:
msg += ", 이전: 없음"
self.log_message(msg, "blue")
def on_selection_changed(self, selected, deselected):
"""selectionChanged 시그널 핸들러"""
if not self.selection_changed_cb.isChecked():
return
total = len(self.selection_model.selectedIndexes())
msg = f"- selectionChanged 시그널 발생 → 총 선택: {total}개"
if not selected.isEmpty():
ranges = selected[0] # 첫 번째 범위
msg += f", 추가: {ranges.top()}-{ranges.bottom()}행"
if not deselected.isEmpty():
ranges = deselected[0] # 첫 번째 범위
msg += f", 제거: {ranges.top()}-{ranges.bottom()}행"
self.log_message(msg, "green")
def on_current_row_changed(self, current, previous):
"""currentRowChanged 시그널 핸들러"""
if not self.current_row_changed_cb.isChecked():
return
msg = "- currentRowChanged 시그널 발생"
if current.isValid():
msg += f" → 새 행: {current.row()}"
if previous.isValid():
msg += f", 이전 행: {previous.row()}"
self.log_message(msg, "purple")
# =============================================================================
# 시그널 토글 메서드들
# =============================================================================
def toggle_current_changed(self, enabled):
if enabled:
self.selection_model.currentChanged.connect(self.on_current_changed)
self.log_message("- currentChanged 시그널 연결됨", "orange")
else:
self.selection_model.currentChanged.disconnect(self.on_current_changed)
self.log_message("- currentChanged 시그널 연결 해제됨", "orange")
def toggle_selection_changed(self, enabled):
if enabled:
self.selection_model.selectionChanged.connect(self.on_selection_changed)
self.log_message("- selectionChanged 시그널 연결됨", "orange")
else:
self.selection_model.selectionChanged.disconnect(self.on_selection_changed)
self.log_message("- selectionChanged 시그널 연결 해제됨", "orange")
def toggle_current_row_changed(self, enabled):
if enabled:
self.selection_model.currentRowChanged.connect(self.on_current_row_changed)
self.log_message("- currentRowChanged 시그널 연결됨", "orange")
else:
self.selection_model.currentRowChanged.disconnect(self.on_current_row_changed)
self.log_message("- currentRowChanged 시그널 연결 해제됨", "orange")
# =============================================================================
# 조회 메서드 테스트들
# =============================================================================
def test_current_index(self):
"""currentIndex() 메서드 테스트"""
current = self.selection_model.currentIndex()
if current.isValid():
item_text = self.model.data(current, Qt.DisplayRole)
self.log_message(f"- currentIndex(): {item_text} (행 {current.row()}, 열 {current.column()})", "navy")
else:
self.log_message("- currentIndex(): 유효하지 않음 (선택 없음)", "navy")
def test_selected_indexes(self):
"""selectedIndexes() 메서드 테스트"""
selected = self.selection_model.selectedIndexes()
self.log_message(f"- selectedIndexes(): {len(selected)}개 항목 선택됨", "navy")
if selected:
items = []
for index in selected[:5]: # 최대 5개만 표시
item_text = self.model.data(index, Qt.DisplayRole)
items.append(f"{item_text}(행{index.row()})")
display_text = ", ".join(items)
if len(selected) > 5:
display_text += f" ... (총 {len(selected)}개)"
self.log_message(f" → {display_text}", "navy")
def test_selected_rows(self):
"""selectedRows() 메서드 테스트"""
selected_rows = self.selection_model.selectedRows()
self.log_message(f"- selectedRows(): {len(selected_rows)}개 행 선택됨", "navy")
if selected_rows:
row_numbers = [idx.row() for idx in selected_rows[:10]]
if len(selected_rows) > 10:
self.log_message(f" → 행 번호: {row_numbers} ... (총 {len(selected_rows)}개)", "navy")
else:
self.log_message(f" → 행 번호: {row_numbers}", "navy")
def test_has_selection(self):
"""hasSelection() 메서드 테스트"""
has_selection = self.selection_model.hasSelection()
self.log_message(f"- hasSelection(): {has_selection}", "navy")
def test_is_selected(self):
"""isSelected() 메서드 테스트"""
row = self.test_index_spin.value()
test_index = self.model.index(row)
is_selected = self.selection_model.isSelected(test_index)
item_text = self.model.data(test_index, Qt.DisplayRole)
self.log_message(f"- isSelected({item_text}): {is_selected}", "navy")
# =============================================================================
# 제어 메서드 테스트들
# =============================================================================
def test_set_current_index(self):
"""setCurrentIndex() 메서드 테스트"""
row = self.set_index_spin.value()
index = self.model.index(row)
flag_text = self.selection_flag_combo.currentText()
flag_map = {
"ClearAndSelect": QItemSelectionModel.ClearAndSelect,
"Select": QItemSelectionModel.Select,
"Current": QItemSelectionModel.Current,
"Clear": QItemSelectionModel.Clear
}
flag = flag_map[flag_text]
self.selection_model.setCurrentIndex(index, flag)
item_text = self.model.data(index, Qt.DisplayRole)
self.log_message(f"- setCurrentIndex({item_text}, {flag_text}) 실행됨", "red")
def test_select(self):
"""select() 메서드 테스트"""
row = self.select_index_spin.value()
index = self.model.index(row)
flag_text = self.select_flag_combo.currentText()
flag_map = {
"Select": QItemSelectionModel.Select,
"Deselect": QItemSelectionModel.Deselect,
"Toggle": QItemSelectionModel.Toggle
}
flag = flag_map[flag_text]
self.selection_model.select(index, flag)
item_text = self.model.data(index, Qt.DisplayRole)
self.log_message(f"- select({item_text}, {flag_text}) 실행됨", "red")
def test_select_all(self):
"""view.selectAll() 메서드 테스트"""
# self.selection_model.selectAll()
self.view.selectAll() # view에 존재함.
total = len(self.selection_model.selectedIndexes())
self.log_message(f"- view.selectAll() 실행됨 → {total}개 항목 선택", "red")
def test_clear_selection(self):
"""clearSelection() 메서드 테스트"""
self.selection_model.clearSelection()
self.log_message("- clearSelection() 실행됨 → 모든 선택 해제", "red")
def test_clear_current(self):
"""clearCurrentIndex() 메서드 테스트"""
self.selection_model.clearCurrentIndex()
self.log_message("- clearCurrentIndex() 실행됨 → 현재 항목 해제", "red")
def test_select_range(self):
"""범위 선택 테스트"""
start_index = self.model.index(5)
end_index = self.model.index(10)
selection = QItemSelection(start_index, end_index)
self.selection_model.select(selection, QItemSelectionModel.ClearAndSelect)
self.log_message("- 범위 선택 (5-10행) 실행됨", "red")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SelectionModelDemo()
window.show()
sys.exit(app.exec())
4. 실용적 활용 가이드
4-1. 상황별 시그널 선택
# 상황 1: 단일 선택에서 선택 변경 감지 (가장 일반적)
selection_model.currentChanged.connect(self.on_item_selected)
# 상황 2: 다중 선택에서 선택 변경 감지
selection_model.selectionChanged.connect(self.on_multi_selection_changed)
# 상황 3: 테이블에서 행 변경만 감지 (열 변경은 무시)
selection_model.currentRowChanged.connect(self.on_row_changed)
4-2. 자주 사용하는 패턴
# 패턴 1: 선택된 데이터 가져오기
def get_selected_data(self):
selected_data = []
for index in self.selection_model.selectedIndexes():
data = self.model.data(index, Qt.DisplayRole)
selected_data.append(data)
return selected_data
# 패턴 2: 조건부 GUI 업데이트
def update_buttons(self):
has_selection = self.selection_model.hasSelection()
self.delete_button.setEnabled(has_selection)
self.edit_button.setEnabled(has_selection)
current = self.selection_model.currentIndex()
self.details_panel.setVisible(current.isValid())
# 패턴 3: 프로그래밍으로 안전한 선택
def select_item_safely(self, row):
if 0 <= row < self.model.rowCount():
index = self.model.index(row)
self.selection_model.setCurrentIndex(index, QItemSelectionModel.ClearAndSelect)
return True
return False
5. 정리
5-1. 가장 중요한 시그널 TOP 3:
currentChanged: 단일 선택 변경 (90% 사용)selectionChanged: 다중 선택 변경currentRowChanged: 테이블에서 행 변경
5-2. 가장 중요한 메서드 TOP 5:
currentIndex()- 현재 선택 조회selectedIndexes()- 모든 선택 조회setCurrentIndex()- 프로그래밍으로 선택hasSelection()- 선택 여부 확인clearSelection()- 선택 해제
5-3. 실무 팁:
- 단일 선택 앱 :
currentChanged시그널만 사용 - 다중 선택 앱 :
selectionChanged시그널 주로 사용 - GUI 업데이트 :
hasSelection(),currentIndex().isValid()활용 - 안전한 선택 : 항상 인덱스 유효성 검사 후 설정
'Python > PySide PyQt' 카테고리의 다른 글
| [PySide] CustomModel 구현을 통한 Model-View 이해 - 작성중 (2) | 2025.06.03 |
|---|---|
| [PySide6] QTreeView 와 QStandardItemModel, QStandardItem (1) | 2025.06.02 |
| [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 |