PyQtでのMVC その2 (データ削除・ツールバー)
QTreeViewでの表示
その1ではQTableView
を使った表示をしました。このViewをQTreeView
に変えてみます。
QTreeView
は木構造のデータモデルを表示するのに適しますが、RDBのようなレコードを持つ構造にも適しています。
QTreeView
では、データがノード単位で選択されます。ここでは「ノード単位 = 行単位」と考えてください。
通常、QTreeView
では子ノードの表示を開閉するためのマーカー(三角形や[+]のようなグラフィック)がついていますが、
今回は子ノードの概念は無いので、描画しないように処理を追加します。Viewクラスの内容は以下のようになります。
class View(QTreeView): def __init__(self, parent=None): super(View, self).__init__(parent) self.setItemsExpandable(False) self.setIndentation(0) def drawBranches(self, painter, rect, index): return
このViewクラスの__init__()
では、開閉処理ができないようにsetItemsExpandable(False)
を呼び、
表示時のインデントも不要なのでsetIndentation(0)
も呼んでいます。また、drawBranches()
をオーバライドし、すぐにreturnさせることで、展開用のマーカーを描画しないようにしておきます。
ヘッダの内容を変える
現在ヘッダ情報はModelからは何も返していないので、デフォルトの列番号が表示されています。
列番号でなく、任意の値を表示するためには、ModelのクラスのheaderData()
を実装します。
headerData()
では、第4引数の値role
から、返すべきデータを判断します。
例えば、role
の値がQt.DisplayRole
である場合、データの内容を返すようにします。
詳細についてはこの記事の最後にあるサンプルに記載します。
※前回出てきたdata()
では第5引数がrole
に該当します。仕組みも前述のheaderData()
と同様です。
データの削除をする機能の追加
表示処理はひとまず終わらせて、次に実装が簡単なデータの削除機能を追加します。
データの削除機能
データを削除するためには、以下の手続が必要になります。
- Viewで削除したいデータを選ぶ
- 1.で選んだインデックスを調べる
- 2.で得たインデックスのデータをModelに削除してもらう
1. Viewで削除したいデータを選ぶ
Viewから削除したいデータを選択状態にするだけなので、特に処理を追加するような部分はありませんが、 使い勝手の向上のため、Viewのクラスの初期化で以下のコードを1行追加して一度に複数行選択できるようにします。
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
2. 1.で選んだインデックスを調べる
ViewのselectedIndexes()
を呼び、選択されているインデックスの値を全て返してもらいます。
返却される値はQModelIndex
のインスタンスのシーケンスになっており、それぞれ行と列が格納されています。
全ての値から、列の値が0のものを選んで、行の値を追加します。これで選択されている行の値が全て揃いました。
3. 2.で得たインデックスのデータをModelに削除してもらう
実際に行を削除をする処理なので、Modelに削除処理のメソッドを追加します。 ここで重要なのは、Viewがどのタイミングで表示を更新するかを判断するために、 Modelが管理しているデータが実際に削除される前に、データの削除位置をViewに伝える必要があります。 さらに、削除完了時に、削除が完了したということもViewに伝える必要があります。 まとめると以下のようになります。
- Viewに対する削除するデータ位置の通知: Modelの
beginRemoveRows()
を呼ぶ - 実際のデータ削除処理
- Viewに対する削除完了通知: Modelの
endRemoveRows()
を呼ぶ
実際の削除処理では、整合性を保つために行インデックスの数値の大きい順から削除します。
サンプル
ここまで説明した部分を実際に実装したものです。 削除を実行するためのボタンはツールバーを用意し、その上に設置しました。
複数選択できます。
削除ボタンを押すと選択部分が削除されます。
import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * class Model(QAbstractItemModel): headers = 'トッピング', 'うどん/そば', '温/冷' def __init__(self, parent=None): super(Model, self).__init__(parent) self.items = [ ['たぬき','そば','温'], ['きつね','うどん','温'], ['月見','うどん','冷'], ['天ぷら','そば','温'], ] def index(self, row, column, parent=QModelIndex()): return self.createIndex(row, column, None) def parent(self, child): return QModelIndex() def rowCount(self, parent=QModelIndex()): return len(self.items) def columnCount(self, parent=QModelIndex()): return len(self.headers) def data(self, index, role=Qt.DisplayRole): if role == Qt.DisplayRole: try: return self.items[index.row()][index.column()] except: return return def headerData(self, section, orientation, role=Qt.DisplayRole): if role != Qt.DisplayRole: return if orientation == Qt.Horizontal: return self.headers[section] def removeRows(self, rowIndexes): for row in sorted(rowIndexes, reverse=True): self.beginRemoveRows(QModelIndex(), row, row + 1) del self.items[row] self.endRemoveRows() class View(QTreeView): def __init__(self, parent=None): super(View, self).__init__(parent) self.setItemsExpandable(False) self.setIndentation(0) self.setSelectionMode(QAbstractItemView.ExtendedSelection) def drawBranches(self, painter, rect, index): return class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.view = View(self) self.model = Model(self) self.view.setModel(self.model) self.setCentralWidget(self.view) toolBar = QToolBar() delButton = QPushButton('削除') delButton.clicked.connect(self.removeItems) toolBar.addWidget(delButton) self.addToolBar(toolBar) def selectedRows(self): rows = [] for index in self.view.selectedIndexes(): if index.column() == 0: rows.append(index.row()) return rows def removeItems(self): self.model.removeRows(self.selectedRows()) def main(): app = QApplication(sys.argv) w = MainWindow() w.show() w.raise_() app.exec_() if __name__ == '__main__': main()