QListViewの項目をDnDで移動する
QStandardItemModelならできたが、QAbstractListModelではできなかった。
成果物
コード
a.py
QStandardItemModel版。ドラッグ&ドロップできる。
#!/usr/bin/python3 # -*- coding: utf-8 -*- import sys, os, numpy, PIL, csv from PySide2 import QtCore, QtGui, QtWidgets from PIL import Image, ImagePalette, ImageQt, ImageSequence class Window(QtWidgets.QMainWindow): def __init__(self): super(self.__class__, self).__init__() self.setAcceptDrops(True) self.setWindowTitle("ListView item move for drag and drop") self.view = FrameListView() self.setCentralWidget(self.view) self.show() class FrameListView(QtWidgets.QListView): def __init__(self, parent=None): super(self.__class__, self).__init__(parent) self.model = MyStandardItemModel() self.setModel(self.model) self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) self.setDragEnabled(True) self.setAcceptDrops(True) self.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove) self.setDefaultDropAction(QtCore.Qt.MoveAction) class MyStandardItemModel(QtGui.QStandardItemModel): def __init__(self): super(self.__class__, self).__init__() for label in ['A', 'B', 'C', 'D', 'E', 'F', 'G']: item = QtGui.QStandardItem(label) item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled) self.appendRow(item) if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) window = Window() sys.exit(app.exec_())
b.py
QAbstractListModel版。ドラッグ&ドロップできない。
#!/usr/bin/python3 # -*- coding: utf-8 -*- import sys, os, numpy, PIL, csv from PySide2 import QtCore, QtGui, QtWidgets from PIL import Image, ImagePalette, ImageQt, ImageSequence class Window(QtWidgets.QMainWindow): def __init__(self): super(self.__class__, self).__init__() self.setAcceptDrops(True) self.setWindowTitle("ListView item move for drag and drop") self.widget = FrameListView() self.setCentralWidget(self.widget) self.show() class FrameListView(QtWidgets.QListView): def __init__(self, parent=None): super(self.__class__, self).__init__(parent) self.model = FrameListModel() self.setModel(self.model) self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) self.setDragEnabled(True) self.setAcceptDrops(True) self.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove) self.setDefaultDropAction(QtCore.Qt.MoveAction) class FrameListModel(QtCore.QAbstractListModel): def __init__(self, parent=None): super(self.__class__, self).__init__(parent) self.__datas = [] for label in ['A', 'B', 'C', 'D', 'E', 'F', 'G']: d = {}; d[QtCore.Qt.DisplayRole] = label; self.__datas.append(d) def rowCount(self, parent=QtCore.QModelIndex()): if parent.isValid(): return 0 return len(self.__datas) def data(self, index, role=QtCore.Qt.DisplayRole):# https://doc.qt.io/qtforpython/PySide2/QtCore/Qt.html if role == QtCore.Qt.DisplayRole: return self.__datas[index.row()][role] def appendRow(self, value=None): self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount()) d = {}; d[QtCore.Qt.DisplayRole] = value if value else 'Z'; self.__datas.append(d) self.endInsertRows() def supportedDropActions(self): return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction def flags(self, index): # http://www.walletfox.com/course/qtreorderablelist.php defaultFlags = super(self.__class__, self).flags(index) return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled | defaultFlags if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) window = Window() sys.exit(app.exec_())
参考
- QAbstractListModel
- QStandardItemModel
- QStandardItem
- http://epic-alfa.kavli.tudelft.nl/share/doc/qt4/html/model-view-dnd.html
問題
ユーザデータ連携できない
リスト項目に独自クラスインスタンスを持たせたい。QAbstractListModelならできる。だがQAbstractListModelはドラッグ&ドロップによる項目移動ができない。独自データを持たせつつDnDするにはどうしたらいいのか? 不明。
項目のアイコンのみ表示ができるか不明
ユーザデータを諦めてDnDすべくQStandardItemModelを使うことにしたとする。このとき、リスト項目としてアイコンのみ表示するようなことができるか不明。
現状コードの大幅な改修が必要かも?
現状のpixpeerはQAbstractListModelで実装している。これでDnDできるなら簡単だったのだが、もしできないなら、大幅な改修が必要になる。
継承クラスをQStandardItemModelに変更せねばならない。これに伴い、QStandardItemクラスを追加するようにせねばならない。そもそもそのクラスで独自データを持たせることができるかわからない。不可能なら自前実装してなんとかできるのか要調査。
以下のように一部追加・修正してみたが、やはりDnDできなかった。
class FrameListView(QtWidgets.QListView): def __init__(self, parent=None): self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) self.setDragEnabled(True) self.setAcceptDrops(True) self.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove) self.setDefaultDropAction(QtCore.Qt.MoveAction) #class FrameListModel(QtGui.QStandardItemModel): class FrameListModel(QtCore.QAbstractListModel): ... def supportedDropActions(self): return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction def flags(self, index): # http://www.walletfox.com/course/qtreorderablelist.php defaultFlags = super(self.__class__, self).flags(index) return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled | defaultFlags
FrameListModel
の継承クラスをQAbstractListModelからQStandardItemModelに変更した- リスト項目が表示されなくなった……
FrameListModel
にsupportedDropActions()
,flags()
を追加した- DnDできない……
ならば継承クラスをQStandardItemModelにして大幅改修すべきか? だが以下によるとQStandardItemModelでは独自データの実装ができないっぽい。
以下のいずれかが候補。絶対に現状のほうがマシ。
- DnDは諦める。現状コードのままで改修不要
- QStandardItemModel改修。独自データを持てないためフレームごとに
pixmap
やduration
を持てない。さらに大幅な改修が必要。頑張ればDnDに対応できるかも?
所感
時間をかけてググりまくったが、情報が少ない。たかがDnDによる順序入替くらい楽勝かと思いきや、実現できないかもしれない。あるいはこのために別の要件が実装できなくなってしまいそう。たとえばフレームごとのduration
など項目ごとに持つ独自データを持たせられなくなるかも。
ListViewの研究がもっと必要。最悪、DnDは実装しない。
いわせてもらえば、ListViewには不満があった。
- アイコンを拡大表示できない
- 背景白や余白が不要
まさか自前でリストっぽいUIを作るのもさらなる苦痛だし、とりあえず現状で妥協するか。
対象環境
- Raspbierry pi 4 Model B
- Raspbian buster 10.0 2019-09-26 ※
- bash 5.0.3(1)-release
- Qt 5.11
- Python 3.7.3
- PySide2
- Pillow 7.1.2
$ uname -a Linux raspberrypi 4.19.97-v7l+ #1294 SMP Thu Jan 30 13:21:14 GMT 2020 armv7l GNU/Linux