2

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...
enter image description here

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
סטנלי גרונן
  • 2,917
  • 23
  • 46
  • 68
  • I think that you need to use a simpler model, like a `QStringListModel`, and post a complete (single file) example that demonstrates the behavior. See [this answer](http://stackoverflow.com/a/20766271/1329652) and [this one](http://stackoverflow.com/a/21315861/1329652) for inspiration as to how a short, single-file test case that uses models and views might look. [Here](http://stackoverflow.com/a/21487724/1329652) I describe the semantics expected of a model - possibly your proxy model breaks some of them, hard to tell without seeing the code! *Your example should be ~100 lines long!* – Kuba hasn't forgotten Monica Jun 18 '15 at 23:58
  • Hi, Worth mention that I used the QSqlTableModel because the table view is populated with data coming from a SQL database table... – סטנלי גרונן Jun 19 '15 at 07:35
  • Hmmm, I tried to post the code but it seems a real pain to do it !!!... The editor complains about formatting and adding 4 spaces?!?! It's the very first time I see such thing... never had similar problems on other sites/forums :( Hehe, finally did it, took ages... rocket science :) – סטנלי גרונן Jun 19 '15 at 07:57
  • 1
    I changed your Question to use Code Sample instead of Code Snippet. Snippets are for runnable code like javasrcipt etc: http://blog.stackoverflow.com/2014/09/introducing-runnable-javascript-css-and-html-code-snippets/ – Mailerdaimon Jun 19 '15 at 08:14
  • Thanks, I hope Mailerdaimon is a human not a daemon/robot to thank to :) – סטנלי גרונן Jun 19 '15 at 08:31
  • @groenhen 100% Human, that is why I choosed the old notation "daimon" which is not used for robots :D – Mailerdaimon Jun 19 '15 at 09:52
  • 1
    I've updated my answer with a link to [another answer](http://stackoverflow.com/a/11021486/1329652) to a question that is almost a duplicate of this one, and contains a full, working example. – Kuba hasn't forgotten Monica Jun 19 '15 at 18:40

2 Answers2

1

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.

screenshot of the example

#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();
}
Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
0

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.

Community
  • 1
  • 1
UmNyobe
  • 22,539
  • 9
  • 61
  • 90