PYQT QabstracteMModelを使用してドラッグアンドドロップを適切に処理する方法
-
21-12-2019 - |
質問
TreeView / Model Madnessの2日後に終了したコードです。対象は私が思ったよりもはるかに広いように見えました。私は唯一の時間をそんなに費やすことができます歌のウィジェットを作成することができます。とにかく。 TreeView項目のドラッグアンドドロップ機能が有効になっています。しかし、興味深いプリントアウト以外にはあまりありません。アイテムをダブルクリックすると、ユーザーがピックアップされない新しいアイテム名を入力できます。
後で改訂されたコードで編集されました。
それは90%の機能ツールである。
ユーザは、ドラッグアンドドロップ、作成/複製/削除および名前変更によりツリービューアイテムを操作することができる。 TreeView項目は、「印刷」ボタンを押すことで(OS.makedirs()の代わりにドライブ上に作成される前に、ディレクトリまたはフォルダを階層的に表現しています(OS.Makedirs()ツールはまだ単に文字列として印刷します。
結果にかなり満足していると言うでしょう。 ハッキーデーと私の質問に答えた皆さんに感謝します。
最後の願い...
希望数01:
- 私はprintOut()メソッドがTreeViewアイテムをループするためによりエレガントなスマートな機能を使用して、make_dirs_from_dict()メソッドに渡されている辞書を構築することを望みます。
- 私はアイテムを削除することがより安定しています。未知の理由によって、ツールが3番目/ 4回目の削除ボタンをクリックするとクラッシュします。これまでのところ、問題を解決できませんでした。
ウィッシュ番号02:
願い番号03: 3.みんなが最高のものであり、あなたの助けを感謝します:
import sys, os
from PyQt4 import QtGui, QtCore
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from copy import deepcopy
import cPickle
class TreeItem(object):
def __init__(self, name, parent=None):
self.name = QtCore.QString(name)
self.parent = parent
self.children = []
self.setParent(parent)
def setParent(self, parent):
if parent != None:
self.parent = parent
self.parent.appendChild(self)
else: self.parent = None
def appendChild(self, child):
self.children.append(child)
def childAtRow(self, row):
if len(self.children)>row:
return self.children[row]
def rowOfChild(self, child):
for i, item in enumerate(self.children):
if item == child: return i
return -1
def removeChild(self, row):
value = self.children[row]
self.children.remove(value)
return True
def __len__(self):
return len(self.children)
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self):
QtCore.QAbstractItemModel.__init__(self)
self.columns = 1
self.clickedItem=None
self.root = TreeItem('root', None)
levelA = TreeItem('levelA', self.root)
levelB = TreeItem('levelB', levelA)
levelC1 = TreeItem('levelC1', levelB)
levelC2 = TreeItem('levelC2', levelB)
levelC3 = TreeItem('levelC3', levelB)
levelD = TreeItem('levelD', levelC3)
levelE = TreeItem('levelE', levelD)
levelF = TreeItem('levelF', levelE)
def nodeFromIndex(self, index):
return index.internalPointer() if index.isValid() else self.root
def index(self, row, column, parent):
node = self.nodeFromIndex(parent)
return self.createIndex(row, column, node.childAtRow(row))
def parent(self, child):
# print '\n parent(child)', child # PyQt4.QtCore.QModelIndex
if not child.isValid(): return QModelIndex()
node = self.nodeFromIndex(child)
if node is None: return QModelIndex()
parent = node.parent
if parent is None: return QModelIndex()
grandparent = parent.parent
if grandparent==None: return QModelIndex()
row = grandparent.rowOfChild(parent)
assert row != - 1
return self.createIndex(row, 0, parent)
def rowCount(self, parent):
node = self.nodeFromIndex(parent)
if node is None: return 0
return len(node)
def columnCount(self, parent):
return self.columns
def data(self, index, role):
if role == Qt.DecorationRole:
return QVariant()
if role == Qt.TextAlignmentRole:
return QVariant(int(Qt.AlignTop | Qt.AlignLeft))
if role != Qt.DisplayRole:
return QVariant()
node = self.nodeFromIndex(index)
if index.column() == 0:
return QVariant(node.name)
elif index.column() == 1:
return QVariant(node.state)
elif index.column() == 2:
return QVariant(node.description)
else: return QVariant()
def supportedDropActions(self):
return Qt.CopyAction | Qt.MoveAction
def flags(self, index):
defaultFlags = QAbstractItemModel.flags(self, index)
if index.isValid(): return Qt.ItemIsEditable | Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | defaultFlags
else: return Qt.ItemIsDropEnabled | defaultFlags
def setData(self, index, value, role):
if role == Qt.EditRole:
if value.toString() and len(value.toString())>0:
self.nodeFromIndex(index).name = value.toString()
self.dataChanged.emit(index, index)
return True
def mimeTypes(self):
return ['bstream', 'text/xml']
def mimeData(self, indexes):
mimedata = QtCore.QMimeData()
bstream = cPickle.dumps(self.nodeFromIndex(indexes[0]))
mimedata.setData('bstream', bstream)
return mimedata
def dropMimeData(self, mimedata, action, row, column, parentIndex):
if action == Qt.IgnoreAction: return True
droppedNode=cPickle.loads(str(mimedata.data('bstream')))
droppedIndex = self.createIndex(row, column, droppedNode)
parentNode = self.nodeFromIndex(parentIndex)
newNode = deepcopy(droppedNode)
newNode.setParent(parentNode)
self.insertRow(len(parentNode)-1, parentIndex)
self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), parentIndex, parentIndex)
return True
def insertRow(self, row, parent):
return self.insertRows(row, 1, parent)
def insertRows(self, row, count, parent):
self.beginInsertRows(parent, row, (row + (count - 1)))
self.endInsertRows()
return True
def removeRow(self, row, parentIndex):
return self.removeRows(row, 1, parentIndex)
def removeRows(self, row, count, parentIndex):
self.beginRemoveRows(parentIndex, row, row)
node = self.nodeFromIndex(parentIndex)
node.removeChild(row)
self.endRemoveRows()
return True
class GUI(QtGui.QDialog):
def build(self, myWindow):
myWindow.resize(600, 400)
self.myWidget = QWidget(myWindow)
self.boxLayout = QtGui.QVBoxLayout(self.myWidget)
self.treeView = QtGui.QTreeView()
self.treeModel = TreeModel()
self.treeView.setModel(self.treeModel)
self.treeView.expandAll()
self.treeView.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
self.treeView.connect(self.treeView.model(), SIGNAL("dataChanged(QModelIndex,QModelIndex)"), self.onDataChanged)
QtCore.QObject.connect(self.treeView, QtCore.SIGNAL("clicked (QModelIndex)"), self.treeItemClicked)
self.boxLayout.addWidget(self.treeView)
self.PrintButton= QtGui.QPushButton("Print")
self.PrintButton.clicked.connect(self.PrintOut)
self.boxLayout.addWidget(self.PrintButton)
self.DeleteButton= QtGui.QPushButton("Delete")
self.DeleteButton.clicked.connect(self.DeleteLevel)
self.boxLayout.addWidget(self.DeleteButton)
self.insertButton= QtGui.QPushButton("Insert")
self.insertButton.clicked.connect(self.insertLevel)
self.boxLayout.addWidget(self.insertButton)
self.duplicateButton= QtGui.QPushButton("Duplicate")
self.duplicateButton.clicked.connect(self.duplicateLevel)
self.boxLayout.addWidget(self.duplicateButton)
myWindow.setCentralWidget(self.myWidget)
def make_dirs_from_dict(self, dirDict, current_dir='/'):
for key, val in dirDict.items():
#os.mkdir(os.path.join(current_dir, key))
print "\t\t Creating directory: ", os.path.join(current_dir, key)
if type(val) == dict:
self.make_dirs_from_dict(val, os.path.join(current_dir, key))
def PrintOut(self):
result_dict = {}
for a1 in self.treeView.model().root.children:
result_dict[str(a1.name)]={}
for a2 in a1.children:
result_dict[str(a1.name)][str(a2.name)]={}
for a3 in a2.children:
result_dict[str(a1.name)][str(a2.name)][str(a3.name)]={}
for a4 in a3.children:
result_dict[ str(a1.name)][str(a2.name)][str(a3.name)][str(a4.name)]={}
for a5 in a4.children:
result_dict[ str(a1.name)][str(a2.name)][str(a3.name)][str(a4.name)][str(a5.name)]={}
for a6 in a5.children:
result_dict[str(a1.name)][str(a2.name)][str(a3.name)][str(a4.name)][str(a5.name)][str(a6.name)]={}
for a7 in a6.children:
result_dict[str(a1.name)][str(a2.name)][str(a3.name)][str(a4.name)][str(a5.name)][str(a6.name)][str(a7.name)]={}
self.make_dirs_from_dict(result_dict)
def DeleteLevel(self):
if len(self.treeView.selectedIndexes())==0: return
currentIndex = self.treeView.selectedIndexes()[0]
currentRow=currentIndex.row()
currentColumn=currentIndex.column()
currentNode = currentIndex.internalPointer()
parentNode = currentNode.parent
parentIndex = self.treeView.model().createIndex(currentRow, currentColumn, parentNode)
print '\n\t\t\t CurrentNode:', currentNode.name, ', ParentNode:', currentNode.parent.name, ', currentColumn:', currentColumn, ', currentRow:', currentRow
# self.treeView.model().removeRow(len(parentNode)-1, parentIndex)
self.treeView.model().removeRows(currentRow, 1, parentIndex )
#self.treeView.model().removeRow(len(parentNode), parentIndex)
#self.treeView.model().emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), parentIndex, parentIndex)
def insertLevel(self):
if len(self.treeView.selectedIndexes())==0: return
currentIndex = self.treeView.selectedIndexes()[0]
currentNode = currentIndex.internalPointer()
newItem = TreeItem('Brand New', currentNode)
self.treeView.model().insertRow(len(currentNode)-1, currentIndex)
self.treeView.model().emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), currentIndex, currentIndex)
def duplicateLevel(self):
if len(self.treeView.selectedIndexes())==0: return
currentIndex = self.treeView.selectedIndexes()[0]
currentRow=currentIndex.row()
currentColumn=currentIndex.column()
currentNode=currentIndex.internalPointer()
parentNode=currentNode.parent
parentIndex=self.treeView.model().createIndex(currentRow, currentColumn, parentNode)
parentRow=parentIndex.row()
parentColumn=parentIndex.column()
newNode = deepcopy(currentNode)
newNode.setParent(parentNode)
self.treeView.model().insertRow(len(parentNode)-1, parentIndex)
self.treeView.model().emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), parentIndex, parentIndex)
print '\n\t\t\t CurrentNode:', currentNode.name, ', ParentNode:', parentNode.name, ', currentColumn:', currentColumn, ', currentRow:', currentRow, ', parentColumn:', parentColumn, ', parentRow:', parentRow
self.treeView.update()
self.treeView.expandAll()
def treeItemClicked(self, index):
print "\n clicked item ----------->", index.internalPointer().name
def onDataChanged(self, indexA, indexB):
print "\n onDataChanged NEVER TRIGGERED! ####################### \n ", index.internalPointer().name
self.treeView.update(indexA)
self.treeView.expandAll()
self.treeView.expanded()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
myWindow = QMainWindow()
myGui = GUI()
myGui.build(myWindow)
myWindow.show()
sys.exit(app.exec_())
.解決
あなたが達成しようとしていることを完全に確信していませんが、ドロップ操作でドラッグしたアイテムを取得したいように聞こえ、ダブルクリックして新しいノード名を保存してください。
最初に、ドラッグした項目をmimeData
に保存する必要があります。現在、あなたは「Mimedata」という文字列を保存しています。 mimeType 文字列(ここでは、私は 'bstream'を使用した ')を実際には何でもすることができます。データを取得するために使用するものと一致する限り、モデルのmimeTypes
メソッドによって返されるリストにあります。オブジェクト自体を渡すには、まずそれをシリアル化する必要があります(オブジェクトを実行しているものがある場合はXMLに変換することもできます)。
保存するデータを保存するには、モデルのSetDataメソッドを再実装し、EditRole
の動作を定義する必要があります。
関連方法:
def setData(self, index, value, role):
if role == Qt.EditRole:
self.nodeFromIndex(index).name = value
self.dataChanged.emit(index, index)
return True
def mimeTypes(self):
return ['bstream', 'text/xml']
def mimeData(self, indexes):
mimedata = QtCore.QMimeData()
# assuming single dragged item ...
# only pass the node name
# mimedata.setData('text/xml', str(self.nodeFromIndex(indexes[0]).name))
# pass the entire object
bstream = cPickle.dumps(self.nodeFromIndex(indexes[0]))
mimedata.setData('bstream', bstream)
return mimedata
def dropMimeData(self, mimedata, action, row, column, parentIndex):
if action == Qt.IgnoreAction: return True
parentNode = self.nodeFromIndex(parentIndex)
# data = mimedata.data('text/xml')
data = cPickle.loads(str(mimedata.data('bstream')))
print '\n\t incoming row number:', row, ', incoming column:', column, \
', action:', action, ' mimedata: ', data.name
print "\n\t Item's name on which drop occurred: ", parentNode.name, \
', number of its childred:', len(parentNode.children)
if len(parentNode.children)>0: print '\n\t zero indexed child:', parentNode.children[0].name
return True
.
編集:
それはあなたが更新したコードがたくさんありますが、私はあなたが強調したポイントを義務付けます。モデルクラスの外部でcreateIndex
を呼び出しないようにします。これはQTの保護された方法です。 Pythonはプライベート/プロテクト変数またはメソッドを強制しませんが、他の言語からライブラリを使用するときは、そのクラスの意図された組織を尊重し、それらにアクセスします。
モデルの目的はあなたのデータへのインターフェースを提供することです。 index
、data
、parent
などを使用してアクセスする必要があります。特定のインデックスの親を取得するには、そのインデックス(またはモデルの)parent
関数を使用します。これはQModelIndexも返します。このようにして、データの内部構造を通過する(または確かに知っている)必要はありません。これはdeleteLevel
メソッドで行ったことです。
からQt docs :
データの表現がアクセスされる方法とは別に保持されるようにするために、モデルインデックスの概念が導入されます。モデルを介して取得できる各情報はモデルインデックスで表されます。モデルのみを知る必要があり、モデルによって管理されるデータの種類は一般的に定義できます。
また、再帰を使用して印刷方法を簡素化できます。
def printOut(self):
result_dict = dictify(self.treeView.model().root)
self.make_dirs_from_dict(result_dict)
def deleteLevel(self):
if len(self.treeView.selectedIndexes()) == 0:
return
currentIndex = self.treeView.selectedIndexes()[0]
self.treeView.model().removeRow(currentIndex.row(), currentIndex.parent())
.
私はクラス
とは別に別々でしたdef dictify(node):
kids = {}
for child in node.children:
kids.update(dictify(child))
return {str(node.name): kids}
.