I am trying to make a simple property editor, where the property list is a nested dict and the data is displayed and edited in a QTreeView. (Before I get to my question -- if anyone already has a working implementation of this in Python 3 I'd love to be pointed at it).
Anyway, after much work I have my QAbstractItemModel and I can open a QTreeView with this model and it shows the data. If I click on a label in the first column (the key) then it opens up an editor, either a text editor or a spinbox etc depending on the datatype. When I finish editing it calls my "model.setData" where I reject it because I don't want to allow editable keys. I can disable the editing of this by using flags and that works fine. I just wanted to check that everything works the way that I'd expect it to.
Here is what doesn't happen: if I click on a cell in the second column (the value that I actually want to edit) then it bypasses the loading of an editor and simply calls model.setData with the current value. I am baffled. I've tried changing the tree selectionBehavior and selectionMode but no dice. I'm returning Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable in flags. It seems to display fine. It just won't open up an editor.
Any thoughts about what stupid mistake I must be making? I'll include the code below, with some print statements that I'm using to try to debug the thing.
Thanks
PS One thing that hung me up for a long time was that my QModelIndex members would just disappear, so the indices that I got back were garbage. I found that by keeping a reference to them (throwing them in a list) that they worked. This seems to be a problem that springs up a lot in Qt work (I had the same problem with menus disappearing -- I guess that means that I should think about it sooner). Is there a "best practices" way of dealing with this?
# -*- 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_())