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



  • 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:
    @
    balanceProxyModel->insertColumns(6, 1);
    balanceProxyModel->setHeaderData(6, Qt::Horizontal, QObject::tr("Difference"));

    for (int i = 0; i < balanceProxyModel->rowCount(); ++i) {
    float budget = balanceProxyModel->index(i, 4).data().toFloat();
    float actual = balanceProxyModel->index(i, 5).data().toFloat();
    float difference = budget - actual;

    QModelIndex diffModelIndex = balanceProxyModel->index(i, 6);

    if (balanceProxyModel->setData(diffModelIndex, QVariant(difference)) == false) {
    qDebug() << ATLINE << ":" << "diff not added!!!";
    }
    ...
    }
    @

    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":http://stackoverflow.com/questions/7314128/why-would-qsortfilterproxymodel-setdata-return-false 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.



  • 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 ?



  • 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.



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

    @
    #include <QSortFilterProxyModel>

    class SortFilterProxyModelWithMetaColumns : public QSortFilterProxyModel {
    Q_OBJECT
    public:
    SortFilterProxyModelWithMetaColumns(QObject *parent = 0)
    : QSortFilterProxyModel(parent) {
    }

    Qt::ItemFlags flags(const QModelIndex &index) const {
    Qt::ItemFlags itemFlags =
    (QSortFilterProxyModel::flags(index) | Qt::ItemIsEditable);

    return itemFlags;
    

    }
    };
    @

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



  • 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 :

    @
    class SortFilterProxyModelWithMetaColumns : public QSortFilterProxyModel {
    Q_OBJECT

    static const int DifferenceColumn = 6;

    public:
    SortFilterProxyModelWithMetaColumns(QObject *parent = 0)
    : QSortFilterProxyModel(parent) {
    }

    int columnCount(const QModelIndex &parent = QModelIndex())
    {
    return QSortFilterProxyModel::columnCount(parent) + 1;
    }

    Qt::ItemFlags flags(const QModelIndex &index)
    {
    if (index.column() == DifferenceColumn)
    {
    return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
    }
    return QSortFilterProxyModel::flags(index);
    }

    QVariant data(const QModelIndex &index, int role)
    {
    if (index.column() == DifferenceColumn && role == Qt::DisplayRole)
    {
    const QSqlTableModel *model = qobject_cast<const QSqlTableModel *>(sourceModel());
    Q_ASSERT(model);
    const int firstColumnIndex = model->fieldIndex("MyFirstColumn");
    const int secondColumnIndex = model->fieldIndex("MySecondColumn");
    const QModelIndex dataIndex1 = this->index(index.row(), firstColumnIndex);
    const QModelIndex dataIndex2 = this->index(index.row(), secondColumnIndex);

           const QVariant data1 = mapToSource(dataIndex1).data(Qt::DisplayRole);
           const QVariant data2 = mapToSource(dataIndex2).data(Qt::DisplayRole);
    
           const int difference = data1.toInt() - data2.toInt();
    
           return difference;
    }
    
    return QSortFilterProxyModel::data(index, role);
    

    }

    QVariant headerData(int section, Qt::Orientation orientation, int role)
    {
    if (orientation == Qt::Horizontal && section == DifferenceColumn)
    {
    return tr("Difference");
    }

      return QSortFilterProxyModel::data(section, orientation, role);
    

    }
    };
    @

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



  • 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.



  • 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 :

    @
    class MyCustomModel : public QSqlTableModel {

    static const int DifferenceColumn = 2;

    Q_OBJECT

    public:
    MyCustomModel(QObject *parent = 0)
    : QSqlTableModel(parent) {
    setTable("datas");
    select();
    }

    int columnCount(const QModelIndex &parent) const
    {
    const int count = QSqlTableModel::columnCount(parent);
    return count + 1;
    }

    Qt::ItemFlags flags(const QModelIndex &index) const
    {
    if (index.column() == DifferenceColumn)
    {
    return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
    }
    return QSqlTableModel::flags(index);
    }

    QVariant data(const QModelIndex &index, int role) const
    {
    if (index.column() == DifferenceColumn && role == Qt::DisplayRole)
    {
    const int firstColumnIndex = fieldIndex("data1");
    const int secondColumnIndex = fieldIndex("data2");
    const QModelIndex dataIndex1 = this->index(index.row(), firstColumnIndex);
    const QModelIndex dataIndex2 = this->index(index.row(), secondColumnIndex);

           const QVariant data1 = dataIndex1.data(Qt::DisplayRole);
           const QVariant data2 = dataIndex2.data(Qt::DisplayRole);
    
           const int difference = data1.toInt() - data2.toInt();
    
           return difference;
        }
    
        return QSqlTableModel::data(index, role);
    

    }

    QVariant headerData(int section, Qt::Orientation orientation, int role) const
    {
    if (orientation == Qt::Horizontal && section == DifferenceColumn)
    {
    return tr("Difference");
    }

      return QSqlTableModel::headerData(section, orientation, role);
    

    }
    };

    Dialog::Dialog(QWidget *parent) :
    QDialog(parent)
    {
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName(":memory:");
    db.open();

    QSqlQuery query;
    query.exec&#40;"create table datas (data1 int, data2 int&#41;");
    
    for (int i = 0; i < 10; ++i)
    {
        query.prepare("INSERT INTO datas VALUES (:data1, :data2)");
        query.bindValue(":data1", qrand() % 100);
        query.bindValue(":data2", qrand() % 100);
        query.exec&#40;&#41;;
    }
    
    
    QHBoxLayout *layout = new QHBoxLayout(this);
    QTableView *view = new QTableView;
    
    MyCustomModel *model = new MyCustomModel(this);
    view->setModel(model);
    
    layout->addWidget(view);
    setLayout(layout);
    

    }
    @

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



  • 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.



  • 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 :-)



  • @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.



  • 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?



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

    I just found it mentioned "here":http://developer.qt.nokia.com/wiki/Model_Test, though:

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

    Thanks!



  • 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.



  • 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":http://developer.qt.nokia.com/wiki/Model_Test 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.



  • bq. 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 :)



  • Will do. Thanks!!!



  • Well, I didn't have any asserts occur with ModelTest, but maybe I used it wrong.

    To use it I:

    copied the files to a seperate folder.

    modified the .pro to be .pri

    included the new .pri file into my .pro file via:

    @include(/seperate-folder/modeltest.pri)@

    removed all sources and headers from the .pri file except modeltest.h and modeltest.cpp

    added a line for QT += test

    comment out the load(qttest_p4) line because of the following warning:

    @WARNING: /home/jetimms/code/Qt/cashflow_01.nix/modeltest.pri:1: Unable to find file for inclusion qttest_p4@

    in the same code that I instantiated my proxy model, I included modeltest.h and added the following line just after creating its instance:

    @new ModelTest(balanceProxyModel, this);@

    I did see these debug messages, but I don't recognize them from my stuff. They occur when a change to the view is entered.
    @
    Debug: ratbr QModelIndex(-1,-1,0x0,QObject(0x0) ) 0 128
    Debug: rr QModelIndex(-1,-1,0x0,QObject(0x0) ) 0 128
    @



  • I had so many Debug messages coming through, that I didn't realize that the two mentioned above were not one of mine :)

    The source file modeltest.cpp clued me into what those messages meant:
    ratbr - rowsAboutToBeRemoved() pushes onto a QStack the index info in rows 0 through 128 that are about to be removed
    rr - rowsRemoved() compares rows 0 through 128 that were removed to those on top of QStack and asserts if they differ

    What is puzzling to me is why it would be removing any rows, much less 129 of them. By the way, my table contains 129 records.

    The view still shows all of my rows so none are missing after the debug message. This removal could be a function of the sort that defaults to the first column, though. I'll try the ModelTools using QSortFilterProxyFilter and see what happens.

    However, the two functions mentioned above only look at only column 0 (see the following snippet from rowsAboutToBeRemoved()):

    The below is with start = 0, end = 128, and Changing is a simple structure
    @
    Changing c;
    c.parent = parent;
    c.oldSize = model->rowCount ( parent );
    c.last = model->data ( model->index ( start - 1, 0, parent ) );
    c.next = model->data ( model->index ( end + 1, 0, parent ) );
    @

    This is strange because my difference column (the one I added) is the 7th column of the view.

    Perhaps the QModelIndex that is referred to is just an index for my difference column and not the entire model's scope. Maybe its the proxy model's scope since it would only contain a column 0, the difference column. It is the model that I added the column to, after all.

    One more mystery is that the ratbr and rr messages occur after each edit submission, not just the first one.

    I'll keep digging.



  • octal,
    While trying to debug my model, I move line 29 and 30 from your first example:

    @
    const QSqlTableModel *model = qobject_cast<const QSqlTableModel *>(sourceModel());
    Q_ASSERT(model);
    @

    to just above the if block that it was in and got an assert on the model being NULL. I've tried in the other functions provided to copy the same code, but the only other one it works in is headerData.

    Any idea of why that happens? Just curious because I saw no reason.



  • I also found "this bug report":https://bugreports.qt.nokia.com/browse/QTBUG-12626 which wasn't fixed until 4.7.2. The report specifies QSqlTableModel, but says nothing about QSortFilterProxyModel.

    I suspect that its related, anyhow, to my disappearing columns.

    Mepis 11 (my Linux distro of choice) only has 4.7.1-2 of libqtcore4 available in its repository. I'm thinking about downloading the source and configuring the libraries myself.

    [EDIT: fixed link, Volker]



  • Just tried this on an install of Qt libs version 4.7.2 built on a Windows XP Pro x64 box at work.

    I got the same results when I ran my code using the subclass of the QSortFilterProxyModel which sources a populated QSqlTableModel.

    The difference column (read-only) appears with the correct amount in it at first. After an edit to the view, it disappears.

    So Qt libs 4.7.2 didn't fix my issue.

    Upon further inspection, the bug mentioned above does not specify a proxy model being used, just the QSqlTableModel and its call to QSqlQueryModel.



  • I wizened up and cut and paste octal's latest demo subclass of QSqlTableModel into my own, changed the number of columns, got rid of the proxy model and it worked!

    I would like to know how to get the proxy model to add the column, but my original problem is solved. Now, I can continue with the app.

    Thanks for the help, octal and Andre!



  • Hi jetimms,

    Could you send the code snippet which use QSortFilterProxyModel subclassing to add the custom column addition.
    I can't realize the index problem that octal mentioned.

    Thanks,
    ugur

    [quote author="jetimms" date="1315884755"]@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.[/quote]



  • @tombalak,

    I ended up basing my solution (almost exactly) from octal's code that subclassed QSqlTableModel after getting his to work in a sample. When I used QSortFilterProxyModel to subclass, any created columns would disappear after changing and submitting the values of one of them. I'm not sure that my issue was with the indexes either.



  • thanks jetimms,
    ugur


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.