PyQt: Guardar QTreeWidgets nativos utilizando QDataStream

Recientemente dediqué un tiempo a aprender cómo usar un QDataStream con un QTreeWidget en PyQt. Nunca encontré ejemplos específicos para hacer exactamente esto, y la documentación de pyqt para QDataStream parece ser bastante escasa en general. Así que pensé que iba a publicar una pregunta aquí como un rastro de migas de pan en caso de que alguien más en la línea necesita una pista. Esperaré un poco en caso de que alguien quiera saltar y tomar una oportunidad, y publicaré un poco con mis propios esfuerzos.

La pregunta es: en PyQt, ¿cómo puedo usar un QDataStream para guardar QTreeWidgetItems en un archivo como objetos QT nativos, y luego leer el archivo para restaurar la estructura del árbol exactamente como se guardó?

Eric

En una de mis otras respuestas a una pregunta similar, escribí una demostración simple que se serializa a xml.

El mismo código se puede adaptar fácilmente para que funcione con QDataStream. Realmente no estoy recomendando esto como una solución (probablemente hay docenas de formas diferentes para lograr lo mismo), pero al menos proporciona un ejemplo funcional:

import sip sip.setapi('QString', 2) from xml.etree import cElementTree as etree from PyQt4 import QtGui, QtCore class Window(QtGui.QWidget): def __init__(self, xml): QtGui.QWidget.__init__(self) self.tree = QtGui.QTreeWidget(self) self.tree.header().hide() self.button = QtGui.QPushButton('Export', self) self.button.clicked[()].connect(self.exportTree) layout = QtGui.QVBoxLayout(self) layout.addWidget(self.tree) layout.addWidget(self.button) self._array = QtCore.QByteArray() self._buffer = QtCore.QBuffer(self._array, self) self._buffer.open(QtCore.QIODevice.ReadWrite) self._datastream = QtCore.QDataStream(self._buffer) self.importTree(xml) def importTree(self, xml): def build(item, root): for element in root.getchildren(): child = QtGui.QTreeWidgetItem(item) data = element.attrib['data'].encode('ascii') self._array.swap(self._array.fromBase64(data)) self._buffer.reset() self._datastream >> child build(child, element) item.setExpanded(True) root = etree.fromstring(xml) build(self.tree.invisibleRootItem(), root) def exportTree(self): def build(item, root): for row in range(item.childCount()): child = item.child(row) self._array.clear() self._buffer.reset() self._datastream << child data = self._array.toBase64().data().decode('ascii') element = etree.SubElement(root, 'node', data=data) build(child, element) root = etree.Element('root') build(self.tree.invisibleRootItem(), root) from xml.dom import minidom print(minidom.parseString(etree.tostring(root)).toprettyxml()) if __name__ == '__main__': import sys app = QtGui.QApplication(sys.argv) window = Window("""\                        """) window.setGeometry(800, 300, 300, 300) window.show() sys.exit(app.exec_()) 

Seguiré adelante y mostraré el método que utilicé. Con suerte no tengo una ventaja injusta al saber cómo quería que se resolviera mi propio problema 🙂

Si alguien tiene una versión más limpia o más pythonica de esto, le invitamos a hacer un seguimiento. ¡Gracias!

 import sys,os.path from PyQt4 import QtGui, QtCore class TreeExperiment(QtGui.QWidget): def __init__(self,parent=None): QtGui.QWidget.__init__(self,parent) self.tree=QtGui.QTreeWidget(self) # self.tree.setObjectName("treeWidget") # self.add_button=QtGui.QPushButton("Add", self) # Initialize a simple self.save_button=QtGui.QPushButton("Save", self) # form containing a gridlayout = QtGui.QGridLayout(self) # treeWidget, an gridlayout.addWidget(self.tree,1,0,1,9) # 'Add' button, and a gridlayout.addWidget(self.add_button,2,0,2,3) # 'Save' button gridlayout.addWidget(self.save_button,2,3,2,3) # self.tree.headerItem().setText(0,"Label") # if os.path.isfile('native_tree_save.qfile'): # First look for a previously saved tree. If found, define # it as a QFile named 'file', open it, and define a datastream # to read from it. # # Each tree node is saved to and read from the file in pairs: # first, the QTreeWidgetItem itself, then the number of children # the item has so that the tree structure can be re-created # # The first item is added directly as the root for simplicity, # and is sent to the function which begins the tree reconstruction file = QtCore.QFile('native_tree_save.qfile') file.open(QtCore.QIODevice.ReadOnly) datastream = QtCore.QDataStream(file) child=QtGui.QTreeWidgetItem(self.tree.invisibleRootItem()) child.read(datastream) num_childs=datastream.readUInt32() self.restre_item(datastream,child,num_childs) else: # Otherwise if this is the first use, create a root item new_item=QtGui.QTreeWidgetItem(self.tree) self.tree.setCurrentItem(self.tree.topLevelItem(0)) self.tree.currentItem().setText(0,'root') self.tree.setItemSelected(self.tree.topLevelItem(0),1) self.tree.setCurrentItem(self.tree.topLevelItem(0)) self.connect(self.add_button, QtCore.SIGNAL("clicked()"), self.add_item) self.connect(self.save_button, QtCore.SIGNAL("clicked()"), self.save_tree) self.added_item_count=0 def add_item(self): # Adds an item to whatever is selected self.added_item_count+=1 label=str(self.added_item_count) new_item=QtGui.QTreeWidgetItem(self.tree.currentItem()) new_item.setText(0,label) self.tree.setCurrentItem(new_item) def restre_item(self,datastream,item,num_childs): for i in range(0, num_childs): child=QtGui.QTreeWidgetItem(item) child.read(datastream) num_childs=datastream.readUInt32() self.restre_item(datastream,child,num_childs) def save_item(self,item,datastream): num_childs=item.childCount() for i in range(0,num_childs): child = item.child(i) child.write(datastream) num_childs=child.childCount() datastream.writeUInt32(num_childs) self.save_item(child,datastream) def save_tree(self): file = QtCore.QFile('native_tree_save.qfile') file.open(QtCore.QIODevice.WriteOnly) datastream = QtCore.QDataStream(file) self.save_item(self.tree.invisibleRootItem(),datastream) if __name__=='__main__': app = QtGui.QApplication(sys.argv) window = TreeExperiment() window.resize(200, 120) window.show() sys.exit(app.exec_()) 

El QTreeWidget es una pista falsa. Lo que está guardando es un QAbstractItemModel genérico ( treeWidget->model() ) – después de todo, un QTreeWidget es una vista y tiene un modelo incorporado. Ahora, los elementos de esos modelos son simplemente QVariant s, y esos son simplemente tipos de Python, pero también son totalmente compatibles con QDataStream::operator<< . Todo lo que necesita es elegir un recorrido del árbol (profundidad primero, ancho primero o algo más) y volcar los elementos y su profundidad en el árbol a la stream. Cuando lees la secuencia, es suficiente información para reconstruir el árbol.