简体   繁体   中英

Interplay between QAbstractItemModel and QModelIndex

The following questions are about the design of the QAbstractItemModel and QModelIndex classes and the interplay between them, as highlighted by the sample of code below:

class Data:
    def __init__(self):
        self.value = 42

class Model( QAbstractItemModel ):
    def __init__( self ):
        QAbstractItemModel.__init__(self)
        data = Data()

        modelIndex = self.createIndex( 1 , 2 , data ) ### 1
        self.index( 1 , 2 , QModelIndex() ) ### 2
        self.setData( modelIndex , data.value ) ### 3
        self.dataChanged.emit( modelIndex , modelIndex )

        modelIndex.data() ###4
        self.data( modelIndex ) ### 5
  1. How should a QModelIndex be created. From my reading of the docs the answer is QAbstractItemModel::createIndex() but it seems incomplete because this function does not supply any information about the offset of the ModelIndex from its parent. Rather this is done by QAbstractItemModel::index() . Is there a way to make both functions play together?
  2. How should data be stored in or the model index and what is the difference between the data stored by, for, or in (unsure of the terminology) the model index and the internal pointer ? And where does the model index get the data that it returns when there it has no setData function? Is the internal pointer ever the data? Can it ever be the data?
  3. What is the difference between the data returned by the ModelIndex and the Model? ie QModelIndex::data() and QAbstractItemModel::data( QModelIndex , int ) ? And why is the setter QAbstractItemModel::setData( QModelIndex , ... ) merely virtual but the getter QAbstractItemModel::data( QModelIndex , ... ) pure virtual. Surely the API should be able to return the data that it stored.

I am aware that my question links to the C++ API whereas the snippet is in PySide. I have done so because this question cuts across both APIs.

Considering that you have many questions that are related to QAbstractItemModel and QModelIndex that involve issues of implementation and other design I will take the time to respond one by one so I will edit this answer giving more details.

1.

QModelIndex does not have the information of who its parent is, nor of the offset that indicates, it only keeps the information of the row, column, pointer to the data and the model to which it belongs(see here ). So as you know who your parent is, that is your task, you must override the parent() method of the model and return that.

def parent(self, index):
    if not index.isValid():
        return QtCore.QModelIndex()
    childItem = index.internalPointer()
    parentItem = childItem.parentItem()
    if parentItem == self.rootItem:
        return QtCore.QModelIndex()
    return self.createIndex(..., ..., parentItem)

So in the parent() method of the QModelIndex this one gets the parent through the model(see here ): def parent(self) : return self.model.parent(self) .

About the relationship between the index() and createIndex() methods, the second is called by the first, but also must have a relationship with your data structure. A generic implementation is:

def index(self, row, column, parent):
    if not self.hasIndex(row, column, parent):
        return QtCore.QModelIndex()
    parentItem = None
    if not parent.isValid():
        parentItem = self.rootItem 
    else:
        parentItem = parent.internalPointer()
    childItem = parentItem.child(...)
    if childItem is not None:
        return self.createIndex(row, column, childItem)
    return QtCore.QModelIndex()

In the case of createIndex() only creates a QModelIndex with the information of the row, the column, the model and the pointer to childItem, that constructor is not in the docs because it is a private constructor(see here ).

2.

The internalPointer is a variable that stores the memory position of the data, that is, the QModelIndex does not have the data but knows where it is, so the data must be stored separately, so when the data is obtained using the method data() of the model you must obtain the internalPointer() that returns the item where the information is stored, and according to that the role obtain the data.

def data(self, index, role=QtCore.Qt.DisplayRole):
    if not index.isValid():
        return
    item = index.internalPointer()
    value = item.data(..., role) # like item.value
    return value

3.

The data() method of the QModelIndex uses the data() method of the model(see here ), so conceptually they are the same, like: def data(self, role): return self.model.data(self, role) .

Not every model is editable, for example QStringListModel is not, so it is not necessary that the setData() method be overwritten, so Qt by default makes the models not editable, that is, they do nothing and return false(see here ), So that means that it must be virtual , that is, this method will only be modified if it is not overridden and if you do not do it, it will be called the parent's method, that is, it will not do anything. Unlike the data() method since every model must return the information so the developer must be forced to overwrite that class when it inherits from QAbstractItemModel, so it is declared pure virtual . I recommend you read so that you know in more detail the differences: C++ Virtual/Pure Virtual Explained

While @eyllanesc's answer is correct I struggled to understand it until I stared long and hard at this article before a pattern emerged. Therefore, I contribute to his answer in what I feel is a more logical way than the order I asked my questions.

  1. In spite of what its name suggests, QAbstractItemModel is better understood as an interface to the model data. Typically the root to the model data is a member of the QAbstractItemModel object, which acts as a wrapper of sorts for the model data. (A different approach would be required, for example, if the data were stored in a SQL database.) QAbstractItemModel also:

    • Defines the (hierarchical) relationship between the data components.

    • Provides functions for adding and removing rows and columns of data from the model. (This fact is key to understanding how QModelIndex is used.)

  2. QModelIndex is many things, but most importantly it contains the internal pointer to each data component, in addition to some information about current the position of the data component in the data hierarchy (the position can change). It should now be clear why the the docs state that:

Model indexes should be used immediately and then discarded. You should not rely on indexes to remain valid after calling model functions that change the structure of the model or delete items.

It is for this reason also why member functions of QAbstractItemModel that return a QModelIndex (eg QModelIndex::index() and QModelIndex::parent() ) have to create a fresh one everytime , using QAbstractItemModel::createIndex() .

Finally, as @eyllanesc said, no implementation of the setter QAbstractItemModel::setData( QModelIndex , value, role ) is mandated unless the data is editable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM