September 11, 2011

jetimms jetimms
Lab Rat
49 posts

add read-only diff column to QSortFilterProxyModel that calculates on model data

Page  
1

I’m trying to use an instance of QSortFilterProxyModel to add a read-only column that takes a difference of two columns existing in the source model.

The code that I am using to add the column attempt to alter the its value:

  1. balanceProxyModel->insertColumns(6, 1);
  2. balanceProxyModel->setHeaderData(6, Qt::Horizontal, QObject::tr("Difference"));
  3.  
  4. for (int i = 0; i < balanceProxyModel->rowCount(); ++i) {
  5.   float budget = balanceProxyModel->index(i, 4).data().toFloat();
  6.   float actual = balanceProxyModel->index(i, 5).data().toFloat();
  7.   float difference = budget - actual;
  8.  
  9.   QModelIndex diffModelIndex = balanceProxyModel->index(i, 6);
  10.  
  11.   if (balanceProxyModel->setData(diffModelIndex, QVariant(difference)) == false) {
  12.     qDebug() << ATLINE << ":" << "diff not added!!!";
  13.   }
  14.   ...
  15. }

Unfortunately in the above example, it streams “diff not added!!!” for every iteration. The diff column shows up, but it is empty.

I have asked at StackOverflow.com [stackoverflow.com] but the only thing we could find is that the flags for that index are not set to Qt::ItemIsEditable.

The only thing is, I don’t want it to be editable by the user, I just want to edit it programatically.

It has been suggested that I should subclass QSortFilterProxyModel and set the column there. I know how to subclass Qt class, minimally (that is without changes), but not how to reimplement setData to allow an update.

Thanks.

24 replies

September 11, 2011

octal octal
Lab Rat
74 posts

If you don’t want your item to be editable, why don’t you just unset the Qt::ItemIsEditable flag ?

I don’t think you need a ProxyModel here, for such an easy thing. What is your source model ?

September 11, 2011

jetimms jetimms
Lab Rat
49 posts

The Qt::ItemIsEditable is unset. What I meant was I don’t think my problem stems from the fact that the Qt::ItemIsEditable is unset for that index.

My source model is QSqlTableModel. It is attached to a SQLite database. The diff column that I wish to create will be a difference between to fields on that table. I see it as redundant data to have a difference field in my database table since it can (maybe) be readily created by the interface. Also, if I did create the field in the database table, I would have to have the view refresh after every change to the model (and I don’t know how to do that yet.)

The main issue, though, is when I try to setData on the proxy model, I get false. How should I set the column values on the proxy model?

Thanks.

September 11, 2011

jetimms jetimms
Lab Rat
49 posts

Since it was suggested prior to posting here, I set the Qt::ItemIsEditable flag via instancing the following class instead of QSortFilterProxyModel:

  1. #include <QSortFilterProxyModel>
  2.  
  3. class SortFilterProxyModelWithMetaColumns : public QSortFilterProxyModel {
  4.   Q_OBJECT
  5. public:
  6.   SortFilterProxyModelWithMetaColumns(QObject *parent = 0)
  7.       : QSortFilterProxyModel(parent) {
  8.   }
  9.  
  10.   Qt::ItemFlags flags(const QModelIndex &index) const {
  11.     Qt::ItemFlags itemFlags =
  12.       (QSortFilterProxyModel::flags(index) | Qt::ItemIsEditable);
  13.  
  14.     return itemFlags;
  15.   }
  16. };

However, this did not affect the return results from the calls to setData(). It still returns false.

September 11, 2011

octal octal
Lab Rat
74 posts

Well, ok.

Since QSortFilterProxyModel just usually forwards the calls to the SourceModel, I think you will have to handle it yourself in your custom proxy model. What I would do is to reimplement the following methods in my ProxyModel :

  • flags() for the additional column ;
  • columnCount() to “add” your column ;
  • data() to retrieve the additionnal data ;
  • headerData()

