PyQt5 QTreeViewのサンプル

Share on:

PyQt5でツリーを表示するときに使えるQTreeViewを使ってみました。

実装

QTreeViewでは親子関係のあるデータを表示できます。モデルで扱うデータはItemというクラスで、プロパティに単一の親parent_itemと子のリストchildrenを持たせて親子関係を実現しました。

1class Item(object):
2    def __init__(self, _parent=None):
3        self._dict = {}
4        self.parent_item = _parent
5        self.children = []

そのほか変わったところといえば、アイテムを追加したときにIDのデータに親の行数を再帰で取得するという処理を入れて、Insertボタンをクリックしてアイテムを追加したときの見た目がわかりやすくなるようにしています。

1        def recursion(item, _ids):
2            _ids.append( str(item.row()) )
3            if item == self.root_item:
4                return
5            return recursion(item.parent(), _ids)
6        
7        ids = []
8        recursion(item, ids)
9        item.setData( 'ID', '-'.join(ids[::-1][1:]) )

所感

QTableViewと比べると、モデルのindexやparentメソッドなどに親がいる場合や子がいる場合の処理などを加えなければいけないのでQTableView用に作成したモデルをそのまま使えない場合があるかもしれません。反対に、QTreeView用に作成したモデルはそのままQListViewやQTableViewでも使えると思います。したがって、QListView、QTableView、QTreeView に同じモデルを渡して同じ内容を表示したりもできます。

例えば部品表などの親子関係があるデータはエクセルで表を作ろうとすると見づらくなったりするので、QTreeViewを使うと見やすい表が作れるかもしれませんね。

ソースコード

main.py

 1import sys
 2from mainwindow import Ui_MainWindow
 3from treeview import Model, Delegate, Item
 4from PyQt5 import QtWidgets, QtCore
 5 
 6class MainWindow(QtWidgets.QMainWindow):
 7    def __init__(self, app):
 8        super().__init__()
 9 
10        self.ui = Ui_MainWindow()
11        self.ui.setupUi(self)
12 
13        self.model = Model(self)
14        self.model.addColumns(['ID', 'Name'])
15 
16        self.ui.treeView.setModel(self.model)
17        self.ui.treeView.setItemDelegate(Delegate())
18        self.ui.treeView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
19        self.ui.treeView.customContextMenuRequested.connect(self.contextMenu)
20        self.ui.pushButton.clicked.connect(self.insertRow)
21        self.ui.pushButton_2.clicked.connect(self.delItem)
22 
23    def contextMenu(self, point):
24        self.menu = QtWidgets.QMenu(self)
25        self.menu.addAction('Add', self.insertRow)
26        self.menu.addAction('Delete', self.delItem)
27        self.menu.exec_( self.focusWidget().mapToGlobal(point) )
28 
29    def insertRow(self):
30        indexes = self.ui.treeView.selectedIndexes()
31        
32        if len(indexes) == 0:
33            self.model.addItem()
34            return
35        
36        indexes2 = []
37        for index in indexes:
38            if not index.row() in [ index2.row() for index2 in indexes2 if index2.parent() == index.parent() ]:
39                indexes2.append(index)
40        
41        for index in indexes2:
42            self.model.addItem(index)
43
44    def delItem(self):
45        indexes = self.ui.treeView.selectedIndexes()
46        if len(indexes) == 0:
47            return
48
49        indexes2 = []
50        for index in indexes:
51            if not index.row() in [ index2.row() for index2 in indexes2 if index2.parent() == index.parent() ]:
52                indexes2.append(index)
53        
54        for index in indexes2:
55            self.model.removeItem(index)
56 
57def main():
58    app = QtWidgets.QApplication(sys.argv)
59    window = MainWindow(app)
60    window.show()
61    app.exec_()
62 
63if __name__ == '__main__':
64    main()

