如何保存 PySide 树视图模型结构

Posted

技术标签:

【中文标题】如何保存 PySide 树视图模型结构【英文标题】:How to save PySide tree view model structure 【发布时间】:2013-12-23 13:47:49 【问题描述】:

这是其他 SO 问题 QTreeView with custom items 的链接,QTreeView 示例在哪里。

请,谁能在这个例子中解释一下,如何从树视图中保存树结构。

    我可以从中提取标签名称的 QAbstractItemModel 类以及我可以再次加载的结构吗?

    如果是这样,我如何访问节点?除了索引还有其他方法吗?

编辑(来自链接):

import sys
from PySide import QtGui, QtCore


#-------------------------------------------------------------------------------
# my test data
class Icon():
    def __init__(self, icon, tooltip):
        self.pixmap = QtGui.QPixmap(icon)
        self.tooltip = tooltip

#-------------------------------------------------------------------------------
# my test data
class MyData():
    def __init__(self, txt, parent=None):
        self.txt = txt
        self.tooltip = None
        self.parent = parent
        self.child = []
        self.icon = []
        self.index = None
        self.widget = None

    #---------------------------------------------------------------------------
    def position(self):
        position = 0
        if self.parent is not None:
            count = 0
            children = self.parent.child
            for child in children:
                if child == self:
                    position = count
                    break
                count += 1
        return position

    #---------------------------------------------------------------------------
    # test initialization
    @staticmethod
    def init():
        root = MyData("root")
        root.icon.append(Icon("icon.png", "ToolTip icon.png"))
        root.tooltip = "root tooltip"
        for i in range(0, 2):
            child1 = MyData("child %i" % (i), root)
            child1.icon.append(Icon("icon1.png", "ToolTip icon1.png"))
            child1.tooltip = "child1 tooltip"
            root.child.append(child1)
            for x in range(0, 2):
                child2 = MyData("child %i %i" % (i, x), child1)
                child2.icon.append(Icon("icon1.png", "ToolTip icon1.png"))
                child2.icon.append(Icon("icon2.png", "ToolTip icon2.png"))
                child2.tooltip = "child2 tooltip"
                child1.child.append(child2)

        return root

#-------------------------------------------------------------------------------
class TreeViewModel(QtCore.QAbstractItemModel):
    #---------------------------------------------------------------------------
    def __init__(self, tree):
        super(TreeViewModel, self).__init__()
        self.__tree = tree
        self.__current = tree
        self.__view = None

    #---------------------------------------------------------------------------
    def flags(self, index):
        flag = QtCore.Qt.ItemIsEnabled
        if index.isValid():
            flag |= QtCore.Qt.ItemIsSelectable \
                 | QtCore.Qt.ItemIsUserCheckable \
                 | QtCore.Qt.ItemIsEditable \
                 | QtCore.Qt.ItemIsDragEnabled \
                 | QtCore.Qt.ItemIsDropEnabled
        return flag

    #---------------------------------------------------------------------------
    def index(self, row, column, parent=QtCore.QModelIndex()):
        node = QtCore.QModelIndex()
        if parent.isValid():
            nodeS = parent.internalPointer()
            nodeX = nodeS.child[row]
            node = self.__createIndex(row, column, nodeX)
        else:
            node = self.__createIndex(row, column, self.__tree)
        return node

    #---------------------------------------------------------------------------
    def parent(self, index):
        node = QtCore.QModelIndex()
        if index.isValid():
            nodeS = index.internalPointer()
            parent = nodeS.parent
            if parent is not None:
                node = self.__createIndex(parent.position(), 0, parent)
        return node

    #---------------------------------------------------------------------------
    def rowCount(self, index=QtCore.QModelIndex()):
        count = 1
        node = index.internalPointer()
        if node is not None:
            count = len(node.child)
        return count

    #---------------------------------------------------------------------------
    def columnCount(self, index=QtCore.QModelIndex()):
        return 1

    #---------------------------------------------------------------------------
    def data(self, index, role=QtCore.Qt.DisplayRole):
        data = None
        return data

    #---------------------------------------------------------------------------
    def setView(self, view):
        self.__view = view

    #---------------------------------------------------------------------------
    def __createIndex(self, row, column, node):
        if node.index == None:
            index = self.createIndex(row, column, node)
            node.index = index
        if node.widget is None:
            node.widget = Widget(node)
            self.__view.setIndexWidget(index, node.widget)
        return node.index


