How can I organize QStandardItemModel`s in one QTreeView - c++

I have two QTreeView's.
First (QTreeView1) displayed folders, second (QTreeView2) - displayed subfolders, that are loaded on folder click in first QTreeView1.
On click by folder in QTreeView1, I create QStandardItemModel with subfolders and set this model to QTreeView2. Also all items in both of QTreeView`s is checkable and I want to save all checked items state.
How can I organize models storage for each loaded folders.
Is it should be something like this:
// store folder model on subfolders check state changed
QMap<QStandardItemModel*, QString> modelStorage;
modelStorage.push_back(folderModel, folderPath);
and restore folder on folder click with:
QStandardItemModel* findFolderModel(QString folderPath)
{
QStandardItemModel* model;
foreach(auto path, modelStorage)
{
if (path == folderPath)
{
model = modelStorage.find(folderPath);
}
else model = nullptr;
}
return model;
}
and show model then ... Is it correct way to store all folder models? or it must be dynamically loaded? But in that case I need to store all model data by myself (for example, checked items state ...). Also model`s data can be changed for a while and I cant show "correct" data if I restore model from "snapshots".
UPD also I have question about implementation of my suggestion:
I store/restore models on click by folders in QTreeView1 and it seems to be worked ... but restored models doesn't contains/displays QStandardItems. It happens because treeItem allocated with new operator in local scope? How can I save all QStandardItems in each model in that case?
model = new QStandardItemModel;
QStandardItem* treeItem = new QStandardItem("item");
model->appendRow(treeItem);
//..
modelStorage.insert(folderItem, model);
ui.treeView->setModel(model);
// after restore model pointer is valid, but hadn't contains any items.

I think, you can try to use two QFileSystemModels for your both QTreeViews and don't create QStandardItemModel every time you click item in first QTreeView. As you can see in documentation, QFileSystemModel have setRootPath method. You can use this method for second model every time you click on folder in first QTreeView.
To make your items checkable, browse this helpful articles:
http://www.qtcentre.org/threads/27253-QFileSystemModel-with-checkboxes
QFilesystemmodel with Checkboxes
http://doc.qt.io/qt-5/qidentityproxymodel.html

Related

How to clear a QTreeView / QFileSystemModel

I have a list of items displayed in a QTableWidget, each one corresponding to a specific folder.
Next to this, I have a QTreeView, with a QFileSystemModel. When I select an item from the QTableWidget, it calls a slot (shown below) in order to display the corresponding folder content.
void MyWidget::diplayFolder(int row)
{
if (auto item = table->item(row, 1))
{
QString correspondingDirectory = item->text();
if (QDir(correspondingDirectory).exists())
{
// treeModel => QFileSystemModel
// tree => QTreeView
treeModel->setRootPath("");
treeModel->setRootPath(correspondingDirectory);
tree->setRootIndex(treeModel->index(correspondingDirectory));
}
else
{
qDebug() << "Reset tree => do not display anything!";
// treeModel->setRootPath("");
// tree->reset();
}
}
}
If the directory does not exist, I don't want to display anything. However, when I try to set an empty root path or reset the view, it show all my computer drives.
How can I reset or clear the QTreeView ?
Had a similar issue. Im not quite sure how i solved it, because it was a long time ago. I think it should work if you set the QTreeview a nullptr as model. So when the QDir exists you set a new QFileSystemModel otherwise you call:
tree->setModel(nullptr);
Hope this helps you.
EDIT:
If your doing it this way the header is deleted too.

QSortFilterProxyModel how to handle QStandardItems correctly

I have QTreeView with some items and search QLineEdit with connected slot on textEdited signal.
With this code:
QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(messagesModel);
proxyModel->setFilterFixedString(text);
ui.treeView->setModel(proxyModel);
text filtering is ok, but when I clicked on QTreeView QStandardItems checkboxes (after proxy model assigned to QTreeView), I have the program crashes in slot, that connected to this QTreeView original model (before proxy was assigned).
What is the right way to processing item checkbox clicks? Need I use new connect/slot to processing model changes, or I can use the same code for original model with some changes? I just need to hide filtered items in QTreeView. In QTreeWidget is hide() method, does QTreeView has something like this, or QSortFilterProxyModel - is what I need? Thx!
UPD crashed in slot, connected to treeView:
auto item = messagesModel->itemFromIndex(index); // item is NULL because proxyModel is set for TreeView now
if(item->whatsThis().isEmpty()) return; // error below
#ifndef QT_NO_WHATSTHIS
inline QString whatsThis() const {
return qvariant_cast<QString>(data(Qt::WhatsThisRole));
}
inline void setWhatsThis(const QString &whatsThis);
#endif
because I set proxyModel to treeView, but messagesModel have whatsThis...
I changed my code with that:
QStandardItem* item;
if(ui.leFilter->text().isEmpty())
item = messagesModel->itemFromIndex(index);
else
item = messagesModel->itemFromIndex(proxyModel->mapToSource(index));
if(item->whatsThis().isEmpty()) return;
and it works. Is that correct way? Proxy model is member of my UI class ... not local.
UPD how can I update source model when checkbox checked in proxyModel?
UPD 2 I have load "original" model for QtreeView and show it. When I edit text in QListEdit, I use proxyModel (code from 1st post). When text edited, I have check checkboxes in QtreeView (now proxyModel is active) and at this step all is ok. But when I do some changes in UI, in QTreeView set the original model and it has no changes that was made for proxyModel. How can I notify and update items in source Model with new data from proxyModel?
UPD3 Yes, source model is also modified ... I have just clear it)

QFileSystemModel rowCount does not work as expected

I am try an example in Model/View Programming.
http://doc.qt.io/qt-5/model-view-programming.html
To demonstrate how data can be retrieved from a model, using model indexes, we set up a QFileSystemModel without a view and display the names of files and directories in a widget. Although this does not show a normal way of using a model, it demonstrates the conventions used by models when dealing with model indexes.
We construct a file system model in the following way:
QFileSystemModel *model = new QFileSystemModel;
QModelIndex parentIndex = model->index(QDir::currentPath());
int numRows = model->rowCount(parentIndex);
In this case, we set up a default QFileSystemModel, obtain a parent index using a specific implementation of index() provided by that model, and we count the number of rows in the model using the rowCount() function.
This is my code:
QFileSystemModel* model = new QFileSystemModel;
QModelIndex parentIndex = model->index(QDir::currentPath());
qDebug() << QDir::currentPath();
// "/media/Local Data/Files/Programming/C++/build-DemostrateQModelIndex-Desktop_Qt_5_5_1_GCC_64bit-Debug"
qDebug() << "RowCount is " << model->rowCount(parentIndex);
But RowCount is always 0.
In the "build-DemostrateQModelIndex-Desktop_Qt_5_5_1_GCC_64bit-Debug" folder, there is files and folder inside. I expect row count should be the number of items inside.
I also tried initialized the QFileSystemModel;
QFileSystemModel* model = new QFileSystemModel;
model->setRootPath(QDir::rootPath());
QModelIndex parentIndex = model->index(QDir::currentPath());
qDebug() << "RowCount is " << model->rowCount(parentIndex);
RowCount is still 0.
Update 1:
Applying the suggestion from Johannes Schaub. I add an QEventLoop to my code.
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QFileSystemModel* model = new QFileSystemModel;
model->setRootPath(QDir::rootPath());
QModelIndex parentIndex = model->index(QDir::currentPath());
qDebug() << QDir::currentPath();
// "/media/Local Data/Files/Programming/C++/build-DemostrateQModelIndex-Desktop_Qt_5_5_1_GCC_64bit-Debug"
qDebug() << "First RowCount Call is " << model->rowCount(parentIndex);
QEventLoop loop;
QObject::connect(model, &QFileSystemModel::directoryLoaded, &loop, &QEventLoop::quit);
loop.exec();
qDebug() << "RowCount Call after eventloop is " << model->rowCount(parentIndex);
return a.exec();
}
I still get a row count of 0.
QFileSystemModel utilizes lazy and deferred loading. You need to watch on its signals, which will be emitted constantly until the entire directory has been loaded.
In particular, the docs say
Unlike QDirModel, 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.
In your case, you could probably run a local QEventLoop and connect the respective signals (directoryLoaded) of the model with the quit() slot of the event loop to wait for the population. I am unsure whether canFetchMore and fetchMore can be used for this scenario aswell to block on waiting for the population (afaik its main use is lazy loading when the user scrolls down in an infinite list, like for example a facebook pinwall stream). It's worth an attempt, at least.
#Kuba notes correctly that a local event loop is not intrinsically required. If you can afford leaving the context in which you create the QFileSystemModel (by storing it as a pointer member for example), and acting on the slot as a normal member function.
The principle to use is:
Create the model and set its root path. At this stage you can assum the model is still empty or very few data have been loaded.
Let the model load data in its separate internal thread. Connect the directoryLoaded signal to a slot. When the model will have loaded the root path, the signal will be sent.
In the slot, check if the folder you are interested in is fully loaded. It won't at first. The reason is the model loads using lazy methods, and likely only the root path folder will be ready.
If your folder is not fully ready, asks the model to load it. This is done by using model.canFetchMore and model.fetchMore with the index of the folder of interest, and returning immediately (or you could try to work with folder entries already ready, but this alternative requires to manage the progress of the model readiness).
When the slot is ready (likely just after calling model.fetchMore, the test canFetchMore will return False, meaning the folder you're interested in is fully loaded and model.rowCount will now return the correct value. If you're interested in another folder, use model.canFetchMore and model.fetchMore again with the index of the new folder.
Instead of using model.canFetchMore, you may also compare the path of the folder of interest with the argument passed to the slot. This string indicates the path the signal was sent for.
You indicated you're using C++, I don't have a code in this language, however the following code in Python can be easily translated (self = this, and indented lines are equivalent to a pair of brackets in delimiting a block)
class MyWidget(QPlainTextEdit):
def __init__(self):
# Init superclass
super(MyWidget, self).__init__()
self.setReadOnly(True)
self.show()
# FS model, set root path
self.model = QFileSystemModel()
path = "C:/"
self.model.setRootPath(path)
# Perform next tasks after model root path is loaded
self.model.directoryLoaded.connect(self.on_loaded)
def on_loaded(self, loaded_path):
# Folder to list
folder_path = "C:/Users" # <--- we are interested in this folder,
# not the root folder
folder_index = self.model.index(folder_path)
# Check the folder we are interested in is completely loaded
if self.model.canFetchMore(folder_index):
self.model.fetchMore(folder_index)
return
# Folder is now loaded, list children
num_rows = self.model.rowCount(folder_index)
for row in range(num_rows):
# Child data
num_columns = self.model.columnCount(folder_index)
if num_columns > 0:
# Child name in first column
index = self.model.index(row, 0, folder_index)
text += index.data(Qt.DisplayRole)
# etc
int rowCount = ui->tableView->verticalHeader()->count();

Qt: Linking my model data with a QListView

I'm writing my first Qt project (so I'm new to the environment) and I've got this project build using the MVC design pattern.
It's a pretty basic note manager/editor. I've got a class uiman ("Ui Manager") which takes care of my UI, and my function to open a notes database (which is just a list of text files to open)
void uiman::openDBDialog(){
QFileDialog dialog;
dialog.setFileMode(QFileDialog::AnyFile);
dialog.setDirectory("/Users/myuserdir/Desktop");
dialog.setFilter(QDir::Files);
dialog.setWindowTitle("Open File");
dialog.setNameFilter("Textie Notes DB(*.db)");
dialog.exec();
QString pathDB = dialog.selectedFiles().first();
model = new notesModel();
model->setNotesDB(new QString(pathDB.toUtf8().constData()));
refreshAll();
}
so far, so good. I take the path of my database and give it to my model, which should now manage the rest.
Now, the refreshAll() function should take the list I opened up and show them in my QListView, but I can't parse the file and append items on the go using clear() and append() unlike the QListWidget. So, how do I approach building a vector (I suppose) of names from my file and feeding them to my QListView?
Sorry if I'm not being clear, but the official documentation hasn't been clear enough.
Edit: This is my model, nodesmodel, and here's the code.
notesModel::notesModel(QObject *parent) :
QFileSystemModel(parent)
{
QStringList noteList;
}
void notesModel::setNotesDB(QString *dbpath){
// open the notes database
databasepath = dbpath;
}
QFile* notesModel::getDB(){
if(this->dbFile == NULL)
this->dbFile = new QFile(databasepath- >toUtf8().constData());
return this->dbFile;
}
As you already noticed, you do not add/remove data from the viewer widget itself, this is done in the model.
You can set the model with the setModel() method.
If you want to show a simple QString list, then you can use:
QStringList strings;
strings << "String 1" << "String 2" << "String 3";
model = new QStringListModel(strings, parent);
view->setModel(model);
// model is managed by parent, and will be deleted when parent is deleted.
// If you create multiple models you might consider other memory management strategy
To read a text file and store its lines in a QStringList, see this answer.

QT tree that allows multiselection

I'm making a simple file explorer and I ran into some problems with Qt. I want to show the user a tree view of files on his computer, but I also want to be able to select multiple files/directories and do something with them later on (by selecting checkboxes or multiple select using ctrl+left click or shift+left click). I've placed the QTreeView element and set up a model to it (QFileSystemModel). It gives me a good tree view, but I can't modify the headers (column names) or add my own column with checkbox in every row (for example). Qt is new to me, I've searched for few good hours for some tips/solutions, but nothing is working with QFileSystemModel. Is there anything I can do to get this working?
The code is short and simple:
QString lPath = "C:/";
QString rPath = "C:/";
leftTree_model = new QFileSystemModel(this);
rightTree_model = new QFileSystemModel(this);
leftTree_model->setRootPath(lPath);
rightTree_model->setRootPath(rPath);
//i have actually 2 tree views that work the same
ui->leftTree->setModel(leftTree_model); //ui->leftTree is the first tree view
ui->rightTree->setModel(rightTree_model); //the second
Use something of the following:
CheckStateRole to add checkboxes to your model. To do this, you inherit your custom item model (which you're going to use) from the QFileSystemModel, and reimplement the data() method, where you return bool values for CheckStateRole. You will also need the QAbstractItemModel::setData method to handle changes. You can also check the docs for QAbstractItemModel to see how to change header texts (headerData())
Change the selection mode of your view to allow multiple selections
EDIT:
here's a sample code to inherit from the model
class MyFancyModel : public QFileSystemModel
{
public:
MyFancyModel(QObject* pParent = NULL) : QFileSystemModel(pParent)
{
}
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole ) const
{
if (role == Qt::CheckStateRole)
{
// stub value is true
return true; // here you will return real values
// depending on which item is currently checked
}
return QFileSystemModel::data(index, role);
}
};