treeview.py

  1# -*- coding: utf-8 -*-
  2from PyQt5 import QtWidgets, QtCore
  3 
  4class Item(object):
  5    def __init__(self, _parent=None):
  6        self._dict = {}
  7        self.parent_item = _parent
  8        self.children = []
  9    
 10    def appendChild(self, item):
 11        self.children.append(item)
 12
 13    def data(self, column):
 14        if column in self._dict.keys():
 15            return self._dict[column]
 16        return ''
 17 
 18    def setData(self, column, data):
 19        self._dict[column] = data
 20    
 21    def child(self, row):
 22        return self.children[row]
 23
 24    def childrenCount(self):
 25        return len(self.children)
 26
 27    def parent(self):
 28        return self.parent_item
 29
 30    def removeChild(self, row):
 31        del self.children[row]
 32
 33    def row(self):
 34        if self.parent_item:
 35            return self.parent_item.children.index(self)
 36        return 0
 37
 38class Model(QtCore.QAbstractItemModel):
 39    def __init__(self, parent_=None):
 40        super(Model, self).__init__(parent_)
 41        self.root_item = Item()
 42        self.root_item.setData('ID', 'root item')
 43        self.columns = []
 44 
 45    def addColumns(self, columns, parent=QtCore.QModelIndex()):
 46        self.beginInsertColumns(parent, self.columnCount(), self.columnCount() + len(columns) - 1)
 47        self.columns.extend(columns)
 48        self.endInsertColumns()
 49 
 50    def addItem(self, parent=QtCore.QModelIndex()):
 51        if parent == QtCore.QModelIndex():
 52            parent_item = self.root_item
 53        else:
 54            parent_item = parent.internalPointer()
 55        item = Item(parent_item)
 56        row = parent_item.childrenCount()
 57        self.beginInsertRows(parent, row, row)
 58        parent_item.children.insert( row, item )
 59        self.endInsertRows()
 60        
 61        def recursion(item, _ids):
 62            _ids.append( str(item.row()) )
 63            if item == self.root_item:
 64                return
 65            return recursion(item.parent(), _ids)
 66        
 67        ids = []
 68        recursion(item, ids)
 69        item.setData( 'ID', '-'.join(ids[::-1][1:]) )
 70        
 71    def column(self, key):
 72        return self.columns[key]
 73 
 74    def columnCount(self, parent=QtCore.QModelIndex()):
 75        return len(self.columns)
 76 
 77    def data(self, index, role):
 78        if not index.isValid():
 79            return QtCore.QVariant()
 80        if role == QtCore.Qt.EditRole or role == QtCore.Qt.DisplayRole:
 81            return index.internalPointer().data( self.column(index.column()) )
 82        return QtCore.QVariant()
 83 
 84    def flags(self, index):
 85        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
 86 
 87    def headerData(self, i, orientation, role):
 88        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
 89            return self.columns[i]
 90        if orientation == QtCore.Qt.Vertical and role == QtCore.Qt.DisplayRole:
 91            return i + 1
 92 
 93    def index(self, row, column, parent):
 94        if not parent.isValid():
 95            parent_item = self.root_item
 96        else:
 97            parent_item = parent.internalPointer()
 98
 99        if parent_item.childrenCount() > 0:
100            return self.createIndex(row, column, parent_item.child(row))
101        return QtCore.QModelIndex()
102
103    def parent(self, index):
104        if not index.isValid():
105            return QtCore.QModelIndex()
106        child_item = index.internalPointer()
107        if not child_item:
108            return QtCore.QModelIndex()
109        parent_item = child_item.parent()
110        if parent_item == self.root_item:
111            return QtCore.QModelIndex()
112        return self.createIndex(parent_item.row(), 0, parent_item)
113        
114    def removeItem(self, index):
115        parent = index.parent()
116        if parent == QtCore.QModelIndex():
117            parent_item = self.root_item
118        else:
119            parent_item = parent.internalPointer()
120        self.beginRemoveRows(parent, index.row(), index.row())
121        parent_item.removeChild(index.row())
122        self.endRemoveRows()
123 
124    def rowCount(self, parent=QtCore.QModelIndex()):
125        if not parent.isValid():
126            return self.root_item.childrenCount()
127        return parent.internalPointer().childrenCount()
128
129    def setData(self, index, value, role=QtCore.Qt.EditRole):
130        if role == QtCore.Qt.EditRole:
131            index.internalPointer().setData( self.column(index.column()), value )
132            return True
133        return False
134 
135class Delegate(QtWidgets.QStyledItemDelegate):
136    def __init__(self, parent=None, setModelDataEvent=None):
137        super(Delegate, self).__init__(parent)
138        self.setModelDataEvent = setModelDataEvent
139 
140    def createEditor(self, parent, option, index):
141        return QtWidgets.QLineEdit(parent)
142 
143    def setEditorData(self, editor, index):
144        value = index.model().data(index, QtCore.Qt.DisplayRole)
145        editor.setText(str(value))
146 
147    def setModelData(self, editor, model, index):
148        model.setData(index, editor.text())
149        if not self.setModelDataEvent is None:
150            self.setModelDataEvent()