#-------------------------------------------------------------------------------
class TreeView(QtGui.QTreeView):
    #---------------------------------------------------------------------------
    def __init__(self, model, parent=None):
        super(TreeView, self).__init__(parent)
        self.setModel(model)
        model.setView(self)
        root = model.index(0, 0)
        self.setCurrentIndex(root)
        self.setHeaderHidden(True)

    #---------------------------------------------------------------------------
    def keyPressEvent(self, event):
        k = event.key()
        if k == QtCore.Qt.Key_F2:
            self.__editMode()

        super(TreeView, self).keyPressEvent(event)

    #---------------------------------------------------------------------------
    def __editMode(self):
        index = self.currentIndex()
        node = index.internalPointer()
        node.widget.editMode(True, True)


#-------------------------------------------------------------------------------
class Label(QtGui.QLabel):
    #---------------------------------------------------------------------------
    def __init__(self, parent, text):
        super(Label, self).__init__(text)
        self.__parent = parent

    #---------------------------------------------------------------------------
    def mouseDoubleClickEvent(self, event):
        #print("mouseDoubleClickEvent")
        if self.__parent is not None:
            self.__parent.editMode(True, True)
        else:
            super(Label, self).mouseDoubleClickEvent(event)


#-------------------------------------------------------------------------------
class LineEdit(QtGui.QLineEdit):
    #---------------------------------------------------------------------------
    def __init__(self, parent, text):
        super(LineEdit, self).__init__(text)
        self.__parent = parent
        self.editingFinished.connect(self.__editingFinished)

    #---------------------------------------------------------------------------
    def keyPressEvent(self, event):
        k = event.key()
        if k == QtCore.Qt.Key_Escape:
            print("ESC 2")
            self.__editingFinished(False)
        super(LineEdit, self).keyPressEvent(event)

    #---------------------------------------------------------------------------
    def __editingFinished(self, bCopy=True):
        print("editingFinished")
        self.__parent.editMode(False, bCopy)

#-------------------------------------------------------------------------------
class Widget(QtGui.QWidget):
    #---------------------------------------------------------------------------
    def __init__(self, node):
        super(Widget, self).__init__()
        self.autoFillBackground()
        self.__node = node
        self.__bEditMode = False
        self.__txt = None
        self.__create(self.__node, self.__bEditMode)

    #---------------------------------------------------------------------------
    def __create(self, node, bEditMode):
        layout = QtGui.QHBoxLayout()
        for icon in node.icon:
            label = Label(None, node.txt)
            label.setPixmap(icon.pixmap)
            label.setToolTip("label tooltip %s %s" % (node.txt, icon.tooltip))
            layout.addWidget(label)

        self.__changeTxt(layout, node, bEditMode, False)
        self.setLayout(layout)

    #---------------------------------------------------------------------------
    def __changeTxt(self, layout, node, bEditMode, bCopy):
        if self.__txt is not None:
            if bCopy:
                node.txt = self.__txt.text()
            if isinstance(self.__txt, LineEdit):
                self.__txt.deselect()
            self.__txt.hide()
            layout.removeWidget(self.__txt)
            self.__txt = None

        if bEditMode:
            self.__txt = LineEdit(self, node.txt)
            self.__txt.setFrame(False)
            self.__txt.selectAll()
            QtCore.QTimer.singleShot(0, self.__txt, QtCore.SLOT('setFocus()'));
        else:
            self.__txt = Label(self, node.txt)
        self.__txt.setToolTip("Text tooltip %s %s" % (node.txt, node.tooltip))
        layout.addWidget(self.__txt, 1)

    #---------------------------------------------------------------------------
    def editMode(self, bEditMode, bCopy):
        if self.__bEditMode != bEditMode:
            self.__bEditMode = bEditMode
            layout = self.layout()
            self.__changeTxt(layout, self.__node, bEditMode, bCopy)

