PyQt:在 QModelIndex 的内部指针中存储复杂的元数据
PyQt: Storing complex metadata in QModelIndex's internalPointer
这可能更适合作为错误报告,但也许我只是误解了一些东西。
我想实现自己的分层 QAbstractItemModel 以在 QTreeView 中显示对象树。我从可编辑的树模型示例 [1] 开始,效果很好。
但是,我的索引中需要更多元数据,因为我有不同类型的节点(或者更确切地说,节点具有属性)并且一个简单的(行、列、对象指针)元组是不够的。另请参阅 以获取另一个示例。
所以我创建了另一个数据结构来保存这些数据:
# this is just an example
class TempContainer:
def __init__(self, name, obj):
self.name = name
self.obj = obj
然后我创建了一个这样的索引:
# in TreeModel.index():
o = TempContainer("child", childItem)
self.tmpcache.append(o) # just to keep a reference alive
return self.createIndex(row, column, o)
访问对象现在可以实现如下:
def getItem(self, index):
if index.isValid():
item = index.internalPointer()
if item:
return item.obj
return self.rootItem
但是,如果我添加这一层元数据,select编辑项目将不再有效。我仍然可以 select 根项目第一列中的单元格并编辑第一列中的所有项目。但是,例如。 select在第二列中添加子项目或项目不再有效!
我注意到如果我将我的 TempContainer 存储在相应的 TreeItem 中它会起作用,但这并不是一个真正的选择,因为在我的完整代码中有多个容器指向同一个项目。
我在 Linux (Fedora 24) 下 运行ning python 3.5.3,使用 PyQt 5.6.2(我也试过 5.8.2)和 Qt 5.6.2.
[1] 不幸的是,我无法附上完整源代码的 ZIP,但代码可在 https://www.riverbankcomputing.com/software/pyqt/download5、/examples/itemviews/editabletreemodel
中找到
修改后的完整来源editabletreemodel.py:
(请注意,我只修改了几行,你仍然需要示例中的其他文件来 运行 这个。)
#!/usr/bin/env python
#############################################################################
##
## Copyright (C) 2013 Riverbank Computing Limited.
## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
## All rights reserved.
##
## This file is part of the examples of PyQt.
##
## $QT_BEGIN_LICENSE:BSD$
## You may use this file under the terms of the BSD license as follows:
##
## "Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions are
## met:
## * Redistributions of source code must retain the above copyright
## notice, this list of conditions and the following disclaimer.
## * Redistributions in binary form must reproduce the above copyright
## notice, this list of conditions and the following disclaimer in
## the documentation and/or other materials provided with the
## distribution.
## * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
## the names of its contributors may be used to endorse or promote
## products derived from this software without specific prior written
## permission.
##
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
## $QT_END_LICENSE$
##
#############################################################################
from PyQt5.QtCore import (QAbstractItemModel, QFile, QIODevice,
QItemSelectionModel, QModelIndex, Qt)
from PyQt5.QtWidgets import QApplication, QMainWindow
import editabletreemodel_rc
from ui_mainwindow import Ui_MainWindow
class TreeItem(object):
def __init__(self, data, parent=None):
self.parentItem = parent
self.itemData = data
self.childItems = []
def child(self, row):
return self.childItems[row]
def childCount(self):
return len(self.childItems)
def childNumber(self):
if self.parentItem != None:
return self.parentItem.childItems.index(self)
return 0
def columnCount(self):
return len(self.itemData)
def data(self, column):
return self.itemData[column]
def insertChildren(self, position, count, columns):
if position < 0 or position > len(self.childItems):
return False
for row in range(count):
data = [None for v in range(columns)]
item = TreeItem(data, self)
self.childItems.insert(position, item)
return True
def insertColumns(self, position, columns):
if position < 0 or position > len(self.itemData):
return False
for column in range(columns):
self.itemData.insert(position, None)
for child in self.childItems:
child.insertColumns(position, columns)
return True
def parent(self):
return self.parentItem
def removeChildren(self, position, count):
if position < 0 or position + count > len(self.childItems):
return False
for row in range(count):
self.childItems.pop(position)
return True
def removeColumns(self, position, columns):
if position < 0 or position + columns > len(self.itemData):
return False
for column in range(columns):
self.itemData.pop(position)
for child in self.childItems:
child.removeColumns(position, columns)
return True
def setData(self, column, value):
if column < 0 or column >= len(self.itemData):
return False
self.itemData[column] = value
return True
class TempContainer:
def __init__(self, name, obj):
self.name = name
self.obj = obj
class TreeModel(QAbstractItemModel):
def __init__(self, headers, data, parent=None):
super(TreeModel, self).__init__(parent)
rootData = [header for header in headers]
self.rootItem = TreeItem(rootData)
self.setupModelData(data.split("\n"), self.rootItem)
self.tmpcache = []
def columnCount(self, parent=QModelIndex()):
return self.rootItem.columnCount()
def data(self, index, role):
if not index.isValid():
return None
if role != Qt.DisplayRole and role != Qt.EditRole:
return None
item = self.getItem(index)
return item.data(index.column())
def flags(self, index):
if not index.isValid():
return 0
return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
def getItem(self, index):
if index.isValid():
item = index.internalPointer()
if item:
return item.obj
return self.rootItem
def headerData(self, section, orientation, role=Qt.DisplayRole):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.rootItem.data(section)
return None
def index(self, row, column, parent=QModelIndex()):
if parent.isValid() and parent.column() != 0:
return QModelIndex()
parentItem = self.getItem(parent)
childItem = parentItem.child(row)
if childItem:
o = TempContainer("child", childItem)
self.tmpcache.append(o)
return self.createIndex(row, column, o)
else:
return QModelIndex()
def insertColumns(self, position, columns, parent=QModelIndex()):
self.beginInsertColumns(parent, position, position + columns - 1)
success = self.rootItem.insertColumns(position, columns)
self.endInsertColumns()
return success
def insertRows(self, position, rows, parent=QModelIndex()):
parentItem = self.getItem(parent)
self.beginInsertRows(parent, position, position + rows - 1)
success = parentItem.insertChildren(position, rows,
self.rootItem.columnCount())
self.endInsertRows()
return success
def parent(self, index):
if not index.isValid():
return QModelIndex()
childItem = self.getItem(index)
parentItem = childItem.parent()
if parentItem == self.rootItem:
return QModelIndex()
o = TempContainer("parent", parentItem)
self.tmpcache.append(o)
return self.createIndex(parentItem.childNumber(), 0, o)
def removeColumns(self, position, columns, parent=QModelIndex()):
self.beginRemoveColumns(parent, position, position + columns - 1)
success = self.rootItem.removeColumns(position, columns)
self.endRemoveColumns()
if self.rootItem.columnCount() == 0:
self.removeRows(0, self.rowCount())
return success
def removeRows(self, position, rows, parent=QModelIndex()):
parentItem = self.getItem(parent)
self.beginRemoveRows(parent, position, position + rows - 1)
success = parentItem.removeChildren(position, rows)
self.endRemoveRows()
return success
def rowCount(self, parent=QModelIndex()):
parentItem = self.getItem(parent)
return parentItem.childCount()
def setData(self, index, value, role=Qt.EditRole):
if role != Qt.EditRole:
return False
item = self.getItem(index)
result = item.setData(index.column(), value)
if result:
self.dataChanged.emit(index, index)
return result
def setHeaderData(self, section, orientation, value, role=Qt.EditRole):
if role != Qt.EditRole or orientation != Qt.Horizontal:
return False
result = self.rootItem.setData(section, value)
if result:
self.headerDataChanged.emit(orientation, section, section)
return result
def setupModelData(self, lines, parent):
parents = [parent]
indentations = [0]
number = 0
while number < len(lines):
position = 0
while position < len(lines[number]):
if lines[number][position] != " ":
break
position += 1
lineData = lines[number][position:].trimmed()
if lineData:
# Read the column data from the rest of the line.
columnData = [s for s in lineData.split('\t') if s]
if position > indentations[-1]:
# The last child of the current parent is now the new
# parent unless the current parent has no children.
if parents[-1].childCount() > 0:
parents.append(parents[-1].child(parents[-1].childCount() - 1))
indentations.append(position)
else:
while position < indentations[-1] and len(parents) > 0:
parents.pop()
indentations.pop()
# Append a new item to the current parent's list of children.
parent = parents[-1]
parent.insertChildren(parent.childCount(), 1,
self.rootItem.columnCount())
for column in range(len(columnData)):
parent.child(parent.childCount() -1).setData(column, columnData[column])
number += 1
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
headers = ("Title", "Description")
file = QFile(':/default.txt')
file.open(QIODevice.ReadOnly)
model = TreeModel(headers, file.readAll())
file.close()
self.view.setModel(model)
for column in range(model.columnCount()):
self.view.resizeColumnToContents(column)
self.exitAction.triggered.connect(QApplication.instance().quit)
self.view.selectionModel().selectionChanged.connect(self.updateActions)
self.actionsMenu.aboutToShow.connect(self.updateActions)
self.insertRowAction.triggered.connect(self.insertRow)
self.insertColumnAction.triggered.connect(self.insertColumn)
self.removeRowAction.triggered.connect(self.removeRow)
self.removeColumnAction.triggered.connect(self.removeColumn)
self.insertChildAction.triggered.connect(self.insertChild)
self.updateActions()
def insertChild(self):
index = self.view.selectionModel().currentIndex()
model = self.view.model()
if model.columnCount(index) == 0:
if not model.insertColumn(0, index):
return
if not model.insertRow(0, index):
return
for column in range(model.columnCount(index)):
child = model.index(0, column, index)
model.setData(child, "[No data]", Qt.EditRole)
if model.headerData(column, Qt.Horizontal) is None:
model.setHeaderData(column, Qt.Horizontal, "[No header]",
Qt.EditRole)
self.view.selectionModel().setCurrentIndex(model.index(0, 0, index),
QItemSelectionModel.ClearAndSelect)
self.updateActions()
def insertColumn(self):
model = self.view.model()
column = self.view.selectionModel().currentIndex().column()
changed = model.insertColumn(column + 1)
if changed:
model.setHeaderData(column + 1, Qt.Horizontal, "[No header]",
Qt.EditRole)
self.updateActions()
return changed
def insertRow(self):
index = self.view.selectionModel().currentIndex()
model = self.view.model()
if not model.insertRow(index.row()+1, index.parent()):
return
self.updateActions()
for column in range(model.columnCount(index.parent())):
child = model.index(index.row()+1, column, index.parent())
model.setData(child, "[No data]", Qt.EditRole)
def removeColumn(self):
model = self.view.model()
column = self.view.selectionModel().currentIndex().column()
changed = model.removeColumn(column)
if changed:
self.updateActions()
return changed
def removeRow(self):
index = self.view.selectionModel().currentIndex()
model = self.view.model()
if (model.removeRow(index.row(), index.parent())):
self.updateActions()
def updateActions(self):
hasSelection = not self.view.selectionModel().selection().isEmpty()
self.removeRowAction.setEnabled(hasSelection)
self.removeColumnAction.setEnabled(hasSelection)
hasCurrent = self.view.selectionModel().currentIndex().isValid()
self.insertRowAction.setEnabled(hasCurrent)
self.insertColumnAction.setEnabled(hasCurrent)
if hasCurrent:
self.view.closePersistentEditor(self.view.selectionModel().currentIndex())
row = self.view.selectionModel().currentIndex().row()
column = self.view.selectionModel().currentIndex().column()
if self.view.selectionModel().currentIndex().parent().isValid():
self.statusBar().showMessage("Position: (%d,%d)" % (row, column))
else:
self.statusBar().showMessage("Position: (%d,%d) in top level" % (row, column))
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
编辑:
我意识到我举的例子毫无意义,TempContainer
没有任何实际意义!这只是一个显示我 运行 遇到的问题的最小示例。至于为什么需要 TempContainer
,假设您的实际 TreeItems
看起来像这样:
class TreeItem:
def __init__(self,
name:str, description:str,
data:List[TreeItem], metadata:Dict[str,str],
parent:TreeItem=None):
...
在 TableView 中应呈现为:
itemA somedescription here
itemB this is another item
\--metadata
| \--created yesterday
| \--color green
\--data
\--itemC this is a sub-item of itemB
| \--metadata
| \--data
\--itemD and another item
然后你需要一个索引,例如'name of the second metadata item of itemB'。您可以创建某种反映此结构但由普通 TreeItem
组成的影子树,但这可能会很麻烦,尤其是在编辑时。我的想法是不仅在索引中存储指向 TreeItem
的指针,而且还用子索引对其进行注释,指示我们指向 TreeItem
的哪一部分。
我认为您正在创建的索引几乎没问题,但它们必须履行更多的合同。具体来说,他们需要支持有效的 equals 方法。在您的模型 index
方法中,您在索引中创建了一个新的 TempContainer
。这意味着当视图将该索引与它之前请求的树中同一位置的另一个索引进行比较时,它们将不会比较相等。那是因为它在 QModelIndex
class 上使用默认等于,比较 row
、column
和 internalPointer
是否相等。文档说:
All values in the model index are used when comparing with another
model index
虽然您的 TempContainer
想法作为一种使您的树适应视图的方式最初看起来很有吸引力,但正如您所发现的那样,这并不容易做到。 Qt 本质上希望模型具有与视图中匹配的清晰树结构,这是很难避免的。在这种情况下,我总是创建一个 Python 对象的树层,我将其引用用作内部指针。
这可能更适合作为错误报告,但也许我只是误解了一些东西。
我想实现自己的分层 QAbstractItemModel 以在 QTreeView 中显示对象树。我从可编辑的树模型示例 [1] 开始,效果很好。
但是,我的索引中需要更多元数据,因为我有不同类型的节点(或者更确切地说,节点具有属性)并且一个简单的(行、列、对象指针)元组是不够的。另请参阅
所以我创建了另一个数据结构来保存这些数据:
# this is just an example
class TempContainer:
def __init__(self, name, obj):
self.name = name
self.obj = obj
然后我创建了一个这样的索引:
# in TreeModel.index():
o = TempContainer("child", childItem)
self.tmpcache.append(o) # just to keep a reference alive
return self.createIndex(row, column, o)
访问对象现在可以实现如下:
def getItem(self, index):
if index.isValid():
item = index.internalPointer()
if item:
return item.obj
return self.rootItem
但是,如果我添加这一层元数据,select编辑项目将不再有效。我仍然可以 select 根项目第一列中的单元格并编辑第一列中的所有项目。但是,例如。 select在第二列中添加子项目或项目不再有效!
我注意到如果我将我的 TempContainer 存储在相应的 TreeItem 中它会起作用,但这并不是一个真正的选择,因为在我的完整代码中有多个容器指向同一个项目。
我在 Linux (Fedora 24) 下 运行ning python 3.5.3,使用 PyQt 5.6.2(我也试过 5.8.2)和 Qt 5.6.2.
[1] 不幸的是,我无法附上完整源代码的 ZIP,但代码可在 https://www.riverbankcomputing.com/software/pyqt/download5、/examples/itemviews/editabletreemodel
修改后的完整来源editabletreemodel.py: (请注意,我只修改了几行,你仍然需要示例中的其他文件来 运行 这个。)
#!/usr/bin/env python
#############################################################################
##
## Copyright (C) 2013 Riverbank Computing Limited.
## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
## All rights reserved.
##
## This file is part of the examples of PyQt.
##
## $QT_BEGIN_LICENSE:BSD$
## You may use this file under the terms of the BSD license as follows:
##
## "Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions are
## met:
## * Redistributions of source code must retain the above copyright
## notice, this list of conditions and the following disclaimer.
## * Redistributions in binary form must reproduce the above copyright
## notice, this list of conditions and the following disclaimer in
## the documentation and/or other materials provided with the
## distribution.
## * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
## the names of its contributors may be used to endorse or promote
## products derived from this software without specific prior written
## permission.
##
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
## $QT_END_LICENSE$
##
#############################################################################
from PyQt5.QtCore import (QAbstractItemModel, QFile, QIODevice,
QItemSelectionModel, QModelIndex, Qt)
from PyQt5.QtWidgets import QApplication, QMainWindow
import editabletreemodel_rc
from ui_mainwindow import Ui_MainWindow
class TreeItem(object):
def __init__(self, data, parent=None):
self.parentItem = parent
self.itemData = data
self.childItems = []
def child(self, row):
return self.childItems[row]
def childCount(self):
return len(self.childItems)
def childNumber(self):
if self.parentItem != None:
return self.parentItem.childItems.index(self)
return 0
def columnCount(self):
return len(self.itemData)
def data(self, column):
return self.itemData[column]
def insertChildren(self, position, count, columns):
if position < 0 or position > len(self.childItems):
return False
for row in range(count):
data = [None for v in range(columns)]
item = TreeItem(data, self)
self.childItems.insert(position, item)
return True
def insertColumns(self, position, columns):
if position < 0 or position > len(self.itemData):
return False
for column in range(columns):
self.itemData.insert(position, None)
for child in self.childItems:
child.insertColumns(position, columns)
return True
def parent(self):
return self.parentItem
def removeChildren(self, position, count):
if position < 0 or position + count > len(self.childItems):
return False
for row in range(count):
self.childItems.pop(position)
return True
def removeColumns(self, position, columns):
if position < 0 or position + columns > len(self.itemData):
return False
for column in range(columns):
self.itemData.pop(position)
for child in self.childItems:
child.removeColumns(position, columns)
return True
def setData(self, column, value):
if column < 0 or column >= len(self.itemData):
return False
self.itemData[column] = value
return True
class TempContainer:
def __init__(self, name, obj):
self.name = name
self.obj = obj
class TreeModel(QAbstractItemModel):
def __init__(self, headers, data, parent=None):
super(TreeModel, self).__init__(parent)
rootData = [header for header in headers]
self.rootItem = TreeItem(rootData)
self.setupModelData(data.split("\n"), self.rootItem)
self.tmpcache = []
def columnCount(self, parent=QModelIndex()):
return self.rootItem.columnCount()
def data(self, index, role):
if not index.isValid():
return None
if role != Qt.DisplayRole and role != Qt.EditRole:
return None
item = self.getItem(index)
return item.data(index.column())
def flags(self, index):
if not index.isValid():
return 0
return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
def getItem(self, index):
if index.isValid():
item = index.internalPointer()
if item:
return item.obj
return self.rootItem
def headerData(self, section, orientation, role=Qt.DisplayRole):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.rootItem.data(section)
return None
def index(self, row, column, parent=QModelIndex()):
if parent.isValid() and parent.column() != 0:
return QModelIndex()
parentItem = self.getItem(parent)
childItem = parentItem.child(row)
if childItem:
o = TempContainer("child", childItem)
self.tmpcache.append(o)
return self.createIndex(row, column, o)
else:
return QModelIndex()
def insertColumns(self, position, columns, parent=QModelIndex()):
self.beginInsertColumns(parent, position, position + columns - 1)
success = self.rootItem.insertColumns(position, columns)
self.endInsertColumns()
return success
def insertRows(self, position, rows, parent=QModelIndex()):
parentItem = self.getItem(parent)
self.beginInsertRows(parent, position, position + rows - 1)
success = parentItem.insertChildren(position, rows,
self.rootItem.columnCount())
self.endInsertRows()
return success
def parent(self, index):
if not index.isValid():
return QModelIndex()
childItem = self.getItem(index)
parentItem = childItem.parent()
if parentItem == self.rootItem:
return QModelIndex()
o = TempContainer("parent", parentItem)
self.tmpcache.append(o)
return self.createIndex(parentItem.childNumber(), 0, o)
def removeColumns(self, position, columns, parent=QModelIndex()):
self.beginRemoveColumns(parent, position, position + columns - 1)
success = self.rootItem.removeColumns(position, columns)
self.endRemoveColumns()
if self.rootItem.columnCount() == 0:
self.removeRows(0, self.rowCount())
return success
def removeRows(self, position, rows, parent=QModelIndex()):
parentItem = self.getItem(parent)
self.beginRemoveRows(parent, position, position + rows - 1)
success = parentItem.removeChildren(position, rows)
self.endRemoveRows()
return success
def rowCount(self, parent=QModelIndex()):
parentItem = self.getItem(parent)
return parentItem.childCount()
def setData(self, index, value, role=Qt.EditRole):
if role != Qt.EditRole:
return False
item = self.getItem(index)
result = item.setData(index.column(), value)
if result:
self.dataChanged.emit(index, index)
return result
def setHeaderData(self, section, orientation, value, role=Qt.EditRole):
if role != Qt.EditRole or orientation != Qt.Horizontal:
return False
result = self.rootItem.setData(section, value)
if result:
self.headerDataChanged.emit(orientation, section, section)
return result
def setupModelData(self, lines, parent):
parents = [parent]
indentations = [0]
number = 0
while number < len(lines):
position = 0
while position < len(lines[number]):
if lines[number][position] != " ":
break
position += 1
lineData = lines[number][position:].trimmed()
if lineData:
# Read the column data from the rest of the line.
columnData = [s for s in lineData.split('\t') if s]
if position > indentations[-1]:
# The last child of the current parent is now the new
# parent unless the current parent has no children.
if parents[-1].childCount() > 0:
parents.append(parents[-1].child(parents[-1].childCount() - 1))
indentations.append(position)
else:
while position < indentations[-1] and len(parents) > 0:
parents.pop()
indentations.pop()
# Append a new item to the current parent's list of children.
parent = parents[-1]
parent.insertChildren(parent.childCount(), 1,
self.rootItem.columnCount())
for column in range(len(columnData)):
parent.child(parent.childCount() -1).setData(column, columnData[column])
number += 1
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
headers = ("Title", "Description")
file = QFile(':/default.txt')
file.open(QIODevice.ReadOnly)
model = TreeModel(headers, file.readAll())
file.close()
self.view.setModel(model)
for column in range(model.columnCount()):
self.view.resizeColumnToContents(column)
self.exitAction.triggered.connect(QApplication.instance().quit)
self.view.selectionModel().selectionChanged.connect(self.updateActions)
self.actionsMenu.aboutToShow.connect(self.updateActions)
self.insertRowAction.triggered.connect(self.insertRow)
self.insertColumnAction.triggered.connect(self.insertColumn)
self.removeRowAction.triggered.connect(self.removeRow)
self.removeColumnAction.triggered.connect(self.removeColumn)
self.insertChildAction.triggered.connect(self.insertChild)
self.updateActions()
def insertChild(self):
index = self.view.selectionModel().currentIndex()
model = self.view.model()
if model.columnCount(index) == 0:
if not model.insertColumn(0, index):
return
if not model.insertRow(0, index):
return
for column in range(model.columnCount(index)):
child = model.index(0, column, index)
model.setData(child, "[No data]", Qt.EditRole)
if model.headerData(column, Qt.Horizontal) is None:
model.setHeaderData(column, Qt.Horizontal, "[No header]",
Qt.EditRole)
self.view.selectionModel().setCurrentIndex(model.index(0, 0, index),
QItemSelectionModel.ClearAndSelect)
self.updateActions()
def insertColumn(self):
model = self.view.model()
column = self.view.selectionModel().currentIndex().column()
changed = model.insertColumn(column + 1)
if changed:
model.setHeaderData(column + 1, Qt.Horizontal, "[No header]",
Qt.EditRole)
self.updateActions()
return changed
def insertRow(self):
index = self.view.selectionModel().currentIndex()
model = self.view.model()
if not model.insertRow(index.row()+1, index.parent()):
return
self.updateActions()
for column in range(model.columnCount(index.parent())):
child = model.index(index.row()+1, column, index.parent())
model.setData(child, "[No data]", Qt.EditRole)
def removeColumn(self):
model = self.view.model()
column = self.view.selectionModel().currentIndex().column()
changed = model.removeColumn(column)
if changed:
self.updateActions()
return changed
def removeRow(self):
index = self.view.selectionModel().currentIndex()
model = self.view.model()
if (model.removeRow(index.row(), index.parent())):
self.updateActions()
def updateActions(self):
hasSelection = not self.view.selectionModel().selection().isEmpty()
self.removeRowAction.setEnabled(hasSelection)
self.removeColumnAction.setEnabled(hasSelection)
hasCurrent = self.view.selectionModel().currentIndex().isValid()
self.insertRowAction.setEnabled(hasCurrent)
self.insertColumnAction.setEnabled(hasCurrent)
if hasCurrent:
self.view.closePersistentEditor(self.view.selectionModel().currentIndex())
row = self.view.selectionModel().currentIndex().row()
column = self.view.selectionModel().currentIndex().column()
if self.view.selectionModel().currentIndex().parent().isValid():
self.statusBar().showMessage("Position: (%d,%d)" % (row, column))
else:
self.statusBar().showMessage("Position: (%d,%d) in top level" % (row, column))
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
编辑:
我意识到我举的例子毫无意义,TempContainer
没有任何实际意义!这只是一个显示我 运行 遇到的问题的最小示例。至于为什么需要 TempContainer
,假设您的实际 TreeItems
看起来像这样:
class TreeItem:
def __init__(self,
name:str, description:str,
data:List[TreeItem], metadata:Dict[str,str],
parent:TreeItem=None):
...
在 TableView 中应呈现为:
itemA somedescription here
itemB this is another item
\--metadata
| \--created yesterday
| \--color green
\--data
\--itemC this is a sub-item of itemB
| \--metadata
| \--data
\--itemD and another item
然后你需要一个索引,例如'name of the second metadata item of itemB'。您可以创建某种反映此结构但由普通 TreeItem
组成的影子树,但这可能会很麻烦,尤其是在编辑时。我的想法是不仅在索引中存储指向 TreeItem
的指针,而且还用子索引对其进行注释,指示我们指向 TreeItem
的哪一部分。
我认为您正在创建的索引几乎没问题,但它们必须履行更多的合同。具体来说,他们需要支持有效的 equals 方法。在您的模型 index
方法中,您在索引中创建了一个新的 TempContainer
。这意味着当视图将该索引与它之前请求的树中同一位置的另一个索引进行比较时,它们将不会比较相等。那是因为它在 QModelIndex
class 上使用默认等于,比较 row
、column
和 internalPointer
是否相等。文档说:
All values in the model index are used when comparing with another model index
虽然您的 TempContainer
想法作为一种使您的树适应视图的方式最初看起来很有吸引力,但正如您所发现的那样,这并不容易做到。 Qt 本质上希望模型具有与视图中匹配的清晰树结构,这是很难避免的。在这种情况下,我总是创建一个 Python 对象的树层,我将其引用用作内部指针。