QTreeView只编辑第一列



我正在尝试制作一个简单的属性编辑器,其中属性列表是一个嵌套字典,数据在QTreeView中显示和编辑。(在我回答我的问题之前——如果有人已经在Python 3中实现了这个功能,我很乐意指出它)。

无论如何,经过大量的工作,我有我的QAbstractItemModel,我可以用这个模型打开一个QTreeView,它显示了数据。如果我点击第一列中的标签(键),那么它会打开一个编辑器,根据数据类型,要么是文本编辑器,要么是旋转框等。当我完成编辑时,它调用我的"模型"。setData"我拒绝它,因为我不想允许可编辑的键。我可以通过使用标志来禁用编辑,这很好。我只是想检查一下是否一切都按我期望的方式运行。

以下是不会发生的事情:如果我单击第二列中的单元格(我实际想要编辑的值),那么它会绕过编辑器的加载并简单地调用model。使用当前值setData。我很困惑。我试过改变树的选择行为和选择模式,但没有骰子。我在标志中返回Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable。它看起来很好。它只是不能打开编辑器。

你知道我犯了什么愚蠢的错误吗?我将包括下面的代码,以及我用来调试的一些打印语句。

感谢

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_())

@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_())

保存一个索引列表,以防止它们被垃圾收集。这是必要的,因为正如文档所解释的那样,由QModelIndexinternalPointer引用的Python对象不受该引用的垃圾收集的保护。然而,你的列表被添加到每次你的模型被要求一个索引,所以一个新的内部指针被创建,甚至在模型中相同的项目。而Qt期望索引,因此内部指针是相同的。这也是有问题的,因为这意味着索引列表一直在增长(正如您可以看到的,如果您添加一个调试打印,打印出self.myIndexes的内容)。

在您的情况下,这不是微不足道的修复。在大多数模型中,内部指针只存储指向父项的指针,因此不会重复。但这在您的情况下不起作用,因为PropertyList中的项不知道它们的父项。最简单的解决方案可能是改变它,但是PropertyList在Qt模型中的使用不应该真正受到影响。

相反,我构建了一个字典,用于为您构建的任何键列表查找"原始"键列表。这看起来有点奇怪,但它可以工作,并以最少的更改修复您的代码。

所以这些是我的改变(实际上只是改变self.myIndexes的行,但也改变键列表是一个元组,而不是一个列表,所以它可以被散列):

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,)
    try:
        childPtr = self.myIndexes[childPtr]
    except KeyError:
        self.myIndexes[childPtr] = childPtr
    newIndex = self.createIndex(row, column, childPtr)
    return newIndex
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]
        try:
            parentKeylist = self.myIndexes[parentKeylist]
        except KeyError:
            self.myIndexes[parentKeylist] = parentKeylist
        return self.createIndex(self.get_row(parentKeylist), 0,
                                parentKeylist)
    else:
        return QModelIndex()

这似乎工作,虽然我没有做太多的测试。

或者,您可以使用内部指针来存储父模型项(字典),并保持从模型项到键列表的映射。或者从模型项到父项的映射。这两种方法都需要稍加修改(主要是因为字典不能立即散列),但两者都是可能的。

最新更新