Frage

Hier ist ein Code, den ich nach zwei Tagen TreeView/Model-Wahnsinn gefunden habe.Das Thema schien viel umfassender zu sein, als ich dachte.Ich kann kaum so viel Zeit damit verbringen, ein einzelnes Widget zu erstellen.Ohnehin.Die Drag-and-Drop-Funktionalität von TreeView-Elementen wurde aktiviert.Aber außer ein paar interessanten Ausdrucken gibt es nicht viel.Der Doppelklick auf einen Artikel ermöglicht es dem Benutzer, einen neuen Artikelnamen einzugeben, der nicht abgeholt wird.

EINEN TAG SPÄTER MIT EINEM ÜBERARBEITETEN CODE BEARBEITET.

Es ist jetzt ein zu 90 % funktionsfähiges Werkzeug.

Der Benutzer kann die TreeView-Elemente durch Ziehen und Ablegen, Erstellen/Duplizieren/Löschen und Umbenennen bearbeiten.Die TreeView-Elemente stellen die Verzeichnisse oder Ordner hierarchisch dar, bevor sie durch Klicken auf die Schaltfläche „Drucken“ auf einem Laufwerk erstellt werden (anstelle von os.makedirs() druckt das Tool weiterhin einfach jedes Verzeichnis als Zeichenfolge).
Ich würde sagen, dass ich mit dem Ergebnis ziemlich zufrieden bin.Dank an Hackyday und an alle, die geantwortet und mir bei meinen Fragen geholfen haben.

Ein paar letzte Wünsche...

Ein Wunsch Nummer 01:

  1. Ich wünschte, die PrintOut()-Methode würde eine elegantere, intelligentere Funktion verwenden, um die TreeView-Elemente zu durchlaufen und ein Wörterbuch zu erstellen, das an die make_dirs_from_dict()-Methode übergeben wird.

Ein Wunsch Nummer 02:

  1. Ich wünschte, das Löschen der Elemente wäre stabiler.Aus einem unbekannten Grund stürzt ein Tool beim dritten/vierten Klicken auf die Schaltfläche „Löschen“ ab.Bisher konnte ich das Problem nicht aufspüren.

Ein Wunsch Nummer 03:3.Ich wünsche allen das Beste und danke für eure Hilfe:

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_())
War es hilfreich?

Lösung

Ich bin mir nicht ganz sicher, was Sie erreichen möchten, aber es hört sich so an, als ob Sie das gezogene Element im Drop-Vorgang abrufen und per Doppelklick einen neuen Knotennamen speichern möchten.

Zunächst müssen Sie das gezogene Element im speichern mimeData.Derzeit speichern Sie nur die Zeichenfolge „mimeData“, was Ihnen nicht viel sagt.Der Mime Typ Die Zeichenfolge, unter der sie gespeichert wird (hier habe ich „bstream“ verwendet), kann eigentlich alles sein.Solange es mit dem übereinstimmt, was Sie zum Abrufen der Daten verwenden, und in der von zurückgegebenen Liste enthalten ist mimeTypes Methode des Modells.Um das Objekt selbst zu übergeben, müssen Sie es zunächst serialisieren (Sie können Ihr Objekt alternativ auch in XML konvertieren, falls Sie dies vorhaben), da es sich nicht um einen Standardtyp für MIME-Daten handelt.

Damit die von Ihnen eingegebenen Daten gespeichert werden, müssen Sie die setData-Methode des Modells erneut implementieren und das Verhalten dafür definieren EditRole.

Die relevanten Methoden:

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

BEARBEITEN:
Das ist eine Menge Code, den Sie aktualisiert haben, aber ich werde den von Ihnen hervorgehobenen Punkten nachkommen.Vermeiden Sie Anrufe createIndex außerhalb der Modellklasse.Dies ist eine geschützte Methode in Qt;Python erzwingt keine privaten/geschützten Variablen oder Methoden, aber wenn ich eine Bibliothek aus einer anderen Sprache verwende, die dies tut, versuche ich, die beabsichtigte Organisation der Klassen und den Zugriff darauf zu respektieren.

Der Zweck des Modells besteht darin, eine Schnittstelle zu Ihren Daten bereitzustellen.Sie sollten über Folgendes darauf zugreifen index, data, parent usw.öffentliche Funktionen des Modells.Um das übergeordnete Element eines bestimmten Index abzurufen, verwenden Sie den Index dieses Index (oder des Modells). parent Funktion, die auch einen QModelIndex zurückgibt.Auf diese Weise müssen Sie sich nicht mit der internen Struktur der Daten auseinandersetzen (oder diese überhaupt kennen).Das habe ich im gemacht deleteLevel Methode.

Aus dem qt Dokumente:

Um sicherzustellen, dass die Darstellung der Daten von der Art und Weise, wie darauf zugegriffen wird, getrennt bleibt, wird das Konzept eines Modellindex eingeführt.Jede Information, die über ein Modell gewonnen werden kann, wird durch einen Modellindex repräsentiert...Nur das Modell muss wissen, wie es Daten erhält, und die Art der vom Modell verwalteten Daten kann ziemlich allgemein definiert werden.

Sie können die Druckmethode auch mithilfe der Rekursion vereinfachen.

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())

Ich hatte das getrennt vom Unterricht

def dictify(node):
    kids = {}
    for child in node.children:
        kids.update(dictify(child)) 
    return {str(node.name): kids}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top