やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

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ならできる。だがQAbstractListModelはドラッグ&ドロップによる項目移動ができない。独自データを持たせつつDnDするにはどうしたらいいのか? 不明。

項目のアイコンのみ表示ができるか不明

 ユーザデータを諦めてDnDすべくQStandardItemModelを使うことにしたとする。このとき、リスト項目としてアイコンのみ表示するようなことができるか不明。

現状コードの大幅な改修が必要かも?

 現状のpixpeerQAbstractListModelで実装している。これで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に変更した
    • リスト項目が表示されなくなった……
  • FrameListModelsupportedDropActions(), flags()を追加した
    • DnDできない……

 ならば継承クラスをQStandardItemModelにして大幅改修すべきか? だが以下によるとQStandardItemModelでは独自データの実装ができないっぽい。

 以下のいずれかが候補。絶対に現状のほうがマシ。

  • DnDは諦める。現状コードのままで改修不要
  • QStandardItemModel改修。独自データを持てないためフレームごとにpixmapdurationを持てない。さらに大幅な改修が必要。頑張ればDnDに対応できるかも?

所感

 時間をかけてググりまくったが、情報が少ない。たかがDnDによる順序入替くらい楽勝かと思いきや、実現できないかもしれない。あるいはこのために別の要件が実装できなくなってしまいそう。たとえばフレームごとのdurationなど項目ごとに持つ独自データを持たせられなくなるかも。

 ListViewの研究がもっと必要。最悪、DnDは実装しない。

 いわせてもらえば、ListViewには不満があった。

  • アイコンを拡大表示できない
  • 背景白や余白が不要

 まさか自前でリストっぽいUIを作るのもさらなる苦痛だし、とりあえず現状で妥協するか。

対象環境

$ uname -a
Linux raspberrypi 4.19.97-v7l+ #1294 SMP Thu Jan 30 13:21:14 GMT 2020 armv7l GNU/Linux