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

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

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

埃里克

【问题讨论】:

    标签: python qt python-2.7 pyqt pyqt4


    【解决方案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。
        猜你喜欢
        • 2012-02-16
        • 2023-02-09
        • 1970-01-01
        • 2012-02-08
        • 2015-04-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多