How to add custom row in QFileSystemModel? - qtreeview

I am using QFileSystemModel to represent file structure through the QTreView. Everything works fine, but I need to add an additional row at some level of the tree. For example for now is:
-root
--row1
--row2
--row3
All these rows mapping folders/files from file system.
I need:
-root
--row1
--row2
--row3
--custom row
So custom row is not representing any data from file system. I just need to add here my own data.
I have read a lot of stuff from the internet and people advice to use proxy model and reimplement rowCount(), data() and flags() functions. I tried to do that(used class derived from QSortFilterProxyModel), but I never get my row in data() and flags() functions. Seems like it takes count from source model.
QVariant AddonFilterModel::data (const QModelIndex & index, int role) const
{
if(role == Qt::DisplayRole && index.row() == FilterModel::rowCount(index))
{
return QString("Add-Ons");
}
return FilterModel::data(index, role);
}
Qt::ItemFlags AddonFilterModel::flags(const QModelIndex & index) const
{
if (!index.isValid())
return 0;
if (index.row() == FilterModel::rowCount(index))
{
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
return FilterModel::flags(index);
}
int AddonFilterModel::rowCount(const QModelIndex &parent) const
{
int count = FilterModel::rowCount(parent);
if(parent == this->getRootIndex())
{
return count+1;
}
return count;
}
Using class derived from QAbstractProxyModel is not acceptable because I need filtering functions of QSortFilterProxyModel().
Also I have tried to reimplement rowCount() of QFileSystemModel to make changes directly in model but I am getting "array out of range" error from QT code.
I have tried insertRow() method but it is not working. I think because QFileSystemModel is read only.
Did anyone face this problem? Any ideas?

Late answer. You have to subclass Qabstractitemmodel.

Related

Hide QStandardItemModel row in data member function

How can I hide / exclude entire rows from a QStandardItemModel without removing them physically from the model?
Rational: I want to not display entities which are shown in another view. I already have a working highlight logic for that in the data function. Could I turn it(easily) in a "not display logic", so such rows are skipped?
QVariant CMyModel::data(const QModelIndex &index, int role) const
{
if (role != Qt::BackgroundRole) { return CModelBase::data(index, role); }
.......
if (model.hasSomeCondition())
{
static const QBrush b(Qt::green);
return b;
}
return QVariant();
}

Qt does not update several QTableView cells when editing in the code

I have a simple logic, that basically goes like this: a few entries are filtered from source model (my custom QAbstractTableModel) and presented to the user using QSortFilterProxyModel, which does have only modified filterAcceptsRow function. This presentation is done using simple dialog. User selects desired entries from filtered ones and those selected entries from model must be updated (actually two fields have to be modified). So simplified code goes like this:
QModelIndexList selectedRows = myProxyModel->selectionModel()->selectedRows();
for (int i = 0; i < selectedRows.count(); i++) {
myProxyModel->setData(myProxyModel->index(selectedRows.at(i).row(), (int) LoanStatusCol, QModelIndex()), (int) ReturnedLoan, Qt::EditRole);
myProxyModel->setData(myProxyModel->index(selectedRows.at(i).row(), (int) LoanRetEntriesCol, QModelIndex()), (lastEntryNo + 1), Qt::EditRole);
}
However, this does not work. And each time behavour is quite weird. What I noticed is that, when it gets to second selected row in this cycle and when it reaches setData() code in the model:
bool TransactionModel::setData(const QModelIndex &index, const QVariant &value, int role) {
if (!index.isValid()) {
return false;
}
it returns invalid index. However, when I swaped these two setData() code lines, one row was updated, but second row was not - due to invalid index. I do not know, whether I explained that correctly, but probably this should be my silly mistake, because I am new at this.
UPDATE:
Since model consists of QList data, where Transaction is a custom class that defines fields of entry, I created a function, that updated underlying entry by column number (so to say...). I use function setValueByColumnNo. I just could not find a better way to do that, when working with lists of custom classes.
bool TransactionModel::setData(const QModelIndex &index, const QVariant &value, int role) {
if (!index.isValid()) {
return false;
}
if ((role == Qt::DisplayRole) || (role == Qt::EditRole)) {
transactionData[index.row()].setValueByColumnNo(index.column(), value);
emit dataChanged(index, index);
return true;
}
return false;
}
Any ideas?
Thanks.

Qt5: How to change background color of a row in a table and make it automatically move when sorting?

I have a QTableView which is populated using a QSqlTableModel.
In this table I need to be able to set the background color (let's say yellow) of some rows (using mouse clicks) in order to do some post-processing on these "yellow-selected" rows.
I found a solution that will seem kinda cumbersome... i.e. I used a proxy model and I used the Qt::Background role to give the row(s) the desired background color (yellow if "selected" and white if not).
It works acceptably (I've noticed some delays - no big deal), yet there is a problem with my implementation: when I sort the table (clicking on some column header), the "yellow-selected" rows DO NOT change their positions according to sorting operation!... The yellow rows just keep the initial positions...
So, is there a way to do this in a simple manner? Maybe using proxy model was not the best approach?!? How / What can I do to have it respond correctly to sorting operation?
I am novice so please it would be great if you can provide some code also, as I am the kind of person who learns better from examples :)
I did try 2-3 days to fix this problem but I didn't manage to do it. Internet / googling for help was of no help so far, unfortunately.
I am using Qt 5 under Windows 7.
#include "keylistgenerator.h"
#include "ui_keylistgenerator.h"
#include "dataBase/database.h"
KeyListGenerator::KeyListGenerator(QWidget * parent) :
QDialog(parent),
ui(new Ui::KeyListGenerator)
{
ui->setupUi(this);
dbConnection = DataBase::instance()->openDataBaseConnection();
model = new QSqlTableModel(this, QSqlDatabase::database(dbConnection));
proxy = new ProxyModel(this);
// unleash the power of proxy :)
proxy->setSourceModel(model);
model->setTable("MachineStatus");
model->select();
// display
ui->tableView->setModel(proxy);
ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->tableView->setSelectionMode(QAbstractItemView::NoSelection);
connect(ui->tableView, SIGNAL(clicked(QModelIndex)), this, SLOT(toggleSelectCurrentRow(QModelIndex)));
connect(ui->selectAllKeys, SIGNAL(clicked()), this, SLOT(setUpdateAllKeys()));
connect(ui->cancelAllKeys, SIGNAL(clicked()), this, SLOT(cancelUpdateAllKeys()));
}
KeyListGenerator::~KeyListGenerator()
{
delete ui;
}
void KeyListGenerator::toggleSelectCurrentRow(QModelIndex index)
{
proxy->toggleRowSelection(index.row());
}
void KeyListGenerator::generateKeysListFile()
{
// TODO...
}
void KeyListGenerator::setUpdateAllKeys()
{
for(int i = 0; i < proxy->rowCount(); ++i)
{
proxy->setRowSelection(i);
}
}
void KeyListGenerator::cancelUpdateAllKeys()
{
for(int i = 0; i < proxy->rowCount(); ++i)
{
proxy->setRowSelection(i, false);
}
}
Ok, and here is my proxy model :
#include <QBrush>
#include "myproxymodel.h"
ProxyModel::ProxyModel(QObject * parent)
: QAbstractProxyModel(parent)
{
}
ProxyModel::~ProxyModel()
{
}
int ProxyModel::rowCount(const QModelIndex & parent) const
{
if(!parent.isValid())
return sourceModel()->rowCount(QModelIndex());
return 0;
}
int ProxyModel::columnCount(const QModelIndex & parent) const
{
if(!parent.isValid())
return sourceModel()->columnCount();
return 0;
}
QModelIndex ProxyModel::index(int row, int column, const QModelIndex & parent) const
{
if(!parent.isValid())
return createIndex(row, column);
return QModelIndex();
}
QModelIndex ProxyModel::parent(const QModelIndex & child) const
{
return QModelIndex();
}
QModelIndex ProxyModel::mapToSource(const QModelIndex & proxyIndex) const
{
if(!proxyIndex.isValid())
return QModelIndex();
return sourceModel()->index(proxyIndex.row(), proxyIndex.column());
}
QModelIndex ProxyModel::mapFromSource(const QModelIndex & sourceIndex) const
{
if(!sourceIndex.isValid())
return QModelIndex();
return index(sourceIndex.row(), sourceIndex.column());
}
QVariant ProxyModel::data(const QModelIndex & index, int role) const
{
if(role == Qt::BackgroundRole)
{
Qt::GlobalColor color = (map.value(index.row()) == true) ? Qt::yellow : Qt::white;
QBrush background(color);
return background;
}
return sourceModel()->data(mapToSource(index), role);
}
void ProxyModel::toggleRowSelection(int row)
{
if(row < sourceModel()->rowCount())
{
// toggle status into ProxyModel for that row
bool status = map.value(row) ^ 1;
map.insert(row, status);
QModelIndex first = createIndex(row, 0);
QModelIndex last = createIndex(row, sourceModel()->columnCount() - 1);
emit dataChanged(first, last);
}
}
void ProxyModel::setRowSelection(int row, bool selected)
{
if(row < sourceModel()->rowCount())
{
// store selected status into ProxyModel for that row
map.insert(row, selected);
QModelIndex first = createIndex(row, 0);
QModelIndex last = createIndex(row, sourceModel()->columnCount() - 1);
emit dataChanged(first, last);
}
}
And here is like it looks now...
Your proposed solution is along the right track, with some problems.
The sorting is done by the model's QSQLTableModel::sort() in a way that doesn't preserve the indices. Namely, sort() resets the model, and that implies that the indices are not valid anymore. Your proxy fails to take that into account. Your map is invalid as soon as sort() is called by the view.
To work around that issue, you need to track the selection by the primary key of the involved row.
It's also simpler to derive the proxy from QIdentityProxyModel - you'd then only need to override the data() const member.
More details of using primary keys for selection indices are given in this answer, along with a full, working example.
Things get vastly simpler if you can deal with a model that happens to provide indices that refer to the underlying data source in spite of the sorting. QStringListModel is just such a model.
#include <QApplication>
#include <QStringListModel>
#include <QTableView>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
auto data = QStringList() << "Foo" << "Baz" << "Bar" << "Car";
QStringListModel model(data);
QTableView view;
view.setModel(&model);
view.setEditTriggers(QAbstractItemView::NoEditTriggers);
view.setSelectionMode(QAbstractItemView::MultiSelection);
view.setSortingEnabled(true);
app.setStyleSheet("QTableView { selection-background-color: yellow; selection-color: black; }");
view.show();
return app.exec();
}
You need persistence in the model. And you just happen to use a QSqlTableModel... So to provide persistence you need to modify the model (aka MachineStatus table) to include the "color" property.
As you said yourself
in order to do some post-processing on these "yellow-selected" rows
You are actually doing some post-processing on items with a specific property, and the fact that you color them means you are modifying that underlying property called "will be post processed".
In Concrete terms, it means ditching the proxy, subclassing the QSqlTableModel as described by QT QSqlTableModel - background color in a given data column. Additionally, you can also hide the color column.

Sort a QAbstractListModel derived model by role in QML ListView

I've created a QAbstractListModel derived model based on an underlying QHash. Since I need to use the model in QML, I cannot make use of the sorting functionality Qt widgets and views have integrated.
I tried using a QSortFilterProxyModel but it doesn't seem to work with my model. Getting the model to properly work in QML wasn't tedious enough, and now I am stuck on sorting.
Any suggestions are appreciated.
Here is the model source:
typedef QHash<QString, uint> Data;
class NewModel : public QAbstractListModel {
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
NewModel(QObject * parent = 0) : QAbstractListModel(parent) {}
enum Roles {WordRole = Qt::UserRole, CountRole};
QHash<int, QByteArray> roleNames() const {
QHash<int, QByteArray> roles;
roles[WordRole] = "word";
roles[CountRole] = "count";
return roles;
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const {
if (index.row() < 0 || index.row() >= m_data.size()) return QVariant();
Data::const_iterator iter = m_data.constBegin() + index.row();
switch (role) {
case WordRole:
return iter.key();
case CountRole:
return iter.value();
} return QVariant();
}
int rowCount(const QModelIndex &parent) const {
Q_UNUSED(parent)
return m_data.size();
}
int count() const { return m_data.size(); }
public slots:
void append(const QString &word) {
bool alreadyThere = m_data.contains(word);
if (alreadyThere) m_data[word]++;
else m_data.insert(word, 1);
Data::const_iterator iter = m_data.find(word);
uint position = delta(iter);
if (alreadyThere) {
QModelIndex index = createIndex(position, 0);
emit dataChanged(index, index);
} else {
beginInsertRows(QModelIndex(), position, position);
endInsertRows();
emit countChanged();
}
}
void prepend(const QString &word) {
if (m_data.contains(word)) m_data[word]++;
else m_data.insert(word, 1);
}
signals:
void countChanged();
private:
uint delta(Data::const_iterator i) {
uint d = 0;
while (i != m_data.constBegin()) { ++d; --i; }
return d;
}
Data m_data;
};
Here is "trying" to sort it:
NewModel model;
QAbstractItemModel * pm = qobject_cast<QAbstractItemModel *>(&model);
QSortFilterProxyModel proxy;
proxy.setSourceModel(pm);
proxy.setSortRole(NewModel::WordRole);
proxy.setDynamicSortFilter(true);
Alas, the proxy works as a model, but it doesn't sort the entries.
If you enable QSortFilterProxyModel::setDynamicSortFilter(true), you need to call QSortFilterProxyModel::sort(...) function once to let the proxy know which way to sort.
With that, any time the model is updated the proxy will sort everything again just automatically.
proxy.setDynamicSortFilter(true);
proxy.sort(0);
First of all, There's no need for qobject_cast<QAbstractItemModel *> downcasting -- the NewModel is a derived class of the QAbstractItemModel and the polymorphism principle says that you can use a subclass everywhere where a parent class is applicable.
Second, your prepend method does not use beginInsertRows and endInsertRows. That's a violation of the MVC API. You'll get data corruption in the attached views and proxy models if you use it this way.
Third, you haven't mentioned whether you're actually using your proxy model as the model for the attached view :).
Finally, you are using QHash as a backing store of your data with QHash::iterator for insertion. That's an itneresting solution, but something which just cannot work -- an insertion or removal can cause the hash table to grow/shrink, which means changing all data you publish via your model indexes. This is just not going to work. Don't use QHash when you need a stable order. The O(n) complexity of your delta method should be interpreted as a warning; this is a wrong approach.
Have a Look at https://github.com/oKcerG/SortFilterProxyModel. This project exposes the functionality of QSortFilterProxyModel nicely to QML. I used it in different projects and it junst worked. However if you want to implement your own solution it's something to get your ideas.

using QList<QStringList> to populate a QTableView. When something gets changed in the View, how do I get it back in the data?

I have a matrix of data, I simply stored it as a QList of QStringLists, all containing an equal number of QStrings. In this way, the data looks almost like a spreadsheet.
I use a QTableView to present this data to the user:
void DialogwitQTableView::setData(QList<QStringList> s)
{
/* Create the data model */
// 1. give it some headers
QStandardItemModel model = new QStandardItemModel(s.count(),25,this); //x Rows and 25 Columns
model->setHorizontalHeaderItem(0, new QStandardItem(QString("Column 1")));
model->setHorizontalHeaderItem(1, new QStandardItem(QString("Column 2")));
// ...
model->setHorizontalHeaderItem(24, new QStandardItem(QString("Column 25")));
// 2. populate the model with the data
for(int i = 0; i < s.count() ; i++)
{
for(int j = 0; j < s[i].count() ; j++)
model->setItem(i,j,new QStandardItem(QString(s[i][j])));
}
ui->NameOfTheTableView->setModel(model);
}
Now, if the user wants to change some of the data, he will just doubleclick in the QTableView in the Dialogbox and edits what he wants.
How do I get that edit also in the data? How can I adapt the QStringList with that new information?
If I search for documentation, I mostly find QTableViews linked to databases, but I don't see how this will work with a simple datastructure in memory. If I go to QtDesigner and click on "go to slots" for the TableView, I also do not see a slot called "datachanged" or anything similar.
Any thoughts? I feel pretty stuck and I am probably overviewing something, any tip is very welcome.
Looking at the doco, a QTableView inherits 6 signals from QAbstractItemView
http://doc.qt.digia.com/qt/qabstractitemview.html#signals
This class has all sorts of functionality for capturing edits, and edit triggers.
Once you can catch when the data is changed you can recommit it back to your model if you are using an MVC view. I am sure there are a lot of examples.
Hope that helps.
I think that for more complicated cases it's always best to use the abstract classes, more specifically QAbstractTableModel in this case.
Looking at this file, I just replaced Contact with StringList and changed the getters and setters. Check it out:
https://doc.qt.io/qt-5/qtwidgets-itemviews-addressbook-tablemodel-cpp.html
TableModel::TableModel(QObject *parent) :
QAbstractTableModel(parent)
{
}
TableModel::TableModel(QList<QStringList> stringLists, QObject *parent) :
QAbstractTableModel(parent),
stringLists(stringLists)
{
}
int TableModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return stringLists.size();
}
int TableModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return 2; // put the amount of columns here
}
QVariant TableModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) return QVariant();
if (index.row() >= stringLists.size() || index.row() < 0) return QVariant();
if (role == Qt::DisplayRole) {
const auto &strings = stringLists.at(index.row());
if (index.column() == 0)
return strings[0];
else if (index.column() == 1)
return strings[1];
}
return QVariant();
}
QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal) {
switch (section) {
case 0:
return tr("String 1");
case 1:
return tr("String 2");
default:
return QVariant();
}
}
return QVariant();
}
bool TableModel::insertRows(int position, int rows, const QModelIndex &index)
{
Q_UNUSED(index);
beginInsertRows(QModelIndex(), position, position + rows - 1);
for (int row = 0; row < rows; ++row)
stringLists.insert(position, { QString(), QString() });
endInsertRows();
return true;
}
bool TableModel::removeRows(int position, int rows, const QModelIndex &index)
{
Q_UNUSED(index);
beginRemoveRows(QModelIndex(), position, position + rows - 1);
for (int row = 0; row < rows; ++row)
stringLists.removeAt(position);
endRemoveRows();
return true;
}
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
int row = index.row();
auto strings = stringLists.at(row);
if (index.column() == 0)
strings[0] = value.toString();
else if (index.column() == 1)
contact[1] = value.toString();
else
return false;
stringLists.replace(row, contact);
emit dataChanged(index, index, {role});
return true;
}
return false;
}
Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
if (!index.isValid()) return Qt::ItemIsEnabled;
return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
}
QList<QStringList> TableModel::getStringLists() const
{
return stringLists;
}
I also highly recommend that you read this:
https://doc.qt.io/qt-5/modelview.html
Hope it helps.
If I search for documentation, I mostly find QTableViews linked to
databases, but I don't see how this will work with a simple
datastructure in memory.
QTableView is a part of Qt's Model/View framework. There are bunch of examples of model-views.
How do I get that edit also in the data? How can I adapt the
QStringList with that new information?
At least these solutions exists:
You can grab all data from QStandardItemModel via item method.
Connect to QStandardItemModel::itemChanged signal.
You can make your own model via subclassing (and I suggest to base on QAbstractTableModel) and implement several methods (data, setData + several utility methods).

Resources