как показать правильное количество столбцов в QTreeView для модели с различным иерархическим количеством столбцов - PullRequest
0 голосов
/ 09 ноября 2018

В приведенном ниже примере кода я заполняю модель элемента набором элементов верхнего уровня, которые содержат свойства значения ключа, которые я могу просматривать и редактировать с помощью QTreeView.

Глядя на документацию Qt для QAbstractItemModel :: columnCount , он говорит, что это должно вернуть число столбцов для дочерних элементов данного родителя, что означает, что это должно быть иерархически зависимое свойство.

Однако, используя приведенный ниже код, если я верну количество столбцов в качестве иерархически зависимого свойства (в данном случае root->children имеет 1 столбец, root->child->children имеет 2 столбца), то представление будет отображать только 1 столбец.

enter image description here

Печать node.columnCount() (см. Код) фактически покажет, что узлы класса Item действительно возвращают columnCount = 2 после развертывания одного из элементов.

Если я просто всегда возвращаю 2 для функции model.columnCount, то представление будет правильно отображать оба столбца.

enter image description here

Требуется ли это, чтобы всегда возвращать желаемое количество столбцов в представлении, независимо от иерархии, или я просто что-то делаю неправильно, и если да, то что? Возвращая количество столбцов для родителя, чьи дети имеют разное количество столбцов, просто чтобы заставить представление работать должным образом, кажется, что это должно быть неправильно.

import sys
import typing
from PyQt5 import QtCore, QtWidgets


class Node:
    def __init__(self, parent=None):
        self.parent = parent  # type: Node
        self.name : str

    def children(self) -> list:
        return None

    def hasChildren(self):
        return bool(self.children())

    def getData(self, index: QtCore.QModelIndex):
        if index.column() == 0:
            return self.name

    def setData(self, val, index: QtCore.QModelIndex):
        if index.column() == 0:
            self.name = val

    def columnCount(self):
        return 1

    def rowCount(self):
        children = self.children()
        return 0 if not children else len(children)

    def flags(self, index: QtCore.QModelIndex):
        if index.column() == 0:
            return (QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable)
        else:
            return QtCore.Qt.NoItemFlags


class Property(Node):
    def __init__(self, parent, label, value):
        super().__init__(parent)
        self.label = label
        self.value = value

    def getData(self, index: QtCore.QModelIndex):
        col = index.column()
        if col == 0:
            return self.label
        elif col == 1:
            return self.value

    def setData(self, val, index: QtCore.QModelIndex):
        if index.column() == 1:
            self.value = val

    def flags(self, index: QtCore.QModelIndex):
        col = index.column()
        if col == 0:
            return QtCore.Qt.ItemIsEnabled
        elif col == 1:
            return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEditable


class Item(Node):
    def __init__(self, parent):
        super().__init__(parent)
        self.name = 'Item'
        self.p1 = Property(self, 'string', 'text')
        self.p2 = Property(self, 'float', 1.2)

    def children(self):
        return [self.p1, self.p2]

    def columnCount(self):
        return 2


class Root(Node):
    def __init__(self):
        super().__init__(parent=None)
        self._children = list()

    def children(self):
        return self._children


