PyQt5 QTreeViewのサンプル
2020/01/27 categories:PyQt5| tags:Python|PyQt5|QTreeView|
PyQt5でツリーを表示するときに使えるQTreeViewを使ってみました。
実装
QTreeViewでは親子関係のあるデータを表示できます。モデルで扱うデータはItemというクラスで、プロパティに単一の親parent_itemと子のリストchildrenを持たせて親子関係を実現しました。
class Item(object):
def __init__(self, _parent=None):
self._dict = {}
self.parent_item = _parent
self.children = []
そのほか変わったところといえば、アイテムを追加したときにIDのデータに親の行数を再帰で取得するという処理を入れて、Insertボタンをクリックしてアイテムを追加したときの見た目がわかりやすくなるようにしています。
def recursion(item, _ids):
_ids.append( str(item.row()) )
if item == self.root_item:
return
return recursion(item.parent(), _ids)
ids = []
recursion(item, ids)
item.setData( 'ID', '-'.join(ids[::-1][1:]) )
所感
QTableViewと比べると、モデルのindexやparentメソッドなどに親がいる場合や子がいる場合の処理などを加えなければいけないのでQTableView用に作成したモデルをそのまま使えない場合があるかもしれません。反対に、QTreeView用に作成したモデルはそのままQListViewやQTableViewでも使えると思います。したがって、QListView、QTableView、QTreeView に同じモデルを渡して同じ内容を表示したりもできます。
例えば部品表などの親子関係があるデータはエクセルで表を作ろうとすると見づらくなったりするので、QTreeViewを使うと見やすい表が作れるかもしれませんね。
ソースコード
main.py
import sys
from mainwindow import Ui_MainWindow
from treeview import Model, Delegate, Item
from PyQt5 import QtWidgets, QtCore
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, app):
super().__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.model = Model(self)
self.model.addColumns(['ID', 'Name'])
self.ui.treeView.setModel(self.model)
self.ui.treeView.setItemDelegate(Delegate())
self.ui.treeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.ui.treeView.customContextMenuRequested.connect(self.contextMenu)
self.ui.pushButton.clicked.connect(self.insertRow)
self.ui.pushButton_2.clicked.connect(self.delItem)
def contextMenu(self, point):
self.menu = QtWidgets.QMenu(self)
self.menu.addAction('Add', self.insertRow)
self.menu.addAction('Delete', self.delItem)
self.menu.exec_( self.focusWidget().mapToGlobal(point) )
def insertRow(self):
indexes = self.ui.treeView.selectedIndexes()
if len(indexes) == 0:
self.model.addItem()
return
indexes2 = []
for index in indexes:
if not index.row() in [ index2.row() for index2 in indexes2 if index2.parent() == index.parent() ]:
indexes2.append(index)
for index in indexes2:
self.model.addItem(index)
def delItem(self):
indexes = self.ui.treeView.selectedIndexes()
if len(indexes) == 0:
return
indexes2 = []
for index in indexes:
if not index.row() in [ index2.row() for index2 in indexes2 if index2.parent() == index.parent() ]:
indexes2.append(index)
for index in indexes2:
self.model.removeItem(index)
def main():
app = QtWidgets.QApplication(sys.argv)
window = MainWindow(app)
window.show()
app.exec_()
if __name__ == '__main__':
main()
treeview.py
## -*- coding: utf-8 -*-
from PyQt5 import QtWidgets, QtCore
class Item(object):
def __init__(self, _parent=None):
self._dict = {}
self.parent_item = _parent
self.children = []
def appendChild(self, item):
self.children.append(item)
def data(self, column):
if column in self._dict.keys():
return self._dict[column]
return ''
def setData(self, column, data):
self._dict[column] = data
def child(self, row):
return self.children[row]
def childrenCount(self):
return len(self.children)
def parent(self):
return self.parent_item
def removeChild(self, row):
del self.children[row]
def row(self):
if self.parent_item:
return self.parent_item.children.index(self)
return 0
class Model(QtCore.QAbstractItemModel):
def __init__(self, parent_=None):
super(Model, self).__init__(parent_)
self.root_item = Item()
self.root_item.setData('ID', 'root item')
self.columns = []
def addColumns(self, columns, parent=QtCore.QModelIndex()):
self.beginInsertColumns(parent, self.columnCount(), self.columnCount() + len(columns) - 1)
self.columns.extend(columns)
self.endInsertColumns()
def addItem(self, parent=QtCore.QModelIndex()):
if parent == QtCore.QModelIndex():
parent_item = self.root_item
else:
parent_item = parent.internalPointer()
item = Item(parent_item)
row = parent_item.childrenCount()
self.beginInsertRows(parent, row, row)
parent_item.children.insert( row, item )
self.endInsertRows()
def recursion(item, _ids):
_ids.append( str(item.row()) )
if item == self.root_item:
return
return recursion(item.parent(), _ids)
ids = []
recursion(item, ids)
item.setData( 'ID', '-'.join(ids[::-1][1:]) )
def column(self, key):
return self.columns[key]
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.columns)
def data(self, index, role):
if not index.isValid():
return QtCore.QVariant()
if role == QtCore.Qt.EditRole or role == QtCore.Qt.DisplayRole:
return index.internalPointer().data( self.column(index.column()) )
return QtCore.QVariant()
def flags(self, index):
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
def headerData(self, i, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self.columns[i]
if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole:
return i + 1
def index(self, row, column, parent):
if not parent.isValid():
parent_item = self.root_item
else:
parent_item = parent.internalPointer()
if parent_item.childrenCount() > 0:
return self.createIndex(row, column, parent_item.child(row))
return QtCore.QModelIndex()
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
child_item = index.internalPointer()
if not child_item:
return QtCore.QModelIndex()
parent_item = child_item.parent()
if parent_item == self.root_item:
return QtCore.QModelIndex()
return self.createIndex(parent_item.row(), 0, parent_item)
def removeItem(self, index):
parent = index.parent()
if parent == QtCore.QModelIndex():
parent_item = self.root_item
else:
parent_item = parent.internalPointer()
self.beginRemoveRows(parent, index.row(), index.row())
parent_item.removeChild(index.row())
self.endRemoveRows()
def rowCount(self, parent=QtCore.QModelIndex()):
if not parent.isValid():
return self.root_item.childrenCount()
return parent.internalPointer().childrenCount()
def setData(self, index, value, role=QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
index.internalPointer().setData( self.column(index.column()), value )
return True
return False
class Delegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None, setModelDataEvent=None):
super(Delegate, self).__init__(parent)
self.setModelDataEvent = setModelDataEvent
def createEditor(self, parent, option, index):
return QtWidgets.QLineEdit(parent)
def setEditorData(self, editor, index):
value = index.model().data(index, QtCore.Qt.DisplayRole)
editor.setText(str(value))
def setModelData(self, editor, model, index):
model.setData(index, editor.text())
if not self.setModelDataEvent is None:
self.setModelDataEvent()
mainwindow.py
## -*- coding: utf-8 -*-
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(400, 500)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setObjectName("pushButton")
self.horizontalLayout.addWidget(self.pushButton)
self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_2.setObjectName("pushButton_2")
self.horizontalLayout.addWidget(self.pushButton_2)
self.verticalLayout.addLayout(self.horizontalLayout)
self.treeView = QtWidgets.QTreeView(self.centralwidget)
self.treeView.setAlternatingRowColors(True)
self.treeView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.treeView.setObjectName("treeView")
self.verticalLayout.addWidget(self.treeView)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 400, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "Insert"))
self.pushButton_2.setText(_translate("MainWindow", "Delete"))
mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Insert</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTreeView" name="treeView">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>