我正在尝试创建一个简单的属性编辑器,其中属性列表是嵌套的dict,数据在QTreeView中显示和编辑。 (在我提出我的问题之前 - 如果有人已经在Python 3中有一个有效的实现,我很乐意指出它。)
无论如何,经过大量的工作后,我有了我的QAbstractItemModel,我可以用这个模型打开一个QTreeView,它会显示数据。如果我单击第一列(键)中的标签,则会根据数据类型打开编辑器,文本编辑器或旋转框等。当我完成编辑它调用我的“model.setData”,我拒绝它,因为我不想允许可编辑的键。我可以通过使用标志禁用编辑此功能,并且工作正常。我只想检查一切是否符合我期望的方式。
以下是不会发生的事情:如果我点击第二列中的单元格(我实际想要编辑的值),那么它会绕过编辑器的加载,只需使用当前值调用model.setData。我很困惑。我已经尝试更改树选择行为和选择模式,但没有骰子。我正在返回Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable in flags。它看起来很好。它只是不会打开一个编辑器。
关于我必须犯下什么愚蠢错误的任何想法?我将包含下面的代码,其中包含一些我正在尝试调试的打印语句。
由于
PS有一件事让我感觉很长时间,因为我的QModelIndex成员会消失,所以我得到的指数就是垃圾。我发现通过保持对它们的引用(将它们放入列表中)它们起作用。这似乎是Qt工作中出现的一个问题(菜单消失时我遇到了同样的问题 - 我想这意味着我应该早点考虑一下)。是否有“最佳实践”方法来解决这个问题?# -*- coding: utf-8 -*-
from collections import OrderedDict
from PyQt4.QtCore import QAbstractItemModel, QModelIndex, Qt
from PyQt4.QtGui import QAbstractItemView
class PropertyList(OrderedDict):
def __init__(self, *args, **kwargs):
OrderedDict.__init__(self, *args, **kwargs)
self.myModel = PropertyListModel(self)
def __getitem__(self,index):
if issubclass(type(index), list):
item = self
for key in index:
item = item[key]
return item
else:
return OrderedDict.__getitem__(self, index)
class PropertyListModel(QAbstractItemModel):
def __init__(self, propList, *args, **kwargs):
QAbstractItemModel.__init__(self, *args, **kwargs)
self.propertyList = propList
self.myIndexes = [] # Needed to stop garbage collection
def index(self, row, column, parent):
"""Returns QModelIndex to row, column in parent (QModelIndex)"""
if not self.hasIndex(row, column, parent):
return QModelIndex()
if parent.isValid():
indexPtr = parent.internalPointer()
parentDict = self.propertyList[indexPtr]
else:
parentDict = self.propertyList
indexPtr = []
rowKey = list(parentDict.keys())[row]
childPtr = indexPtr+[rowKey]
newIndex = self.createIndex(row, column, childPtr)
self.myIndexes.append(childPtr)
return newIndex
def get_row(self, key):
"""Returns the row of the given key (list of keys) in its parent"""
if key:
parent = key[:-1]
return list(self.propertyList[parent].keys()).index(key[-1])
else:
return 0
def parent(self, index):
"""
Returns the parent (QModelIndex) of the given item (QModelIndex)
Top level returns QModelIndex()
"""
if not index.isValid():
return QModelIndex()
childKeylist = index.internalPointer()
if childKeylist:
parentKeylist = childKeylist[:-1]
self.myIndexes.append(parentKeylist)
return self.createIndex(self.get_row(parentKeylist), 0,
parentKeylist)
else:
return QModelIndex()
def rowCount(self, parent):
"""Returns number of rows in parent (QModelIndex)"""
if parent.column() > 0:
return 0 # only keys have children, not values
if parent.isValid():
indexPtr = parent.internalPointer()
try:
parentValue = self.propertyList[indexPtr]
except:
return 0
if issubclass(type(parentValue), dict):
return len(self.propertyList[indexPtr])
else:
return 0
else:
return len(self.propertyList)
def columnCount(self, parent):
return 2 # Key & value
def data(self, index, role):
"""Returns data for given role for given index (QModelIndex)"""
# print('Looking for data in role {}'.format(role))
if not index.isValid():
return None
if role in (Qt.DisplayRole, Qt.EditRole):
indexPtr = index.internalPointer()
if index.column() == 1: # Column 1, send the value
return self.propertyList[indexPtr]
else: # Column 0, send the key
if indexPtr:
return indexPtr[-1]
else:
return ""
else: # Not display or Edit
return None
def setData(self, index, value, role):
"""Sets the value of index in a given role"""
print('In SetData')
if not index.isValid():
return False
print('Trying to set {} to {}'.format(index,value))
print('That is column {}'.format(index.column()))
if not index.column(): # Only change column 1
return False
try:
ptr = index.internalPointer()
self.propertyList[ptr[:-1]][ptr[-1]] = value
self.emit(self.dataChanged(index, index))
return True
except:
return False
def flags(self, index):
"""Indicates what can be done with the data"""
if not index.isValid():
return Qt.NoItemFlags
if index.column(): # only enable editing of values, not keys
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
else:
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable #Qt.NoItemFlags
if __name__ == '__main__':
p = PropertyList({'k1':'v1','k2':{'k3':'v3','k4':4}})
import sys
from PyQt4 import QtGui
qApp = QtGui.QApplication(sys.argv)
treeView = QtGui.QTreeView()
# I've played with all the settings on these to no avail
treeView.setHeaderHidden(False)
treeView.setAllColumnsShowFocus(True)
treeView.setUniformRowHeights(True)
treeView.setSelectionBehavior(QAbstractItemView.SelectRows)
treeView.setSelectionMode(QAbstractItemView.SingleSelection)
treeView.setAlternatingRowColors(True)
treeView.setEditTriggers(QAbstractItemView.DoubleClicked |
QAbstractItemView.SelectedClicked |
QAbstractItemView.EditKeyPressed |
QAbstractItemView.AnyKeyPressed)
treeView.setTabKeyNavigation(True)
treeView.setModel(p.myModel)
treeView.show()
sys.exit(qApp.exec_())
答案 0 :(得分:1)
@strubbly非常接近,但忘了用index
方法解压缩元组。
这是Qt5的工作代码。可能需要修复几个导入和东西。只花了我生命中的几个星期:))
import sys
from collections import OrderedDict
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt
class TupleKeyedOrderedDict(OrderedDict):
def __init__(self, *args, **kwargs):
super().__init__(sorted(kwargs.items()))
def __getitem__(self, key):
if isinstance(key, tuple):
item = self
for k in key:
if item != ():
item = item[k]
return item
else:
return super().__getitem__(key)
def __setitem__(self, key, value):
if isinstance(key, tuple):
item = self
previous_item = None
for k in key:
if item != ():
previous_item = item
item = item[k]
previous_item[key[-1]] = value
else:
return super().__setitem__(key, value)
class SettingsModel(QtCore.QAbstractItemModel):
def __init__(self, data, parent=None):
super().__init__(parent)
self.root = data
self.my_index = {} # Needed to stop garbage collection
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
if parent.isValid():
index_pointer = parent.internalPointer()
parent_dict = self.root[index_pointer]
else:
parent_dict = self.root
index_pointer = ()
row_key = list(parent_dict.keys())[row]
child_pointer = (*index_pointer, row_key)
try:
child_pointer = self.my_index[child_pointer]
except KeyError:
self.my_index[child_pointer] = child_pointer
index = self.createIndex(row, column, child_pointer)
return index
def get_row(self, key):
if key:
parent = key[:-1]
if not parent:
return 0
return list(self.root[parent].keys()).index(key[-1])
else:
return 0
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
child_key_list = index.internalPointer()
if child_key_list:
parent_key_list = child_key_list[:-1]
try:
parent_key_list = self.my_index[parent_key_list]
except KeyError:
self.my_index[parent_key_list] = parent_key_list
return self.createIndex(self.get_row(parent_key_list), 0,
parent_key_list)
else:
return QtCore.QModelIndex()
def rowCount(self, parent):
if parent.column() > 0:
return 0 # only keys have children, not values
if parent.isValid():
indexPtr = parent.internalPointer()
parentValue = self.root[indexPtr]
if isinstance(parentValue, OrderedDict):
return len(self.root[indexPtr])
else:
return 0
else:
return len(self.root)
def columnCount(self, parent):
return 2 # Key & value
def data(self, index, role):
if not index.isValid():
return None
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
indexPtr = index.internalPointer()
if index.column() == 1: # Column 1, send the value
return self.root[indexPtr]
else: # Column 0, send the key
if indexPtr:
return indexPtr[-1]
else:
return None
else: # Not display or Edit
return None
def setData(self, index, value, role):
pointer = self.my_index[index.internalPointer()]
self.root[pointer] = value
self.dataChanged.emit(index, index)
return True
def flags(self, index):
if not index.isValid():
return 0
return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
data = TupleKeyedOrderedDict(**{'1': OrderedDict({'sub': 'b'}), '2': OrderedDict({'subsub': '3'})})
model = SettingsModel(data)
tree_view = QtWidgets.QTreeView()
tree_view.setModel(model)
tree_view.show()
sys.exit(app.exec_())
答案 1 :(得分:0)
保留索引列表以防止它们被垃圾回收。这是必需的,因为正如文档所解释的那样,internalPointer
的{{1}}引用的Python对象不受该引用的垃圾回收保护。但是,每次要求模型索引时,都会添加列表,因此即使对于模型中的同一项,也会创建新的internalPointer。而Qt期望索引,因此internalPointer是相同的。这也是有问题的,因为这意味着索引列表会不断增长(正如您可以看到,如果您添加调试打印打印出QModelIndex
的内容)。
在您的案例中,这不是一件容易的事情。在大多数模型中,internalPointer只存储指向父项的指针,因此永远不会重复。但是这在你的情况下不起作用,因为PropertyList中的项目不知道它们的父项。最简单的解决方案可能是更改它,但PropertyList不应该真正受到它在Qt模型中使用的影响。
相反,我已经构建了一个dict,用于查找您构建的任何键列表的“原始”键列表。这看起来有点奇怪,但它可以使用最少的更改来修复代码。我在底部提到了一些替代方法。
所以这些是我的更改(实际上只是更改self.myIndexes
的行,但也将键列表更改为元组而不是列表,因此可以进行哈希处理):
self.myIndexes
这似乎有效,但我没有做太多测试。
或者,您可以使用internalPointer来存储父模型项(字典)并保持从模型项到键列表的映射。或者从模型项到父项的映射。这两个都需要一点点摆弄(尤其是因为字典不能立即清洗)但两者都是可能的。