使用Qt

时间:2018-04-17 21:36:29

标签: python model-view-controller tree pyqt pyqt5

我有一个我想建模的树数据结构。树由不同的项类型组成,比如TypeA,TypeB和TypeC。

TypeA只能有TypeA或TypeB的孩子。 TypeB只能拥有TypeB或TypeC的子级,而TypeC只能拥有TypeC的子级。

我希望将整个树保留在内部结构中,并且只提供TypeA / B / C的主题部分视图。此外,如果某些子树相同,我想压缩视图。

我认为我可以使用代理执行此操作,但我无法让它正常工作。到目前为止,这是我的代码:

import sys

from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.Qt import Qt, QVariant, QModelIndex
from PyQt5.QtCore import QAbstractItemModel, QIdentityProxyModel, pyqtSignal

class TreeItem():
    """
    Adapted for python from  http://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html
    """

    def __init__(self, data, parent=None):
        self._child_items = []
        self._item_data = data
        self._parent_item = parent
        if parent:
            parent.append_child(self)

    def __str__(self):
        return "%s: %s" % (self.__class__, self._item_data)

    def append_child(self, child):
        if not isinstance(child, TreeItem):
            raise ('Some exception todo')
        self._child_items.append(child)

    def get_child(self, item_data):
        """
        Check if a child of the passed item data exists. if so return it
        :param item_data:
        :return: The found child. None if matching child exists
        """
        child_item_data = [child._item_data for child in self._child_items]

        if item_data in child_item_data:
            idx = child_item_data.index(item_data)
            return self.child(idx)
        return None

    def child(self, row):
        """
        In the tree several directories, can be under the current one as children.
        :param row: Index of sub-directory to return
        :return: a sub-directory
        """
        return self._child_items[row]

    def child_count(self):
        return len(self._child_items)

    def column_count(self):
        """
        A PathTreeItem has exactly one column, the directory name
        :return: 1
        """
        return 1

    def data(self, column):
        """
        PathTreeItem has only one colum, returning its data
        :param column: can only be 0 for PathTreeItem
        :return: the path part / directory name
        """
        if column != 0:
            raise IndexError
        return self._item_data

    def row(self):
        """
        :return: Index in the parent tree item
        """
        if self._parent_item:
            return self._parent_item._child_items.index(self)
        return 0

    def parent_item(self):
        return self._parent_item



class ItemTypeA(TreeItem):
    pass

class ItemTypeB(TreeItem):
    pass

class ItemTypeC(TreeItem):
    pass


class InternalTreeModel(QAbstractItemModel):
    """
    Adapted for python from  http://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html
    """

    items_changed = pyqtSignal(QAbstractItemModel)

    def __init__(self, parent=None):
        super(InternalTreeModel, self).__init__(parent)
        self._root = ItemTypeA('root')

    def append_row(self, row):
        self._root.append_child(row)

    @property
    def root(self):
        return self._root

    def data(self, index: QModelIndex, role: int):
        if not index.isValid():
            return QVariant()

        if not role == Qt.DisplayRole:
            return QVariant()

        item = index.internalPointer()

        return item.data(index.column())

    def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()):
        if not self.hasIndex(row, column, parent):
            return QModelIndex()

        if not parent.isValid():
            parent_item = self._root
        else:
            parent_item = parent.internalPointer()

        child_item = parent_item.child(row)

        if child_item:
            return self.createIndex(row, column, child_item)
        else:
            return QModelIndex()

    def parent(self, child: QModelIndex):
        if not child.isValid():
            return QModelIndex()

        child_item = child.internalPointer()
        parent_item = child_item.parent_item()

        if parent_item == self._root:
            return QModelIndex()

        return self.createIndex(parent_item.row(), 0, parent_item)

    def rowCount(self, parent: QModelIndex = QModelIndex()):
        if parent.column() > 0:
            return 0

        if not parent.isValid():
            parent_item = self._root
        else:
            parent_item = parent.internalPointer()

        return parent_item.child_count()

    def columnCount(self, parent: QModelIndex = QModelIndex()):
        if parent.isValid():
            return parent.internalPointer().column_count()
        else:
            return self._root.column_count()