#-------------------------------------------------------------------------------
class MyTree(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MyTree, self).__init__(parent)

        data = MyData.init()
        frame = QtGui.QFrame();
        frame.setLayout( QtGui.QHBoxLayout() );

        treeViewModel = TreeViewModel(data)
        treeView = TreeView(treeViewModel)
        frame.layout().addWidget( treeView );

        self.setCentralWidget(frame)

#-------------------------------------------------------------------------------
def main():
    app = QtGui.QApplication(sys.argv)
    form = MyTree()
    form.show()
    app.exec_()

if __name__ == '__main__':
    main()

【问题讨论】:

【参考方案1】:

你的问题很不清楚,因为你没有说你希望树结构以什么形式保存。

假设您并不真正关心,并且 XML 是可以接受的,您可以查看 Qt 文档中的 Simple DOM Model Example。

Qt 文档中的大多数示例已移植到 PyQt,并提供了源代码,可以从 here 下载。简单 DOM 模型示例可以在 examples/itemviews 下找到。

编辑

我忽略了您正在使用 PySide。 PySide 的等效移植示例可以在here 或PySide source code 下的sources/pyside-examples/examples/itemviews 中找到。

更新

这是一个使用xml.etree 序列化树的简单示例:

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.importTree(xml)
        self.button = QtGui.QPushButton('Export', self)
        self.button.clicked[()].connect(self.exportTree)
        layout = QtGui.QVBoxLayout(self)
        layout.addWidget(self.tree)
        layout.addWidget(self.button)

    def importTree(self, xml):
        def build(item, root):
            for element in root.getchildren():
                child = QtGui.QTreeWidgetItem(
                    item, [element.attrib['text']])
                child.setFlags(
                    child.flags() | QtCore.Qt.ItemIsEditable)
                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)
                element = etree.SubElement(
                    root, 'node', text=child.text(0))
                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 text="Child (0)">
        <node text="Child (0)">
            <node text="Child (0)"/>
            <node text="Child (1)"/>
        </node>
        <node text="Child (1)">
            <node text="Child (0)"/>
            <node text="Child (1)"/>
        </node>
    </node>
    <node text="Child (1)">
        <node text="Child (0)">
            <node text="Child (0)"/>
            <node text="Child (1)"/>
        </node>
        <node text="Child (1)">
            <node text="Child (0)"/>
            <node text="Child (1)"/>
        </node>
    </node>
</root>
        """)
    window.setGeometry(800, 300, 300, 300)
    window.show()
    sys.exit(app.exec_())

【讨论】:

是的,试过这个例子,但我想编辑树项目名称,然后再次保存结构。请以我提到的这个为例,你会弄清楚我需要什么。谢谢。 @Alex。我真的没有看到您发布的代码的相关性。我建议您先编写一个基于QTreeWidget 的非常简单 示例(即没有自定义模型或时髦的编辑小部件)。首先,您需要的只是一个基本的递归函数,它遍历树并打印每个项目的文本(带有适当的缩进)。一旦你有了它,它应该很容易适应,比如说,将输出保存为 xml。 是的,我会轻松构建递归函数,但无法确定树的生长位置:( @Alex。你想要的是恰当命名的invisibleRootItem。无论如何,我在我的答案中添加了一个简单的示例,应该可以帮助您入门。

以上是关于如何保存 PySide 树视图模型结构的主要内容,如果未能解决你的问题,请参考以下文章

使用 PySide2 和 QTableView 如何使用 pandas 模型在表格视图中获得多个委托?

为树视图创建 Qt 模型

一个模型,两个不同的视图 - PySide

python 普通的PySide模型/视图

来自模型的 QML 树视图

Cakephp:使用`saveAssociated()`时如何将`parent_id`保存到基于树的模型中?