QTreeView仅在第一列编辑?

时间:2015-07-14 22:34:15

标签: python-3.x qt4 qtreeview qabstractitemmodel propertyeditor

我正在尝试创建一个简单的属性编辑器,其中属性列表是嵌套的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_())

2 个答案:

答案 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来存储父模型项(字典)并保持从模型项到键列表的映射。或者从模型项到父项的映射。这两个都需要一点点摆弄(尤其是因为字典不能立即清洗)但两者都是可能的。