mainwindow.py

 1# -*- coding: utf-8 -*-
 2
 3from PyQt5 import QtCore, QtGui, QtWidgets
 4
 5class Ui_MainWindow(object):
 6    def setupUi(self, MainWindow):
 7        MainWindow.setObjectName("MainWindow")
 8        MainWindow.resize(400, 500)
 9        self.centralwidget = QtWidgets.QWidget(MainWindow)
10        self.centralwidget.setObjectName("centralwidget")
11        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
12        self.verticalLayout.setObjectName("verticalLayout")
13        self.horizontalLayout = QtWidgets.QHBoxLayout()
14        self.horizontalLayout.setObjectName("horizontalLayout")
15        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
16        self.pushButton.setObjectName("pushButton")
17        self.horizontalLayout.addWidget(self.pushButton)
18        self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
19        self.pushButton_2.setObjectName("pushButton_2")
20        self.horizontalLayout.addWidget(self.pushButton_2)
21        self.verticalLayout.addLayout(self.horizontalLayout)
22        self.treeView = QtWidgets.QTreeView(self.centralwidget)
23        self.treeView.setAlternatingRowColors(True)
24        self.treeView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
25        self.treeView.setObjectName("treeView")
26        self.verticalLayout.addWidget(self.treeView)
27        MainWindow.setCentralWidget(self.centralwidget)
28        self.menubar = QtWidgets.QMenuBar(MainWindow)
29        self.menubar.setGeometry(QtCore.QRect(0, 0, 400, 21))
30        self.menubar.setObjectName("menubar")
31        MainWindow.setMenuBar(self.menubar)
32        self.statusbar = QtWidgets.QStatusBar(MainWindow)
33        self.statusbar.setObjectName("statusbar")
34        MainWindow.setStatusBar(self.statusbar)
35
36        self.retranslateUi(MainWindow)
37        QtCore.QMetaObject.connectSlotsByName(MainWindow)
38
39    def retranslateUi(self, MainWindow):
40        _translate = QtCore.QCoreApplication.translate
41        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
42        self.pushButton.setText(_translate("MainWindow", "Insert"))
43        self.pushButton_2.setText(_translate("MainWindow", "Delete"))

mainwindow.ui

 1<?xml version="1.0" encoding="UTF-8"?>
 2<ui version="4.0">
 3 <class>MainWindow</class>
 4 <widget class="QMainWindow" name="MainWindow">
 5  <property name="geometry">
 6   <rect>
 7    <x>0</x>
 8    <y>0</y>
 9    <width>400</width>
10    <height>500</height>
11   </rect>
12  </property>
13  <property name="windowTitle">
14   <string>MainWindow</string>
15  </property>
16  <widget class="QWidget" name="centralwidget">
17   <layout class="QVBoxLayout" name="verticalLayout">
18    <item>
19     <layout class="QHBoxLayout" name="horizontalLayout">
20      <item>
21       <widget class="QPushButton" name="pushButton">
22        <property name="text">
23         <string>Insert</string>
24        </property>
25       </widget>
26      </item>
27      <item>
28       <widget class="QPushButton" name="pushButton_2">
29        <property name="text">
30         <string>Delete</string>
31        </property>
32       </widget>
33      </item>
34     </layout>
35    </item>
36    <item>
37     <widget class="QTreeView" name="treeView">
38      <property name="alternatingRowColors">
39       <bool>true</bool>
40      </property>
41      <property name="selectionMode">
42       <enum>QAbstractItemView::ExtendedSelection</enum>
43      </property>
44     </widget>
45    </item>
46   </layout>
47  </widget>
48  <widget class="QMenuBar" name="menubar">
49   <property name="geometry">
50    <rect>
51     <x>0</x>
52     <y>0</y>
53     <width>400</width>
54     <height>21</height>
55    </rect>
56   </property>
57  </widget>
58  <widget class="QStatusBar" name="statusbar"/>
59 </widget>
60 <resources/>
61 <connections/>
62</ui>

関連記事