Let’s give it a try :

  1. class SortFilterProxyModelWithMetaColumns : public QSortFilterProxyModel {
  2.   Q_OBJECT
  3.  
  4.   static const int DifferenceColumn = 6;
  5.  
  6. public:
  7.   SortFilterProxyModelWithMetaColumns(QObject *parent = 0)
  8.       : QSortFilterProxyModel(parent) {
  9.   }
  10.  
  11.   int columnCount(const QModelIndex &parent = QModelIndex())
  12.   {
  13.       return QSortFilterProxyModel::columnCount(parent) + 1;
  14.   }
  15.  
  16.   Qt::ItemFlags flags(const QModelIndex &index)
  17.   {
  18.       if (index.column() == DifferenceColumn)
  19.       {
  20.           return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
  21.       }
  22.       return QSortFilterProxyModel::flags(index);
  23.   }
  24.  
  25.   QVariant data(const QModelIndex &index, int role)
  26.   {
  27.        if (index.column() == DifferenceColumn && role == Qt::DisplayRole)
  28.        {
  29.            const QSqlTableModel *model = qobject_cast<const QSqlTableModel *>(sourceModel());
  30.            Q_ASSERT(model);
  31.            const int firstColumnIndex = model->fieldIndex("MyFirstColumn");
  32.            const int secondColumnIndex = model->fieldIndex("MySecondColumn");
  33.            const QModelIndex dataIndex1 = this->index(index.row(), firstColumnIndex);
  34.            const QModelIndex dataIndex2 = this->index(index.row(), secondColumnIndex);
  35.  
  36.            const QVariant data1 = mapToSource(dataIndex1).data(Qt::DisplayRole);
  37.            const QVariant data2 = mapToSource(dataIndex2).data(Qt::DisplayRole);
  38.  
  39.            const int difference = data1.toInt() - data2.toInt();
  40.  
  41.            return difference;
  42.     }
  43.  
  44.     return QSortFilterProxyModel::data(index, role);
  45.   }
  46.  
  47.   QVariant headerData(int section, Qt::Orientation orientation, int role)
  48.   {
  49.       if (orientation == Qt::Horizontal && section == DifferenceColumn)
  50.       {
  51.           return tr("Difference");
  52.       }
  53.  
  54.       return QSortFilterProxyModel::data(section, orientation, role);
  55.   }
  56. };

However, I didn’t try it, so you might have to adjust it.

September 11, 2011

jetimms jetimms
Lab Rat
49 posts

Wow, thanks for all of the effort, octal! I’m still sketchy on subclassing Qt classes as far as what works and what to pass forward.

I tried the above, but didn’t see any difference in the Diff column (its still blank.) I tried commenting out the insertColumns line, but that only removed the column from the view.

Also, the Diff column has no header displayed, which is weird since I entered your headerData function above.

Another weird thing is that I am counting the number of fields and when I include the insertColumns and your code for the subclass, it actually shows 8 columns numbered (0 – 7.)

Is this a common task to ask of the Qt (adding a meta-field that isn’t really in the source table?)

Thanks again.

September 11, 2011

octal octal
Lab Rat
74 posts

Damn, I forgot one important thing : the index.

Because of that, I think it would actually be better to directly subclass QSqlTableModel. I’m a little bit tired, so I’ll let you with this code sample :

  1. class MyCustomModel : public QSqlTableModel {
  2.  
  3.   static const int DifferenceColumn = 2;
  4.  
  5.   Q_OBJECT
  6.  
  7. public:
  8.   MyCustomModel(QObject *parent = 0)
  9.       : QSqlTableModel(parent) {
  10.       setTable("datas");
  11.       select();
  12.   }
  13.  
  14.   int columnCount(const QModelIndex &parent) const
  15.   {
  16.       const int count = QSqlTableModel::columnCount(parent);
  17.       return count + 1;
  18.   }
  19.  
  20.   Qt::ItemFlags flags(const QModelIndex &index) const
  21.   {
  22.       if (index.column() == DifferenceColumn)
  23.       {
  24.           return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
  25.       }
  26.       return QSqlTableModel::flags(index);
  27.   }
  28.  
  29.   QVariant data(const QModelIndex &index, int role) const
  30.   {
  31.       if (index.column() == DifferenceColumn && role == Qt::DisplayRole)
  32.       {
  33.            const int firstColumnIndex = fieldIndex("data1");
  34.            const int secondColumnIndex = fieldIndex("data2");
  35.            const QModelIndex dataIndex1 = this->index(index.row(), firstColumnIndex);
  36.            const QModelIndex dataIndex2 = this->index(index.row(), secondColumnIndex);
  37.  
  38.            const QVariant data1 = dataIndex1.data(Qt::DisplayRole);
  39.            const QVariant data2 = dataIndex2.data(Qt::DisplayRole);
  40.  
  41.            const int difference = data1.toInt() - data2.toInt();
  42.  
  43.            return difference;
  44.         }
  45.  
  46.         return QSqlTableModel::data(index, role);
  47.   }
  48.  
  49.   QVariant headerData(int section, Qt::Orientation orientation, int role) const
  50.   {
  51.       if (orientation == Qt::Horizontal && section == DifferenceColumn)
  52.       {
  53.           return tr("Difference");
  54.       }
  55.  
  56.       return QSqlTableModel::headerData(section, orientation, role);
  57.   }
  58. };
  59.  
  60. Dialog::Dialog(QWidget *parent) :
  61.     QDialog(parent)
  62. {
  63.     QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
  64.     db.setDatabaseName(":memory:");
  65.     db.open();
  66.  
  67.     QSqlQuery query;
  68.     query.exec("create table datas (data1 int, data2 int)");
  69.  
  70.     for (int i = 0; i < 10; ++i)
  71.     {
  72.         query.prepare("INSERT INTO datas VALUES (:data1, :data2)");
  73.         query.bindValue(":data1", qrand() % 100);
  74.         query.bindValue(":data2", qrand() % 100);
  75.         query.exec();
  76.     }
  77.  
  78.  
  79.     QHBoxLayout *layout = new QHBoxLayout(this);
  80.     QTableView *view = new QTableView;
  81.  
  82.     MyCustomModel *model = new MyCustomModel(this);
  83.     view->setModel(model);
  84.  
  85.     layout->addWidget(view);
  86.     setLayout(layout);
  87.  
  88. }

