Results 1 to 6 of 6

Thread: QFileSystemModel /QTreeView, check and expend all items under a user checked item

  1. #1
    Join Date
    Feb 2013
    Location
    San Diego
    Posts
    37
    Thanks
    14
    Thanked 7 Times in 7 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Windows

    Default QFileSystemModel /QTreeView, check and expend all items under a user checked item

    I’m having an issue for a while with a QFileSystemModel /QTreeView implementation. What I’m trying to achieve form the application user prospective. I have a QTreeView displaying the file system it only shows the directory name and file names. Each item in the view is checkable. That part is easy to design but what comes next is much harder. I’d like the application automatically expend and check all the items in all sub-directories if the user checks a directory. And I’d like it to automatically uncheck all the items in all sub-directories if the user unchecks a directory too.

    I have been trying to implement the second part for a while. I searched the forum and the internet for hints and I found that other people had the same problem and could not find solid answers.Some people tried to iterate recursively through the indexes in the model starting from the parent index associated with the checked directory in the view. (If the children in level#2 are parents then we iterate through them, and so on in all the sub level, until there is no more parents to iterate through.) And most people have issues with:
    Int rowCount(const QModelIndex & parent = QModelIndex()) const
    In my case, it returns inconsistent results (often 1 or 0) even if there are many more child elements under the parent element.

    Here is one of the unsuccessful attempts I made to check all the elements under the user selected directory in my QFileSystemModel subclass:
    Qt Code:
    1. bool FileSysSelectModel::setData(const QModelIndex &index, const QVariant &value, int role)
    2. {
    3. if (role == Qt::CheckStateRole)
    4. {
    5.  
    6. //if the current item is in the checklist then remove it.
    7. //if the current item is not in the checklist then add it.
    8. if(value == Qt::Checked) m_checkTable.insert(index); else m_checkTable.remove(index);
    9. emit dataChanged(index, index);
    10.  
    11. QModelIndex *currentIndex;
    12. QModelIndex *currentChildIndex;
    13. std::vector<QModelIndex*> parentTable(1,&const_cast<QModelIndex&>(index));
    14.  
    15. //Check all the sub-items.
    16. while (!parentTable.empty())
    17. {
    18. currentIndex = parentTable[parentTable.size()-1];
    19. parentTable.pop_back();
    20. if(value == Qt::Checked) m_checkTable.insert(*currentIndex); else m_checkTable.remove(*currentIndex);
    21. emit dataChanged(*currentIndex, *currentIndex);
    22. if (hasChildren(*currentIndex))
    23. {
    24.  
    25. for (int i(0); i < rowCount(*currentIndex); i++)
    26. {
    27. currentChildIndex = &FileSysSelectModel::index(i,0,*currentIndex);
    28. parentTable.push_back(currentChildIndex);
    29. }
    30. }
    31. }
    32. return true;
    33. }
    34. // regular QFileSystemModel case.
    35. return QFileSystemModel::setData(index, value, role)
    To copy to clipboard, switch view to plain text mode 
    Question1: Does the data in the tree model depend on the exposed items in the tree view?
    For example, does the data corresponding to a collapsed branch exist in the model? I believe it does not and I think that the model retrieves the data corresponding to a directory only when the user asks for expending that same directory in the tree view. I’d like to confirm that because the QFileSystemModel doc says very little about it and it not clear to me at all.

    Question2: If my assumption in question 1 is correct, how do we access the data of the collapsed branches in order to iterate through them?

    A few people mentioned the use of the two following methodes to resolve the issue in one or two threads I’ve seen:
    • Virtual Bool canFetchMore(const QModelIndex & parent) const
    • Virtual void fetchMore(const QModelIndex & parent)

    Here again, the documentation does not say much about it.

    Question 3: From my understanding fetchMore populates the data under the parent QModelIndex and adds it to the model. Is that correct?
    Here is another attempt. I tried to rework the same piece of code but it does not do better:
    Qt Code:
    1. //Check all the sub-items.
    2. while (!parentTable.empty())
    3. {
    4. currentIndex = parentTable[parentTable.size()-1];
    5. parentTable.pop_back();
    6. if(value == Qt::Checked) m_checkTable.insert(*currentIndex); else m_checkTable.remove(*currentIndex);
    7. emit dataChanged(*currentIndex, *currentIndex);
    8. if (hasChildren(*currentIndex))
    9. {
    10. while (canFetchMore(*currentIndex))
    11. {
    12. fetchMore(*currentIndex);
    13. }
    14.  
    15. for (int i(0); i < rowCount(*currentIndex); i++)
    16. {
    17. currentChildIndex = &FileSysSelectModel::index(i,0,*currentIndex);
    18. parentTable.push_back(currentChildIndex);
    19. }
    20. }
    21. }
    To copy to clipboard, switch view to plain text mode 
    Some fetchMore users say that the reason why that kind of implementation does not work is because the FileSystemModel updated its data with an independent thread, so when the main thread executes rowCount, fetchMore has not finished updating the data.
    I tried to use the directoryLoaded(const QString & path) signal by implementing the SLOT below in my FileSystemModel sub class to make sure fetchMore more had finish executing when calling rowCount.
    Qt Code:
    1. void FileSysSelectModel::dataReady(const QString &currentPath)
    2. {
    3. m_currentPath = currentPath;
    4. }
    To copy to clipboard, switch view to plain text mode 
    Qt Code:
    1. //Check all the sub-items.
    2. while (!parentTable.empty())
    3. {
    4. currentIndex = parentTable[parentTable.size()-1];
    5. parentTable.pop_back();
    6. if(value == Qt::Checked) m_checkTable.insert(*currentIndex); else m_checkTable.remove(*currentIndex);
    7. emit dataChanged(*currentIndex, *currentIndex);
    8. if (hasChildren(*currentIndex))
    9. {
    10. QString test1 = filePath(*currentIndex); //debug
    11. while ((canFetchMore(*currentIndex)) || (m_currentPath != filePath(*currentIndex)))
    12. {
    13. fetchMore(*currentIndex);
    14. }
    15.  
    16. for (int i(0); i < rowCount(*currentIndex); i++)
    17. {
    18. currentChildIndex = &FileSysSelectModel::index(i,0,*currentIndex);
    19. parentTable.push_back(currentChildIndex);
    20. }
    21. }
    22. }
    23. return true;
    To copy to clipboard, switch view to plain text mode 
    That does not work either.
    Any idea?

    Thanks

  2. The following user says thank you to Guett_31 for this useful post:


  3. #2
    Join Date
    Mar 2009
    Location
    Brisbane, Australia
    Posts
    7,729
    Thanks
    13
    Thanked 1,610 Times in 1,537 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows
    Wiki edits
    17

    Default Re: QFileSystemModel /QTreeView, check and expend all items under a user checked item

    QModelIndex is a value class. No need for all the pointer stuff which, AFAICT, is just asking for crashes given you are not allocating them on the heap..

    QFileSystemModel is documented as using a thread to load data to allow for responsiveness and memory minimisation in the face of 1000000+ file directory trees (like mine). This loading is lazy, i.e. not occurring until a view/program requests some part of the tree that has not been loaded. A call to fetchMore() triggers the load of the immediate children only and emits directoryLoaded() when it is done. You need to account for the asynchronous nature of this process.

  4. The following user says thank you to ChrisW67 for this useful post:


  5. #3
    Join Date
    Feb 2013
    Location
    San Diego
    Posts
    37
    Thanks
    14
    Thanked 7 Times in 7 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Windows

    Default Re: QFileSystemModel /QTreeView, check and expend all items under a user checked item

    Thank you for you answer. It confirms that the use of fetchMore() and directoryLoaded() is the way to go in that case.

    Please, could you tell me more about your first statement? I don't really understand what that means:
    "QModelIndex is a value class. No need for all the pointer stuff which, AFAICT, is just asking for crashes given you are not allocating them on the heap.."

    I don't know what a "value class" is. How does that affect my code?

    Thanks,

  6. The following user says thank you to Guett_31 for this useful post:


  7. #4
    Join Date
    Mar 2009
    Location
    Brisbane, Australia
    Posts
    7,729
    Thanks
    13
    Thanked 1,610 Times in 1,537 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Unix/X11 Windows
    Wiki edits
    17

    Default Re: QFileSystemModel /QTreeView, check and expend all items under a user checked item

    By value class I mean a class that is intended to convey a value around, much like an intrinsic int or float type. The class is designed to be copied and treated much like the intrinsic types. QString is another example. Generally two distinct value objects are considered equal if they convey the same meaning as a value.

    Objects like QWidget are not value classes, they cannot be copied, and two objects are distinct even if e.g. they represent a label with precisely the same text on it. Generally these objects travel around in Qt programs as pointers.


    Firstly, you are holding pointers to QModelIndex in your vector and having to constantly dereference them, rather than simply holding the indexes themselves. Not an error... just makes the code uglier than it need be and doesn't buy you anything. It does leave you open to the second point though.

    Secondly, and more dangerously, you are holding pointers to temporary QModelIndex instances and using them later. Take lines 27 and 28 of your first listing:
    Qt Code:
    1. currentChildIndex = &FileSysSelectModel::index(i,0,*currentIndex);
    2. parentTable.push_back(currentChildIndex);
    To copy to clipboard, switch view to plain text mode 
    The call to index() returns a temporary QModelIndex, you take the address of that return value and store it in currentChildIndex, the temporary return value is then destroyed at the end of the statement call. You are left with a classic dangling pointer that points to memory that may no longer hold anything resembling correct data. Keeping a pointer to an object does not ensure the object persists. The normal usage pattern of:
    Qt Code:
    1. {
    2. QModelIndex currentChildIndex = index(i,0,currentIndex); // copies the return value
    3. parentTable.push_back(currentChildIndex); // puts another copy into the list
    4. }
    5. // currentChildIndex is now out-of-scope and destroyed, the copy in the list still exists
    To copy to clipboard, switch view to plain text mode 
    takes a copy of the returned object that persists past the end of statement for as long as you keep it in scope.

  8. The following 2 users say thank you to ChrisW67 for this useful post:

    Guett_31 (15th March 2013)

  9. #5
    Join Date
    Feb 2013
    Location
    San Diego
    Posts
    37
    Thanks
    14
    Thanked 7 Times in 7 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Windows

    Default Re: QFileSystemModel /QTreeView, check and expend all items under a user checked item

    Thank you very much for this very clear and detailed info about “value class”.
    It seems to be some very basic knowledge but I could not find any valuable info about it anywhere else. One more question about that. Is there something in the class prototype or somewhere else that tells it is a “value class”? I would like to able to avoid the same kind of mistake in the future.

    Secondly, as you suggested, I took out all the pointer stuff to be able to keep copies of the indexes generated in the “For loop” even when the original indexes are destroyed. But I’m still having a major issue with the fetchMore() function.
    Has you can see in the code below, I have implemented a dataReady(const QString &) Slot in my FileSystemModel sub class that is just designed to save the path of the last loaded directory in the m_currentPath member variable. In FileSysSelectModel::setData(), I call fetchMore(currentIndex) and I “wait” until m_currentPath is equals to the currentIndex’s path (meaning that the data under currentIndex has finished loading and that I’m ready to use the rowCount() function).
    Qt Code:
    1. bool FileSysSelectModel::setData(const QModelIndex &index, const QVariant &value, int role)
    2. {
    3. if (role == Qt::CheckStateRole)
    4. {
    5.  
    6. //if the current item is in the checklist then remove it.
    7. //if the current item is not in the checklist then add it.
    8. if(value == Qt::Checked) m_checkTable.insert(index); else m_checkTable.remove(index);
    9. emit dataChanged(index, index);
    10.  
    11. QModelIndex currentIndex;
    12. QModelIndex currentChildIndex;
    13. std::vector<QModelIndex> parentTable(1,index);
    14.  
    15. //Check all the sub-items.
    16. while (!parentTable.empty())
    17. {
    18. currentIndex = parentTable[parentTable.size()-1];
    19. parentTable.pop_back();
    20. if(value == Qt::Checked) m_checkTable.insert(currentIndex); else m_checkTable.remove(currentIndex);
    21. emit dataChanged(currentIndex, currentIndex);
    22. if (hasChildren(currentIndex))
    23. {
    24.  
    25. // Load the currentIndex children and wait until it has finished loading.
    26. while (m_currentPath != filePath(currentIndex))
    27. {
    28. if (canFetchMore(currentIndex)) fetchMore(currentIndex);
    29. }
    30.  
    31. // Explore the currentIndex children and hold the ones that are parents themselves.
    32. for (int i(0); i < rowCount(currentIndex); i++)
    33. {
    34. currentChildIndex = FileSysSelectModel::index(i,0,currentIndex);
    35. parentTable.push_back(currentChildIndex);
    36. }
    37. }
    38. }
    39. return true;
    40. }
    41. // regular QFileSystemModel case.
    42. return QFileSystemModel::setData(index, value, role);
    43. }
    44.  
    45.  
    46. void FileSysSelectModel::dataReady(const QString &currentPath)
    47. {
    48. m_currentPath = currentPath;
    49. }
    To copy to clipboard, switch view to plain text mode 
    The issue is that I’m running in an infinite loop. I never receive a directoryLoaded() signal when I call fetchMore(). I've put a breakpoint in the FileSysSelectModel::dataReady() Slot and it never stops there after calling fetchMore() However, the execution does stop on the break point whenever I expend a branch in the QTreeView and I do get the path of the expended branch too, as expected. That means that the Signal, the Slot and the Connection work properly. It must come from fetchMore() itself or the way I use it. But I have no idea what is wrong…

    Thanks,

  10. The following user says thank you to Guett_31 for this useful post:


  11. #6
    Join Date
    Feb 2013
    Location
    San Diego
    Posts
    37
    Thanks
    14
    Thanked 7 Times in 7 Posts
    Qt products
    Qt4 Qt5
    Platforms
    Windows

    Default Re: QFileSystemModel /QTreeView, check and expend all items under a user checked item

    Hi,
    I'm still stuck with that issue. I'm expecting a directoryLoaded() signal after I called fetchMore() and it never comes.

    A directoryLoaded() is emited when I expand an item in the QtreeView though, so I decided to look into QtreeView.cpp to see how the fetchMore() calls are implemented.
    I looked at the expand all implementation, because it is very close to what I want to acheive after all. (Expand all the items under a user checked item)

    Actually QTreeView::expandAll() calls a generic member of its private Class, QTreeViewPrivate::layout(int i, bool recursiveExpanding, bool afterIsUninitialized), which runs recursively and which calls fetchmore()
    as follows:
    Qt Code:
    1. int count = 0;
    2. if (model->hasChildren(parent)) {
    3. if (model->canFetchMore(parent))
    4. model->fetchMore(parent);
    5. count = model->rowCount(parent);
    6. }
    To copy to clipboard, switch view to plain text mode 
    I don't get it. I've seen in several other threads pepole complaining about unexpected rowcount() returned values. And the given explanation for that behavior was the asynchorneous nature of the QFileSystemModel data update process. (The data is still being updated when rowCount() is called.)
    That was the all point of what I'm trying to do, making sure that the data is updateed when I call fetchmore(). (See code in my previous message)
    By looking a the QtreeVieww implementation, rowCount() is called right after fetchMore(). It doesn't seem to be a problem, execpt that when I try the same implentation in my code I end up with the same unexpected count value. And when I try to detect directoryLoaded() after calling fetchmore(), I never get it... I'm stuck!

    Could someone help me with that? Thanks

  12. The following user says thank you to Guett_31 for this useful post:


Similar Threads

  1. Replies: 2
    Last Post: 17th May 2011, 14:47
  2. QTreeView different item delegate for child items possible?
    By Royceybaby in forum Qt Programming
    Replies: 4
    Last Post: 7th January 2010, 22:14
  3. Replies: 2
    Last Post: 13th September 2008, 14:55
  4. Checked item in QTreeWidget?
    By vishal.chauhan in forum Qt Programming
    Replies: 18
    Last Post: 3rd January 2008, 21:18
  5. Iterate and get Checked QTable items??
    By darpan in forum Newbie
    Replies: 2
    Last Post: 10th May 2006, 19:27

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.