PDA

View Full Version : Model/View synchronization issue. Need a QThread?



Guett_31
1st March 2013, 23:40
Hello,

I have a QFileSystemModel sub-class “FileSysSelectModel” connected to a QTreeView
The QTreeView shows only the file name information and each item is checkable.

Here is how the QFileSystemModel sub_class is implemented.

#ifndef FILESYSSELECTMODEL_H
#define FILESYSSELECTMODEL_H

#include<QFileSystemModel>
#include <QSet>
#include <QPersistentModelIndex>
#include <QVariant>

class FileSysSelectModel: public QFileSystemModel
{
Q_OBJECT

private:
QSet<QPersistentModelIndex> m_checkTable;
QVariant m_noData;

public:
int FileSysSelectModel::columnCount(const QModelIndex &parent) const;
QVariant FileSysSelectModel::data(const QModelIndex &index, int role) const;
virtual Qt::ItemFlags FileSysSelectModel::flags(const QModelIndex &index) const;
virtual bool FileSysSelectModel::setData(const QModelIndex &index, const QVariant &value, int role);

signals:
void expandParentItem(const QModelIndex &index);
};

#endif // FILESYSSELECTMODEL_H

#include "FileSysSelectModel.h"

//Returns columnCount=1 if columnCount >= 1, so only the name item displays.
int FileSysSelectModel::columnCount(const QModelIndex &parent) const
{
int columns = QFileSystemModel::columnCount(parent);
return (columns >= 1)? 1 : columns;
}


//Returns the regular QFileSystemModel data or the CheckState status if required.
//All decoration has been removed from QFileSystemModel data.
QVariant FileSysSelectModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::CheckStateRole) return m_checkTable.contains(index)? Qt::Checked : Qt::Unchecked;
if (role == Qt::DecorationRole) return m_noData;
// regular QFileSystemModel case.
return QFileSystemModel::data(index, role);
}


//Sets all elements as user checkable.
Qt::ItemFlags FileSysSelectModel::flags(const QModelIndex &index) const
{
return QFileSystemModel::flags(index) | Qt::ItemIsUserCheckable;
}


//Sets the regular QFileSystemModel data or the CheckState status.
bool FileSysSelectModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role == Qt::CheckStateRole)
{
QModelIndex *currentFirstChildItem;
QModelIndex *currentItem;
QModelIndex *currentChildItem;
std::vector<QModelIndex*> parentTable(1,&const_cast<QModelIndex&>(index));

//if the current item is in the checklist then remove it.
//if the current item is not in the checklist then add it.
if(value == Qt::Checked) m_checkTable.insert(index); else m_checkTable.remove(index);
emit dataChanged(index, index);

//Expand all the sub-items.
while (!parentTable.empty())
{
currentItem = parentTable[parentTable.size()-1];
parentTable.pop_back();
currentFirstChildItem = &currentItem->child(0,0);

if (currentFirstChildItem->isValid())
{
emit expandParentItem(*currentItem);
int i = 0;
while(1)
{
currentChildItem = &currentItem->child(i,0);
if (!currentChildItem->isValid()) break;
parentTable.push_back(currentChildItem);
++i;
}
}
}
return true;
}
// regular QFileSystemModel case.
return QFileSystemModel::setData(index, value, role);
}
Here is my problem.
When I check an item, I would like the QTreeView to automatically expand all the checked item’s children (in all sub levels).
I tried to implement a mechanism in FileSysSelectModel::setData. It uses a While loop and a vector < QModelIndex* > to process and expand each item in the sub-tree under the check item.
I initialize the vector with the checked item. The While loop runs until the vector is empty. And here is what is going on inside the loop. The currently processed item is removed from the vector. If it has children, it expands it, it explores its children and it saves all ones that have children in the vector (for them to be processed on a next round).

It does not run as expected… When I check an item it only expands the first level under the checked item.
When running it with the debugger, I figured out that the all the signals that were emitted in the while loop were processed by the QTreeView slot only after the while loop terminated… That is probably where the issue is.

I need the QTreeView to check for signals for each iteration in the While loop, right?
Is a QThread the only solution for that issue?
If yes, Do I need to implement the entire QFileSystemModel instance in a sub-QThread? Or only a portion of it?

Thanks!

wysota
2nd March 2013, 00:27
When I check an item, I would like the QTreeView to automatically expand all the checked item’s children (in all sub levels).
I tried to implement a mechanism in FileSysSelectModel::setData.
If you want the view to do something, why are you trying to implement that in the model and not in the view?

Connect to the dataChanged() signal of the model, detect a situation when an item was checked and iterate through its children expanding them the way you want.


Is a QThread the only solution for that issue?
You can't use threads here.

Guett_31
2nd March 2013, 01:22
I'm probably having a logic issue here... I don't understand why you want to use the dataChanged() signal to tell the view to expand items.

The data does not change, except for the checked item. For that, I do use dataChanged() to tell the view that this only one item has changed. And that is before entering the While loop. Once in the While loop, the data part remains the same. The only thing that changes is how we look at the data through the view by expanding or to collapsing items.
That is why I use my own signal here "expandParentItem" to the the view to expand the items.
All I do in the loop is exploring the data to determine its structure and I just want to tell the view to expand any item that has children...

Here is how the signal is connected in my Main Window:

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);

m_fileSystemModel = new FileSysSelectModel;
m_fileSystemModel->setRootPath(QDir::currentPath());
ui->scriptFileView->setModel(m_fileSystemModel);

QObject::connect(m_fileSystemModel,SIGNAL(expandPa rentItem(QModelIndex)),ui->scriptFileView,SLOT(expand(QModelIndex)));
}

MainWindow::~MainWindow()
{
delete ui;
}

ChrisW67
2nd March 2013, 01:42
Precisely, the data does not change (apart from the Qt::CheckState obviously). The model is all about the data. The while loop you refer to is in the model which is not required to change, i.e. the wrong place.

Expanding or collapsing a branch is about changing how data is presented. The view is all about how that data is presented. You want the view to expand the branch underneath an item when it is checked. The model's QAbtractItemModel::dataChanged() signal allows the view to know that a particular model item changed, and the view can choose to react to that by QTreeView::expand()ing the affected branch (if there is one) etc. Your view can then expand no matter how the check state in the model is changed; whether by your user interacting with this view, another view on the same model, or if the check state changes programatically.

You don't explain why you want this behaviour tied to check state. The default expansion controls already give the user a way to expand a branch. If you want expanding to cascade you might be able to get the result you seek by using the view's own expanded() signal to trigger a routine to expand subordinate branches. This logic could be entirely with the view.

wysota
2nd March 2013, 02:00
One additional thing to question which you should answer to understand that your approach is not correct -- what happens if your model is connected to two or more views? Why would all views on the model have to expand their items?

Guett_31
2nd March 2013, 03:29
Thank you guys for your helpful answers. I understand my mistake now. The mechanism with the While loop needs to be located within the QTreeView instance, and it needs to be triggered by a signal on the model side that is emitted when the Qt::CheckState is modified.

The problem I'm facing now is that the QTreeView instance lives in a Form that I have created with Qt Designer. From what I know about Qt Designer (which is not much...), it only allows the use of regular Qt classes. Is there a way I can create a sub QTreeView class with the mechanism I mentioned above and use it in the Form?

ChrisW67
2nd March 2013, 05:56
Look for Promote To on the tree widget context menu in Designer and in the Designer manual