[SOLVED] QTreeView, QAbstractItemModel and selections



  • I have wrote a model class which represents a two-level structure of «architectures» and «files»:

    • arch1
      ** file1
      ** file2
    • arch2
      ** file3
      ** file4
    • ...

    I have implemented all required methods in the model: index(), parent(), rowCount(), columnCount(), and data(). Currently, the tree is rendered exactly the way I need, and clicking on items selects them, so I can connect some code to QTreeView's activated() signal and perform some actions when users selects an item. But visually the selected item remains unselected unless it is a first-level item («architecture»). This doesn't depend on skin or anything else.

    When I select an «architecture» with mouse and then try to choose a «file» using arrow key, the application writes to console:
    Can't select indexes from different model or with different parents
    What does it mean?

    (By the way, expandAll() doesn't seem to work on my model. I don't know if it is caused by the same problem or not.)

    If somebody has enough time to read the whole code of my model, here is it: "fileslistmodel.h":http://pastebin.com/JpkSX87z, "fileslistmodel.cpp":http://pastebin.com/epgw4Nc5.



  • I've found my problem!

    My index() and parent() were made in such way so they could return different pointers for the same QModelIndex. But QTreeView uses the internal pointer to compare items, and it caused incorrect selection behavior.

    So, I just needed to add a sort of cache where I store all created internal pointers, and when a pointer for given QModelIndex exists there, I use it instead of creating a new one.

    Here's an example from another QAbstractItemModel-based class where I solved similar problem:

    packagestreemodel.h
    @
    // ...
    private:
    mutable QMap<Package*,PodWrapper*> _wrappedPackages;
    PodWrapper* wrap(Package* package) const;
    // ...
    @

    packagestreemodel.cpp:
    @
    // ...
    QModelIndex PackagesTreeModel::index(int row, int column, const QModelIndex& parent) const
    {
    // ...
    return createIndex(row, column, wrap(package));
    // PodWrapper is my own class.
    // I had "new PodWrapper(package)" here before,
    // so it returned different pointers every time.
    // Now I use "wrap(package)" that uses cache (see below).
    }

    PodWrapper* PackagesTreeModel::wrap(Package* package) const
    {
    if (!_wrappedPackages.contains(package))
    _wrappedPackages[package] = new PodWrapper(package);
    return _wrappedPackages[package];
    }
    ...
    @

    Note that index() is a const method, so all methods called by it must be const, too, and the cache map (_wrappedPackages in my example) must be mutable in order to be changed by this method. Otherwise, you will get compiler errors.



  • I've been looking at the same error messages for years, so when I found your post I thought I might try to resolve the problem using the results of your investigations.

    You write,

    bq. So, I just needed to add a sort of cache where I store all created internal pointers, and when a pointer for given QModelIndex exists there, I use it instead of creating a new one.

    This suggests that the wrapping should be done inside

    @Node *Model::nodeFromIndex(const QModelIndex &index)const;@

    which is where

    @void* QModelIndex::internalPointer()const; @

    is actually called. I did it like so

    @Node myNode;
    mutable QMap<QModelIndex,void
    > myIndex2NodeCache;
    Node Model::nodeFromIndex(const QModelIndex &index){
    if(index.isValid()){
    if(!myIndex2NodeCache.contains(index))
    myIndex2NodeCache[index]=index.internalPointer();
    return static_cast<Node
    >(myIndex2NodeCache[index]);
    }

    else return myRoot;
    }@

    Sadly, the behavior doesn't change even though the internal pointers are now cached rather than being recalculated whenever index() or parent() needs an internal pointer.

    I looked at your code and was lost trying to sort out Package* and PodWrapper* in the context of a QAbstractItemModel or your initial example. While I can't sort out the class names, it seems clear that you've invented a wrapper class PodWrapper that is constructed using a pointer to your underlying data class Package. Does PodWrapper clone Package? Or just contain a pointer the Package as its only attribute? Is there a 1:1 correspondence between instances of Package and PodWrapper? Also, there seems to be no correspondence between the wrap() function in Model, which does the caching and the name PodWrapper, which wraps Package, but not in the caching sense you mean. I think.

    I'm sure my questions betray a deep ignorance. If you post a links to your Model.h, Model.cpp, Node.h/Package.h, Node.cpp/Package.cpp (and evidently also your PodWrapper.h and PodWrapper.cpp files) I may be able to figure out what you are doing.

    But this much is true: you seem to be the only one who has treated this problem with the attention and care it deserves. It was worth my registering to qt-project to improve my understanding your thinking.

    :)



  • Hello.

    In my specific case, PodWrapper means "Package or Distro wrapper", I needed it in order to wrap a Package* or a Distro* as a void*. Its code is as simple as "this":https://bitbucket.org/maaaks/tainer/src/813c41e609d4884d0af32eaab8a018b52d035f40/ui/models/packagestreemodel.h?at=master#cl-14. I used to create a new PodWrapper every time where I needed it, and I thought that Qt would compare different instantiantions of it by value, but it seems to just compare pointers, so two totally identical PodWrappers (containing the same void* indise) consider non-equal.

    Your code seems to be the same as mine now. Sorry, but I don't know why behavior doesn't change. :( Don't you have other places where cache isn't used?

    (By the way, I don't understand: if every your node is a Node*, why do you store void* values instead of Node* in your QMap? I use PodWrapper because I want to have objects of different classes in the tree: some nodes are "Packages", and some are "Distros". If you can use Node* everywhere, why not using Node* directly everywhere? :-) )



  • I had the same Problem. My fault was to save the same pointer for two QModelIndex, for different columns. So row 0, column 0 and 1 had the same internal pointer. In addition i have used createIndex() in the wrong way. My row and column from index was not the same as in parent. If you get this error, maybe your row column index is not right or your pointer is not unique.



  • As far as I understand, the main idea is: make sure that internal pointers are always equal for the same place in the tree structure and always different for different places.



  • Hi Maaaks I have the same problem for a two-level structure like the one of your first post.
    In my case the model index is unique for each element, i use createIndex(row, column, id) where id is an integer =0 when the element is first-level like "arch" in your example and it is =(parent.row()+1) when the element is second-level like "file" for you.
    So I didn't use an internal pointer but an internal Id that identifies the parent, however assuring the 3 values that make an index (row column and id) are the same for the same place and different for two different items.
    I still have the Can't select indexes from different model or with different parents message when clicking on second-level items.
    I also have tried to make the internal id unique but the behavior does not change.
    Any idea?



  • I have solved.
    In the implementation of QAbstractItemModel::parent() , i was doing return createIndex( parent_row , index.colum() ) . In this case the parent is not the same when selecting all cells in a row (default selection mode)!
    So the parent should always be column 0, and the correct implementations should return createIndex( parent_row , 0 ).

    Hope it is useful for others.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.