When I was subclassing QSortFilterProxyModel, all the indexes refering to the additional column we created were invalid.

September 12, 2011

jetimms jetimms
Lab Rat
49 posts

Awesome!

Thanks so much for working that out, octal. I just finished getting the example above working and it does so perfectly!

Next, I will give it a shot with the real code.

Thanks again for your help.

September 12, 2011

Andre Andre
Robot Herder
6296 posts

Still, as a side note, it is very much possible to do this using a proxy model. That makes the code reusable with any underlying model that you may wish to use with it.

Note: /me is a fan of proxy models :-)

 Signature 

Looking for Qt developers to join our team @ i-Optics: https://qt-project.org/forums/viewthread/25393/

September 13, 2011

jetimms jetimms
Lab Rat
49 posts

@Andre, thanks for the tip!

I’ve almost gotten it to work using the proxy model.

The weird thing is, that the new column shows and displays the correct field name and value (the difference,) but after I update one of the values in the table the difference column disappears.

Any ideas? It removes the column if I use either QSortFilterProxyModel or my subclass.

Thanks.

September 13, 2011

Andre Andre
Robot Herder
6296 posts

Using the modeltest class to test your (proxy) model is usually a great help to find the problems that underly symptoms like these. Did you use it?

 Signature 

Looking for Qt developers to join our team @ i-Optics: https://qt-project.org/forums/viewthread/25393/

September 13, 2011

jetimms jetimms
Lab Rat
49 posts

No, I didn’t. I have not heard of that before.

I just found it mentioned here [developer.qt.nokia.com], though:

I’ll give that a shot and see what happens.

Thanks!

September 13, 2011

jetimms jetimms
Lab Rat
49 posts

Hmm. Do I have to sign up with gitorious to get these files?

I can click in them and copy out the text and create my own files, since there’s not many. For some reason it doesn’t give me the choice to just download them in Opera on Linux. This is the first time I’ve dealt with files on a gitorious (or git) server.

September 13, 2011

jetimms jetimms
Lab Rat
49 posts

I cut and pasted the modeltest source files, this morning. I’ll get to test with them tonight, hopefully.

Update —20110913_1249—
I just figured out the download thing. I just need to click on the “Raw blob data”. Heh, wish I would have seen this sooner. I’m an idiot.

Also, the link to Model Test [developer.qt.nokia.com] says to include the modeltest.pri file inside of my project’s pro file, but there was no .pri file in the repository. I’m going to assume that means to rename modeltest.pro to modeltest.pri and include that.

September 13, 2011

octal octal
Lab Rat
74 posts

I’m going to assume that means to rename modeltest.pro to modeltest.pri and include that.

Personally, that’s what I do when I use the ModelTest. Now have fun debugging your model :)

September 13, 2011

jetimms jetimms
Lab Rat
49 posts

Will do. Thanks!!!

Page  
1

  ‹‹ [SOLVED] If i intent not to have translations at my app should i remove tr()?      [SOLVED] QLocale and currency data ››

You must log in to post a reply. Not a member yet? Register here!