class Model(QtCore.QAbstractItemModel):
    def __init__(self):
        super().__init__()
        self.root = Root()

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

        node = parent.internalPointer() if parent.isValid() else self.root
        if node.children:
            return self.createIndex(row, column, node.children()[row])
        else:
            return QtCore.QModelIndex()

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

        node = child.internalPointer()  # type: Node

        if node.parent and node.parent.parent:
            row = node.parent.parent.children().index(node.parent)
            return self.createIndex(row, 0, node.parent)
        else:
            return QtCore.QModelIndex()

    def rowCount(self, parent: QtCore.QModelIndex = ...) -> int:
        node = parent.internalPointer() if parent.isValid() else self.root
        children = node.children()
        return len(children) if children else 0

    def columnCount(self, parent: QtCore.QModelIndex = ...) -> int:
        node = parent.internalPointer() if parent.isValid() else self.root
        print(f'{node.__class__.__name__} column count: ', node.columnCount())  # shows that column count 2 is returned, when items are expanded
        # return 2  # 2nd column only shows up if I just always return 2
        return node.columnCount()  # view only shows 1 columns

    def hasChildren(self, parent: QtCore.QModelIndex = ...) -> bool:
        node = parent.internalPointer() if parent.isValid() else self.root
        return node.hasChildren()

    def data(self, index: QtCore.QModelIndex, role: int = ...):
        if index.isValid() and role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
            node = index.internalPointer()  # type: Node
            return node.getData(index)
        else:
            return None

    def setData(self, index: QtCore.QModelIndex, value: typing.Any, role: int = ...) -> bool:
        if role in (QtCore.Qt.EditRole,):
            node = index.internalPointer()  # type: Node
            node.setData(value, index)
            self.dataChanged.emit(index, index)
            return True
        else:
            return False

    def flags(self, index: QtCore.QModelIndex):
        node = index.internalPointer() if index.isValid() else self.root
        return node.flags(index)

    def appendRow(self, item):
        row = len(self.root.children())
        self.beginInsertRows(QtCore.QModelIndex(), row, row)
        self.root.children().append(item)
        self.endInsertRows()


class TreeView(QtWidgets.QTreeView):
    def __init__(self, parent=None):
        super(TreeView, self).__init__(parent)
        self._model = Model()
        self.setModel(self._model)
        self.setSelectionMode(self.ExtendedSelection)
        # self.setDropIndicatorShown(False)
        self.setEditTriggers(self.DoubleClicked | self.SelectedClicked | self.EditKeyPressed)

    def model(self) -> Model:
        return self._model

sys.excepthook = sys.__excepthook__
app = QtWidgets.QApplication(sys.argv)
widget = TreeView()
model = widget.model()
for i in range(2):
    model.appendRow(Item(model.root))
widget.show()
widget.setAttribute(QtCore.Qt.WA_DeleteOnClose)
sys.exit(app.exec_())

Ответы [ 2 ]

0 голосов
/ 09 ноября 2018

Как ответил @eyllanesc, на число столбцов в представлении влияет только корневой элемент. Однако columnCount() будет влиять на строки, которые дают число, которое меньше этого числа, в тех столбцах, которые меньше этого числа, заполняться не будут.

Принимая вышеуказанный код:

class Root(Node):
    ...
    def columnCount():
        return 2

class Property(Node):
    ...
    def columnCount():
        return 1

В этом случае, даже если отображаются два столбца, узлы Property не будут отображать данные для второго столбца, поскольку представление columnCount() сообщает, что существует только 1 столбец

0 голосов
/ 09 ноября 2018

Кажется, что документы не ясны и не совсем соответствуют реализации, в реализации число столбцов в представлении зависит от горизонтального QHeaderView и горизонтального QHeaderView использует количество столбцов корня, являющегося невидимым элементом, то есть количество столбцов должно быть задано Root(), а поскольку Root() не перезаписывает columnCount(), оно будет иметь значение по умолчанию 1 (хотя для меня columnCount() узла должно быть 0, а children() должно возвращать пустой список), поэтому в Root columnCount().

для решения установлено значение 2.
import sys
import typing
from PyQt5 import QtCore, QtWidgets


class Node:
    def __init__(self, parent=None):
        self.parent = parent  # type: Node
        self.name : str

    def children(self) -> list:
        return list()

    def hasChildren(self):
        return bool(self.children())

    def getData(self, index: QtCore.QModelIndex):
        if index.column() == 0:
            return self.name

    def setData(self, val, index: QtCore.QModelIndex):
        if index.column() == 0:
            self.name = val

    def columnCount(self):
        return 0

    def rowCount(self):
        children = self.children()
        return len(children)

    def flags(self, index: QtCore.QModelIndex):
        if index.column() == 0:
            return (QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable)
        else:
            return QtCore.Qt.NoItemFlags


