PyQt:使用 QDataStream 保存本机 QTreeWidget

Posted

技术标签:

【中文标题】PyQt:使用 QDataStream 保存本机 QTreeWidget【英文标题】:PyQt: Saving native QTreeWidgets using QDataStream 【发布时间】:2014-03-06 08:40:59 【问题描述】:

我最近花了一些时间研究如何在 PyQt 中使用 QDataStream 和 QTreeWidget。我从来没有找到具体的例子来做到这一点,而且 QDataStream 的 pyqt 文档通常似乎非常稀缺。所以我想我会在这里发布一个问题作为面包屑路径,以防其他人需要提示。我会稍等片刻,以防有人想跳进去试一试,我会尽力回复。

问题是:在 PyQt 中,如何使用 QDataStream 将 QTreeWidgetItems 作为原生 QT 对象保存到文件中,然后将文件读回以完全恢复保存时的树结构?

埃里克

【问题讨论】:

【参考方案1】:

在one of my other answers 的一个类似问题中,我写了一个简单的demo,可以序列化为xml。

相同的代码可以很容易地适应 QDataStream。我并不是真的推荐它作为解决方案(可能有几十种不同的方法可以实现相同的目标),但它至少提供了一个可行的示例:

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】:

我会继续展示我使用的方法。希望我在知道自己的问题如何解决方面没有不公平的优势:-)

如果有人对此有更清晰或更 Pythonic 的看法,欢迎跟进。谢谢!

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

【讨论】:

【参考方案3】:

QTreeWidget 是一个红鲱鱼。您要保存的是通用 QAbstractItemModel (treeWidget-&gt;model()) - 毕竟,QTreeWidget 是一个视图,并且具有内置模型。现在,这些模型的项目只是QVariants,这些只是 Python 类型,但也完全受到 QDataStream::operator&lt;&lt; 的支持。您只需要选择一个树遍历(深度优先、广度优先或其他),然后将树中的项目及其深度转储到流中。当您读取流时,这些信息足以重建树。

【讨论】:

谢谢——我同意,这是对解决方案的一个很好的描述,希望我在谈论 treeWidget 而不是它的项目时没有欺骗。我得出的解决方案完全正确,它依赖于存储项目并使用它们来填充新 treeWidget 的结构。你想提供几行python代码吗?我看到你在用 C++ 说话,但我无法让这些运算符工作。使用 QDataStream 功能是我花了一段时间才理解的技巧之一。 @EricMyers 明天会做,我偶尔会使用 Python。

以上是关于PyQt:使用 QDataStream 保存本机 QTreeWidget的主要内容,如果未能解决你的问题,请参考以下文章

pyQT4本机文件对话框记住最后一个目录

PyQt4 - 如何禁用所有热键(本机键盘快捷键)

如何从 QDataStream 中读取数组

如何使用 QDataStream::readBytes()

qt中用Qdatastream写.dat二进制文件,写入完毕后,打开.dat文件看里面都不是二进制

使用带有 QDataStream 的自定义 QMetaType