Qt: Linking my model data with a QListView - c++

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.

Related

How to use a custom validation function for a QCompleter in a QComboBox

I have a string matching function to be used for searching for names that is more advanced than QString::contains() (e. g. when you search for "mueller", it will match "Müller").
I'd like to use this function to search inside a QComboBox. The default completion almost does what I need: If I do
combobox->setEditable(true);
combobox->setInsertPolicy(QComboBox::NoInsert);
combobox->completer()->setCompletionMode(QCompleter::PopupCompletion);
and type some text in the QComboBox's lineedit, the popup pops up, only showing entries starting what has been typed.
This is what I want, but I would like the QCompleter to evaluate matches using my search function rather than the QString::startsWith() that is apparently used here (and setting the mode to Qt::MatchContains is better but still not sufficient).
Is there any way to customize the completer's search function?
Thanks for all help!
I ended up using an own QCompleter and set it for the QComboBox's QLineEdit. The completer does not use the combobox's model, but an own one, which is filled with data everytime the entered text changes.
Can be done as follows:
m_matchingNames = new QStringListModel(this);
m_nameCompleter = new QCompleter(m_matchingNames, this);
m_nameCompleter->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
m_playersSelect->setEditable(true);
m_playersSelect->setInsertPolicy(QComboBox::NoInsert);
m_playersSelect->setCompleter(0);
m_playersSelect->lineEdit()->setCompleter(m_nameCompleter);
connect(m_playersSelect->lineEdit(), &QLineEdit::textEdited, this, &ScorePage::nameSearchChanged);
and
void ScorePage::nameSearchChanged(const QString &text)
{
QStringList possibleNames;
for (const QString &name : m_availableNames) {
if (checkMatch(name, text)) {
possibleNames << name;
}
}
m_matchingNames->setStringList(possibleNames);
}
Most probably not the most prerformant solution, but it works :-)
One then can also connect to QCompleter::activated() to process what has been chosen from the list and e. g. do a QComboBox::setCurrentIndex() or such.

How to clear setNameFilters from a QFileSystemModel?

I have some code I am writing which displays the contents of a directory associated with a category whenever a new category is clicked on in a different list view. For convenience, I wanted to supply a filter option that would only display those contents of the directory that matched a given string input.
I have all of this working properly, but there's a bug whenever I apply a filter to one category, and then switch to another. The filter is properly applied to the category I'm presently viewing. But when I try to click on another category, every file list for every category shows up empty. This bug only occurs when I apply a filter, it does not occur when I switch between categories without ever using a filter.
I thought, "that's fine, I'll just nuke the filter between each category change." So I tried:
if (filemodel->nameFilters().size() > 0)
{
// create an empty list of strings to pass to the filter.
QListString clearFilter;
filemodel->setNameFilters(clearFilter);
fileView->setModel(filemodel);
}
Sadly, that made no change in the behavior. I even tried appending an empty string to the list. No change. Ultimately, I had to resort to the following code:
if (filemodel->nameFilters().size() > 0)
{
// throw away the old filemodel and start over with a fresh one.
delete filemodel;
filemodel = new QFileSystemModel;
filemodel->setFilter(QDir::Files);
filemodel->setRootPath("/");
fileView->setModel(filemodel);
}
That worked, but this seems horribly wrong. It doesn't seem like I should have to throw away the filemodel and start with a new one from scratch. Is there a better solution to this problem?
For the sake of reference, here is all the code relevant to how I set things up in the MainWindow constructor, and how I implemented the method to do the filtering:
MainWindow::MainWindow(...)
{
// ... skipping a bunch of stuff
fileView = new QListView(this);
filemodel = new QFileSystemModel;
filemodel->setFilter(QDir::Files);
filemodel->setRootPath("/");
fileView->setModel(filemodel);
filterText = new QLineEdit(this);
doFilter = new QPushButton(this);
doFilter->setText("Filter Filenames");
connect(doFilter, SIGNAL(clicked(bool)), this, SLOT(filterFileView()));
// ... skipping a bunch of other stuff
}
void MainWindow::filterFileView()
{
QStringList filterToApply;
filterToApply.append("*" + filterText->text() + "*");
filemodel->setNameFilters(filterToApply);
filemodel->setNameFilterDisables(false);
fileView->setModel(filemodel);
}
Resetting the filters to "*" works for me.
QStringList filters;
filters << "*";
filemodel->setNameFilters(filters);

How can I organize QStandardItemModel`s in one QTreeView

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

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 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);
}
};