class Property(Node):
    def __init__(self, parent, label, value):
        super().__init__(parent)
        self.label = label
        self.value = value

    def getData(self, index: QtCore.QModelIndex):
        col = index.column()
        if col == 0:
            return self.label
        elif col == 1:
            return self.value

    def setData(self, val, index: QtCore.QModelIndex):
        if index.column() == 1:
            self.value = val

    def flags(self, index: QtCore.QModelIndex):
        col = index.column()
        if col == 0:
            return QtCore.Qt.ItemIsEnabled
        elif col == 1:
            return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEditable

    def columnCount(self):
        return 1


class Item(Node):
    def __init__(self, parent):
        super().__init__(parent)
        self.name = 'Item'
        self.p1 = Property(self, 'string', 'text')
        self.p2 = Property(self, 'float', 1.2)

    def children(self):
        return [self.p1, self.p2]

    def columnCount(self):
        return 2


class Root(Node):
    def __init__(self):
        super().__init__(parent=None)
        self._children = list()

    def children(self):
        return self._children

    def columnCount(self):
        return 2


class Model(QtCore.QAbstractItemModel):
    def __init__(self):
        super().__init__()
        self.root = Root()

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

        node = parent.internalPointer() if parent.isValid() else self.root
        if node.children:
            return self.createIndex(row, column, node.children()[row])
        else:
            return QtCore.QModelIndex()

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

        node = child.internalPointer()  # type: Node

        if node.parent and node.parent.parent:
            row = node.parent.parent.children().index(node.parent)
            return self.createIndex(row, 0, node.parent)
        else:
            return QtCore.QModelIndex()

    def rowCount(self, parent: QtCore.QModelIndex = ...) -> int:
        node = parent.internalPointer() if parent.isValid() else self.root
        children = node.children()
        return len(children) if children else 0

    def columnCount(self, parent: QtCore.QModelIndex = ...) -> int:
        node = parent.internalPointer() if parent.isValid() else self.root
        print(f'{node.__class__.__name__} column count: ', node.columnCount())  # shows that column count 2 is returned, when items are expanded
        # return 2  # 2nd column only shows up if I just always return 2
        return node.columnCount()  # view only shows 1 columns

    def hasChildren(self, parent: QtCore.QModelIndex = ...) -> bool:
        node = parent.internalPointer() if parent.isValid() else self.root
        return node.hasChildren()

    def data(self, index: QtCore.QModelIndex, role: int = ...):
        if index.isValid() and role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
            node = index.internalPointer()  # type: Node
            return node.getData(index)
        else:
            return None

    def setData(self, index: QtCore.QModelIndex, value: typing.Any, role: int = ...) -> bool:
        if role in (QtCore.Qt.EditRole,):
            node = index.internalPointer()  # type: Node
            node.setData(value, index)
            self.dataChanged.emit(index, index)
            return True
        else:
            return False

    def flags(self, index: QtCore.QModelIndex):
        node = index.internalPointer() if index.isValid() else self.root
        return node.flags(index)

    def appendRow(self, item):
        row = len(self.root.children())
        self.beginInsertRows(QtCore.QModelIndex(), row, row)
        self.root.children().append(item)
        self.endInsertRows()


class TreeView(QtWidgets.QTreeView):
    def __init__(self, parent=None):
        super(TreeView, self).__init__(parent)
        self._model = Model()
        self.setModel(self._model)
        self.setSelectionMode(self.ExtendedSelection)
        # self.setDropIndicatorShown(False)
        self.setEditTriggers(self.DoubleClicked | self.SelectedClicked | self.EditKeyPressed)

    def model(self) -> Model:
        return self._model

sys.excepthook = sys.__excepthook__
app = QtWidgets.QApplication(sys.argv)
widget = TreeView()
model = widget.model()
for i in range(2):
    model.appendRow(Item(model.root))
widget.show()
widget.setAttribute(QtCore.Qt.WA_DeleteOnClose)
sys.exit(app.exec_())
...