PyQt: saving custom QTreeWidgets using QDataStream

I recently spent some time on how to use QDataStream with QTreeWidget in PyQt. I have never found specific examples for this, and the pyqt documentation for QDataStream seems rather meager in general. So I thought that I would raise the question here, like a reed in case someone else on the line needs a hint. I will wait a bit if someone wants to jump in and take a picture, and I will send back a little on my own.

Question: In PyQt, how can I use QDataStream to save QTreeWidgetItems to a file as my own QT objects, and then read the file back to restore the tree structure exactly as it was saved?

Eric

+3
source share
3 answers

I will continue and show the method that I used. Hope I don't have an unfair advantage in understanding how I decided to solve my own problem :-)

If anyone has a cleaner or more Python game, please be prepared to attend. Thank!

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.restore_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 restore_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.restore_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_())
+1
source

In one of my other answers to a similar question, I wrote a simple demo that serializes in xml.

The same code can be easily adapted to work with QDataStream. I do not recommend this as a solution (there are probably dozens of different ways to achieve the same thing), but it at least provides a working example:

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("""\
<?xml version="1.0" ?>
<root>
    <node data="AAAAAQAAAAEAAAAJAAAAQwAB/////wAA
                AAAAAAAAAAEAAAAKAAAAAAYAUgBlAGQ=">
        <node data="AAAAAQAAAAEAAAAJAAAAQwAB//8AAP//
                    //8AAAAAAAEAAAAKAAAAAAgAQwB5AGEAbg==">
            <node data="AAAAAQAAAAEAAAAJAAAAQwAB//8AAICA
                        AAAAAAAAAAEAAAAKAAAAAAoARwByAGUAZQBu"/>
            <node data="AAAAAQAAAAEAAAAJAAAAQwAB//8AAAAA
                        //8AAAAAAAEAAAAKAAAAAAgAQgBsAHUAZQ=="/>
        </node>
        <node data="AAAAAQAAAAEAAAAJAAAAQwAB/////6Wl
                    AAAAAAAAAAEAAAAKAAAAAAwATwByAGEAbgBnAGU=">
            <node data="AAAAAQAAAAEAAAAJAAAAQwAB//+AgAAA
                        gIAAAAAAAAEAAAAKAAAAAAwAUAB1AHIAcABsAGU="/>
            <node data="AAAAAQAAAAEAAAAJAAAAQwAB//8AAICA
                        AAAAAAAAAAEAAAAKAAAAAAoARwByAGUAZQBu"/>
        </node>
    </node>
    <node data="AAAAAQAAAAEAAAAJAAAAQwAB//8AAP//
                //8AAAAAAAEAAAAKAAAAAAgAQwB5AGEAbg==">
        <node data="AAAAAQAAAAEAAAAJAAAAQwAB/////6Wl
                    AAAAAAAAAAEAAAAKAAAAAAwATwByAGEAbgBnAGU=">
            <node data="AAAAAQAAAAEAAAAJAAAAQwAB//8AAP//
                        //8AAAAAAAEAAAAKAAAAAAgAQwB5AGEAbg=="/>
            <node data="AAAAAQAAAAEAAAAJAAAAQwAB//+AgAAA
                        gIAAAAAAAAEAAAAKAAAAAAwAUAB1AHIAcABsAGU="/>
        </node>
        <node data="AAAAAQAAAAEAAAAJAAAAQwAB//8AAICA
                    AAAAAAAAAAEAAAAKAAAAAAoARwByAGUAZQBu">
            <node data="AAAAAQAAAAEAAAAJAAAAQwAB//8AAAAA
                        //8AAAAAAAEAAAAKAAAAAAgAQgBsAHUAZQ=="/>
            <node data="AAAAAQAAAAEAAAAJAAAAQwAB/////wAA
                        AAAAAAAAAAEAAAAKAAAAAAYAUgBlAGQ="/>
        </node>
    </node>
</root>
        """)
    window.setGeometry(800, 300, 300, 300)
    window.show()
    sys.exit(app.exec_())
+2
source

QTreeWidget is a red herring. What you save is a generic QAbstractItemModel ( treeWidget->model()) - after all, it QTreeWidgetis a view and has a built-in model. Now these model elements are just QVariants, and these are just Python types, but also fully supported QDataStream::operator<<. All you need to do is select a tree traversal (first depth, width or something else), as well as dump the elements and their depth in the tree into the stream. When you read the stream, there is enough information to restore the tree.

0
source

All Articles