I want to select some different folders in the treeview. There are two solution in QT like this:
QTreeView + QFileSystemModel, But how add the treebox in it? I dotn't know at all. In the same time, QFileSystemModel is asychronised, so after you choose a folder, then expand the directory, you will find the sub-folder were not choosen. How can I solve the this problem?
QTreeView + QDirModel, there is a good model and it work well:
http://www.programmershare.com/2041913/
But QDirModel is synchronised. So we have to wait a long time when choose a big folder. We can accept a long time, but how I can know the selection is finished?
Thanks anyway.
Your example should be tweaked a bit to use QFileSystemModel.
The trick is to declare the checkedIndexes set as mutable and update it inside the CFileSystemModel::data method.
QVariant CFileSystemModel::data(const QModelIndex &index, int role) const
{
if(role == Qt::CheckStateRole)
{
if (checkedIndexes.contains(index))
{
return checkedIndexes.contains(index) ? Qt::Checked : Qt::Unchecked;
}
else
{
int checked = Qt::Unchecked;
QModelIndex parent = index.parent();
while (parent.isValid())
{
if (checkedIndexes.contains(parent))
{
checked = Qt::Checked;
break;
}
parent = parent.parent();
}
if (checked == Qt::Checked)
{
checkedIndexes.insert(parent);
}
return checked;
}
}
else
{
return QFileSystemModel::data(index, role);
}
}
When you open a directory node in a view, QFileSystemModel starts to load new contents. After they are loaded, the view retieves new data using CFileSystemModel::data function, which checks if new nodes anchestors were checked and returns proper Qt::CheckStateRole value (and also updates the checkedIndexes set).
Related
I'm developing a Qt C++ application in Unix and I've been trying to do something similar to what this image shows:
As you can see, there is a list of files and folders and a user can select multiple of them (if a folder is selected, all childs also get selected). I don't really care if the folder/file icons are shown.
I was able to create a list of QDir which stores all the files and folders paths given a root path. The problem is that I don't really know which widgets to use to design the selection panel.
By the way, the lis of QDir is a vector, but it can be easily modified to anything else.
Thanks!
You can try to make proxy model for QFileSystemModel, override flags() with Qt::ItemIsUserCheckable, override setData() and apply the model to QTreeView. Full example can be found at https://github.com/em2er/filesysmodel. This code is just a concept, i have not tested it thoroughly, but you can take some ideas from it. It will look smth like on the screenshot:
.
Also you can combine it with Merged Proxy Model to display multiple starting paths at one view.
You might want to consider the QTreeWidget, or it's a tad more advanced version - QTreeView and an appropriate data model.
As some users suggested, I ended up using QFileSystemModel. I'm gonna give a full description of how I implemented it, in case someone else comes up with this problem and needs a clear response.
First of all, a QFileSystemModel is a file tree without checkboxes, to add them, a new class which extends QFileSystemModel and at least 3 methods must be overriden.
class FileSelector : public QFileSystemModel
{
public:
FileSelector(const char *rootPath, QObject *parent = nullptr);
~FileSelector();
bool setData(const QModelIndex& index, const QVariant& value, int role);
Qt::ItemFlags flags(const QModelIndex& index) const;
QVariant data(const QModelIndex& index, int role) const;
private:
QObject *parent_;
/* checklist_ stores all the elements which have been marked as checked */
QSet<QPersistentModelIndex> checklist_;
};
When creating the model a flag, to indicate that it should have a checkable box, must be set. This is why we will use the flags function:
Qt::ItemFlags FileSelector::flags(const QModelIndex& index) const
{
return QFileSystemModel::flags(index) | Qt::ItemIsUserCheckable;
}
When a click is made in the checkbox, the method setData will be called, with the index of the element that was clicked (not the checkbox itself, but the :
bool FileSelector::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (role == Qt::CheckStateRole && index.column() == 0) {
QModelIndexList list;
getAllChildren(index, list); // this function gets all children
// given the index and saves them into list (also saves index in the head)
if(value == Qt::Checked)
{
for(int i = 0; i < list.size(); i++)
{
checklist_.insert(list[i]);
// signals that a change has been made
emit dataChanged(list[i], list[i]);
}
}
else if(value == Qt::Unchecked)
{
for(int i = 0; i < list.size(); i++)
{
checklist_.remove(list[i]);
emit dataChanged(list[i], list[i]);
}
}
return true;
}
return QFileSystemModel::setData(index, value, role);
}
When dataChanged is signaled or you open a new path of the tree, the data function will be called. Here you have to make sure to only display the checkbox at the first column (next to the filename), and to retrieve the state of the checkbox, to mark it as checked/unchecked.
QVariant FileSelector::data(const QModelIndex& index, int role) const
{
if (role == Qt::CheckStateRole && index.column() == 0) {
if(checklist_.contains(index)) return Qt::Checked;
else return Qt::Unchecked;
}
return QFileSystemModel::data(index, role);
}
The only thing I was not able to accomplish was getting all childs, since the folders must be open to retrieve the childs. So a closed folder won't have any child until you open it.
Hope this can help someone who has the same problem as I did!
I'm using this code to query sqlite and put the results in a QTableView.
//MainWindow.cpp
void MainWindow::on_pushButton_clicked()
{
QSqlQueryModel * modal=new QSqlQueryModel();
connOpen();
QSqlQuery* qry=new QSqlQuery(mydb);
qry->prepare("select * from database");
qry->exec();
modal->setQuery(*qry);
//from stack
modal->insertColumn(0);
ui->tableView->setModel(modal);
//from stack
ui->tableView->resizeColumnsToContents();
int p;
for(p=0; p<modal->rowCount(); p++)
{
ui->tableView->setIndexWidget(modal->index(p,0),new QCheckBox());
}
connClose();
qDebug() <<(modal->rowCount());
}
I've seen several examples of the web for adding checkboxes to a column, but I'm not quite sure what to use for my simple example.
This answer suggests a few lines that doesn't seem standard.
There are more examples like this and this one that appear to outline what I need, but it's unclear to where you place the code.
What I intend to do is to have column 1 checkable. On next btn press, If checked those rows of data get written to a file.
I still need to understand how to loop thru the selected data, or perhaps I need to get the ids of the checked rows and do another query.
Questions:
How do you add 1 column of editable checkboxes to QTableView?
How do you loop through values in the QTableView data, so values of the checked rows can be accessed?
How do you check all/none?
I think the best way to have a column of checkable cells is to create your item model, e.g. by subclassing the QSqlQueryModel.
You must reimplement the flags() method to make checkable the cells.
Also you need to reimplement the data() method to return the check state and the setData() method and to set the check state. You must implement your own logic to keep track of the check state of every rows (e.g. using an array of Qt::CheckState that you must initialize and resize when the model data changes).
Yuo can start with something like this:
class MyModel : public QSqlQueryModel
{
public:
Qt::ItemFlags flags(const QModelIndex & index) const
{
if(index.column() == 0)
return QSqlQueryModel::flags(index) | Qt::ItemIsUserCheckable;
return QSqlQueryModel::flags(index);
}
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const
{
if(index.column() == 0 && role == Qt::CheckStateRole)
{
//implement your logic to return the check state
//....
}
else
return QSqlQueryModel::data(index, role);
}
bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole)
{
if(index.column() == 0 && role == Qt::CheckStateRole)
{
//implement your logic to set the check state
//....
}
else
QSqlQueryModel::setData(index, value, role);
}
};
Se also:
Model Subclassing
QAbstractItemModel documentation
I've been working on a simple QTreeView of a local directory. The goal is allow the user to browse to his/her directory and select the correct csv file.
I've created a QFileSystemModel and displayed it with a QTreeView. I'm confused how to get the filename from the currently selected node.
I've read through the documentation and I've found the following signal/slot pairing:
connect(tree, SIGNAL(clicked(QModelIndex)), this, SLOT(handleTreeWidgetEvent(QModelIndex)));
But I'm not sure what to do with the QModelIndex once activated. I know you're supossed to index the QTreeView with this index, but I'm not sure how.
Any help is greatly appreciated.
EDIT: Adding code so people can see what I'm doing.
QFileSystemModel *model = new QFileSystemModel;
model->setRootPath("/");
tree = new QTreeView;
tree->setModel(model);
tree->setRootIndex(model->index("/home/Missions/"));
tree->setColumnWidth(0, 350);
connect(tree, SIGNAL(clicked(QModelIndex)), this, SLOT(handleTreeWidgetEvent(QModelIndex)));
WhatEverClassInheritingQObject::handleTreeWidgetEvent(const QModelIndex& index)
{
const QString valuablePathAskedFor(fileSystemModel->fileName(index));
...
}
you can retrieve the path as a QString in your setData method via filePath() method based just on the QModelIndex, so it will be called each time user has checked (or unchecked) some directory or file in your model being displayed, and then you need to store all these paths in some conatainer and implement method to return this:
bool MyQFileSystemModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.column() != 0 || role != Qt::CheckStateRole)
return QFileSystemModel::setData(index, value, role);
int newCheckState = value.toInt();
QString filePath = filePath(index);
if (newCheckState == Qt::Checked || newCheckState == Qt::PartiallyChecked )
checkedPaths.insert(filePath);
else
checkedPaths.remove(filePath);
emit dataChanged(index, index.child(index.row(),0));
return true;
}
class MyQFileSystemModel : public QFileSystemModel
{
Q_OBJECT
public:
//...
QSet<QString> getChecked() const { return checkedPaths; }
private:
QSet<QString> chackedPaths;
//...
};
From the Qt documentation:
QFileSystemModel will not fetch any files or directories until setRootPath() is called. This will prevent any unnecessary querying on the file system until that point such as listing the drives on Windows.
Unlike QDirModel(obsolete), QFileSystemModel uses a separate thread to populate itself so it will not cause the main thread to hang as the file system is being queried. Calls to rowCount() will return 0 until the model populates a directory. QFileSystemModel keeps a cache with file information. The cache is automatically kept up to date using the QFileSystemWatcher.
I'm using QTreeView together with a sub-classed QFileSystemModel which uses checkable boxes.
If I call QFileSystemModel::rowCount(index) before an item has been expanded in the tree I will receive '0', regardless of whether there are any subdirectories or files. However, once it has been expanded the correct row count will then be given when called again.
I think if you call QFileSystemModel::setRootPath() this will fetch the data from the specified file path, but it seems that it does not 'execute fast enough' (the cache is not updated) before I call QFileSystemModel::rowCount in my code below.
// Whenever a checkbox in the TreeView is clicked
bool MyModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (role == Qt::CheckStateRole)
{
if (value == Qt::Checked)
{
setRootPath(this->filePath(index));
checklist.insert(index);
set_children(index);
}
else
{
checklist.remove(index);
unchecklist->insert(index);
}
emit dataChanged(index, index);
return true;
}
return QFileSystemModel::setData(index, value, role);
}
// Counts how many items/children the node has (i.e. file/folders)
void MyModel::set_children(const QModelIndex& index)
{
int row = this->rowCount(index);
qDebug() << QString::number(row);
}
Is there a way I can pre-emptively gather the sub-folder information before I try to count how many items are contained in that folder?
Thanks
QFileSystemModel emits directoryLoaded(const QString &path) signal, when gather thread finished loading directory.
I have subclassed filesystemmodel to include checkboxes in listview , which is working fine. My problem is whenever I click an item the text of that item disappears and when i click another item the text of previously selected item becomes visible. Can anyone please tell me the reason behind it.
Here is the code i implemented.
Please tell me what i am missing here,
Thanks
#include "custommodel.h"
#include <iostream>
using namespace std;
CustomModel::CustomModel()
{
}
QVariant CustomModel::data(const QModelIndex& index, int role) const
{
QModelIndex parent=this->parent(index);
if(role == Qt::DecorationRole)
{
if(this->filePath(parent)=="")
{
return QIcon(":/Icons/HardDisk.png");
}
else if(this->isDir(index))
{
QDir dir(this->filePath(index));
QFileInfoList files = dir.entryInfoList(QDir::NoDotAndDotDot |
QDir::Files | QDir::Dirs);
for(int file = 0; file < files.count(); file++)
if(files.count()>0)
return QIcon(":/Icons/FullFolder.png");
if(files.count()==0)
return QIcon(":/Icons/EmptyFolder.png");
}
else{
QFileInfo fi( this->filePath(index));
QString ext = fi.suffix();
if(ext=="jpeg"||ext=="jpg"||ext=="png"||ext=="bmp")
return QIcon(filePath(index));
}
}
if (role == Qt::CheckStateRole && !(this->filePath(parent)==""))
return checklist.contains(index) ? Qt::Checked : Qt::Unchecked;
return QFileSystemModel::data(index, role);
}
Qt::ItemFlags CustomModel::flags(const QModelIndex& index) const
{
return QFileSystemModel::flags(index)| Qt::ItemIsUserCheckable;
}
bool CustomModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (role == Qt::CheckStateRole) {
if (value == Qt::Checked)
checklist.insert(index);
else
checklist.remove(index);
emit dataChanged(index, index);
return true;
}
return QFileSystemModel::setData(index, value, role);
}
Not sure whether it's relevant, but I found the following note at:
http://doc.trolltech.com/4.6/qt.html#ItemFlag-enum
"Note that checkable items need to be given both a suitable set of flags and an initial state, indicating whether the item is checked or not. This is handled automatically for model/view components, but needs to be explicitly set for instances of QListWidgetItem, QTableWidgetItem, and QTreeWidgetItem."
As far as I can make out, your code looks correct -- but maybe try setting the ItemIsUserCheckable flag on the base QFileSystemModel class (in your custom constructor), and see if the inherited data() and setData() methods work with role=Qt::CheckStateRole. If you need to maintain a list of what's currently checked for some other reason, then go ahead and do so in your derived setData(), but still call QFileSystemModel::setData() as well.
Meanwhile, I'm looking for why my QTreeView doesn't update the timestamp when I modify a file (unless I quit and restart my app, kind of defeats the purpose!) ... looks like the dataChanged() signal isn't getting emitted.