class TypeAModel(QIdentityProxyModel):
    def mapFromSource(self, sourceIndex: QModelIndex):
        proxy_index = super().mapFromSource(sourceIndex)
        if not proxy_index.isValid():
            return QModelIndex()
        tree_item = proxy_index.internalPointer()
        if not isinstance(tree_item, ItemTypeA):
            return QModelIndex()
        return proxy_index




class TypeBModel(QIdentityProxyModel):
    def mapToSource(self, proxy_index: QModelIndex):
        if not self.sourceModel() or not proxy_index.isValid():
            return QModelIndex()
        assert proxy_index.model() == self
        # tree_item = proxy_index.internalPointer()
        # print("proxy data: r:%d, c:%d, %s" % (proxy_index.row(), proxy_index.column(), tree_item.data(0)))

        return self.sourceModel().createIndex(proxy_index.row(), proxy_index.column(), proxy_index.internalPointer())

    def mapFromSource(self, source_index: QModelIndex):
        if not self.sourceModel() or not source_index.isValid():
            return QModelIndex()
        assert source_index.model() == self.sourceModel()
        tree_item = source_index.internalPointer()

        if not source_index.parent().isValid():
            # this is a top level item
            # map it to the first FileTreeItem in its tree
            child_item = source_index.child(0,0)
            parent = source_index
            while not isinstance(child_item.internalPointer(), ItemTypeB):
                if not child_item.isValid():
                    # no FileTreeItem found in this tree
                    return QModelIndex()
                # print(child_item.data(0))
                parent = child_item
                child_item = child_item.child(0,0)

            child_inst = child_item.internalPointer()
            parent_inst = parent.internalPointer()

            return self.createIndex(child_item.row(), child_item.column(), child_item.internalPointer())

            # return self.createIndex(source_index.row(), source_index.column(), child_item.internalPointer())

        tree_item = source_index.internalPointer()
        if not isinstance(tree_item, ItemTypeB):
            return QModelIndex()
        print(
            "direct map: col: %d, row: %d, %s" % (source_index.column(), source_index.row(), source_index.internalPointer().data(0)))

        return self.createIndex(source_index.row(), source_index.column(), source_index.internalPointer())

    pass


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)

    model = InternalTreeModel()
    a_items = ['Foo', 'Bar', 'Baz']
    b_items = ['red', 'green', 'blue']
    c_items = ['alpha', 'beta', 'gamma']
    for first in a_items:
        row1 = ItemTypeA("TypeA " + first, model.root)
        for second in a_items:
            row2 = ItemTypeA("TypeA " + second, row1)
            for third in a_items:
                row3 = ItemTypeA("TypeA " + third, row2)
                for fourth in b_items:
                    row4 = ItemTypeB("TypeB " + fourth, row3)
                    for fifth in b_items:
                        row5 = ItemTypeB("TypeB " + fifth, row4)
                        for sixth in c_items:
                            row6 = ItemTypeC("TypeC " + sixth, row5)
                            for seventh in c_items:
                                row7 = ItemTypeC("TypeC " + seventh, row6)

    proxy = TypeAModel()
    proxy.setSourceModel(model)

    proxy2 = TypeBModel()
    proxy2.setSourceModel(model)

    w = QtWidgets.QWidget()
    layout = QtWidgets.QHBoxLayout(w)
    view = QtWidgets.QTreeView()
    view.setModel(model)
    view.expandAll()
    # view.header().hide()
    layout.addWidget(view)
    view = QtWidgets.QTreeView()
    view.setModel(proxy)
    view.expandAll()
    layout.addWidget(view)
    view = QtWidgets.QTreeView()
    view.setModel(proxy2)
    view.expandAll()
    layout.addWidget(view)

    w.show()
    # view.reset()

    sys.exit(app.exec_())

My application

左侧显示完整的树。如果我让它正常工作,我会有这个内部,而不是向用户显示它。

在中间我只显示TypeA项目的树。这似乎工作正常。但是,即使视图不允许,叶子仍然显示为可扩展。

在右边我想显示TypeB的项目。但是,显示的树太多了。由于树木相同,我只想展示其中一棵。即视图/代理应该合并重复的树。

我的最终目标是能够从每个视图中选择项目,并在内部树中生成所有匹配项目的选择。首先必须让视图工作。

0 个答案:

没有答案