QTableView & CheckBox delegate alignmnent



  • I have a QSqlTableModel that I am trying to use with a QTableView.
    As I have some columns that contain 1/0 which means true/false in my case, I need to display check boxes instead of the numbers.

    I created a delegate, but I'm having an alignment problem: when it is displayed it is centered as intended, but when editing it is displayed to the left (with another checkbox visible in the center).

    Here is my code:
    @
    BooleanItemDelegate::BooleanItemDelegate(QObject *parent) :
    QStyledItemDelegate(parent)
    {
    }

    QWidget *BooleanItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
    if (index.isValid()) {
    QCheckBox *cb =new QCheckBox(parent);
    return cb;
    } else {
    return QStyledItemDelegate::createEditor(parent, option, index);
    }
    }

    void BooleanItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const {
    if(index.isValid())
    {
    int value = index.model()->data(index, Qt::DisplayRole).toInt();
    QCheckBox checkBox = static_cast<QCheckBox>(editor);
    if(value == 1) {
    checkBox->setCheckState(Qt::Checked);
    } else {
    checkBox->setCheckState(Qt::Unchecked);
    }
    }
    else
    {
    QStyledItemDelegate::setEditorData(editor, index);
    }

    }

    void BooleanItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const {
    if(index.isValid())
    {
    QCheckBox checkBox = static_cast<QCheckBox>(editor);
    int value;
    if(checkBox->checkState() == Qt::Checked)
    value = 1;
    else
    value = 0;

        model->setData(index, value);
    }
    else
    {
        QStyledItemDelegate::setModelData(editor, model, index);
    }
    

    }

    QRect BooleanItemDelegate::CheckBoxRect(const QStyleOptionViewItem &view_item_style_options) {
    QStyleOptionButton check_box_style_option;
    QRect check_box_rect = QApplication::style()->subElementRect( QStyle::SE_CheckBoxIndicator, &check_box_style_option);
    QPoint check_box_point(view_item_style_options.rect.x() + view_item_style_options.rect.width() / 2 - check_box_rect.width() / 2,
    view_item_style_options.rect.y() + view_item_style_options.rect.height() / 2 -
    check_box_rect.height() / 2);
    return QRect(check_box_point, check_box_rect.size());
    }

    void BooleanItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {

     int value = index.model()->data(index, Qt::DisplayRole).toInt();
    
     QStyleOptionButton check_box_style_option;
     check_box_style_option.state |= QStyle::State_Enabled;
     if (value == 1) {
         check_box_style_option.state |= QStyle::State_On;
     } else {
         check_box_style_option.state |= QStyle::State_Off;
     }
     check_box_style_option.rect = BooleanItemDelegate::CheckBoxRect(option);
    
     QApplication::style()->drawControl(QStyle::CE_CheckBox, &check_box_style_option, painter);
    

    }
    @

    Can somone please tell me what I am doing wrong?

    Thanks,
    Harry



  • perhaps you should not use the checkbox itself, try to make the cell itself checkable.
    As you want to use a QSqlTableModel, you could use a custom proxy model, derived from QSortFilterProxyModel and add the flag there. then editing is not done via a real combo box :-)



  • Indeed. A proxy model is the way to go here. You only need to reimplement the data(), setData() and the flags() methods to make the column user checkable with a checkbox. That is much simpler than using a custom delegate.



  • If the check sign should be centered, the delegate is in fact needed...
    Otherwise it's in the front of the cell



  • But that would mean I'd need a custom proxy model for every table I use, doesn't it?
    I've been trying to avoid that.

    If I had to do that, would it make more sense QSqlTableModel for every table?
    What would be the benefits in each case?



  • No, you can make a generic proxy model that can make any column checkable, and simply reuse that.



  • Do you have a link of an example handy? I would think I'd have to tweak ony the flags() functions....
    Thanks, Harry



  • If you only tweak flags, you will never show a checked state.

    That's why you have to also implement data and setData.



  • Ok I found something that seems to work.
    (based on this http://www.qtcentre.org/archive/index.php/t-18675.html)

    I'm putting the code here, in case it helps someone else:

    The basic idea is that there is something like a QList that holds the indexes of the columns that you want to have checkboxes (I'm already thinking of adding another QList for readonly columns).
    Actual usage would be
    @
    CheckableSortFilterProxyModel *cfpm = new CheckableSortFilterProxyModel(this);
    QList<int> boolCols;
    boolCols.append( usrModel->fieldIndex("isActive") );
    boolCols.append( usrModel->fieldIndex("isOk") );
    cfpm->setParameters(boolCols);
    cfpm->setSourceModel( mySqlTableModel );
    myTableView->setModel(cfpm);
    @

    Here is the code:

    checkablesortfilterproxymodel.h:
    @
    #ifndef CHECKABLESORTFILTERPROXYMODEL_H
    #define CHECKABLESORTFILTERPROXYMODEL_H

    #include <QSortFilterProxyModel>

    class CheckableSortFilterProxyModel : public QSortFilterProxyModel
    {
    Q_OBJECT
    public:
    explicit CheckableSortFilterProxyModel(QObject *parent = 0);

    void setParameters(QList<int> boolCols);
    

    protected:
    QVariant data(const QModelIndex &index, int role) const;
    bool setData(const QModelIndex &index, const QVariant &value, int role);
    Qt::ItemFlags flags ( const QModelIndex & index ) const;

    signals:

    public slots:

    private:
    QList<int> booleanSet;

    };

    #endif // CHECKABLESORTFILTERPROXYMODEL_H
    @

    checkablesortfilterproxymodel.cpp:
    @
    #include "checkablesortfilterproxymodel.h"

    CheckableSortFilterProxyModel::CheckableSortFilterProxyModel(QObject *parent) :
    QSortFilterProxyModel(parent)
    {
    }

    void CheckableSortFilterProxyModel::setParameters(QList<int> boolCols) {
    booleanSet.clear();
    if (!boolCols.isEmpty()) {
    foreach(int column , boolCols)
    {
    booleanSet.append(column);
    }
    }
    }

    QVariant CheckableSortFilterProxyModel::data(const QModelIndex &index, int role) const {
    if(!index.isValid())
    return QVariant();

    if(booleanSet.contains(index.column()) && (role == Qt::CheckStateRole || role == Qt::DisplayRole)) {
        if (role == Qt::CheckStateRole)
            return index.data(Qt::EditRole).toBool() ? QVariant(Qt::Checked) : QVariant(Qt::Unchecked);
        else if (role == Qt::DisplayRole)
            return QVariant();
    }
    else
        return QSortFilterProxyModel::data(index,role);
    

    }

    bool CheckableSortFilterProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) {
    if(!index.isValid())
    return false;

    if(booleanSet.contains(index.column()) && role==Qt::CheckStateRole)
    {
        QVariant data = (value.toInt()==Qt::Checked) ? QVariant(1) : QVariant (0);
        return QSortFilterProxyModel::setData(index, data, Qt::EditRole);
    }
    else
        return QSortFilterProxyModel::setData(index,value,role);
    

    }

    Qt::ItemFlags CheckableSortFilterProxyModel::flags ( const QModelIndex & index ) const {
    if(!index.isValid())
    return Qt::ItemIsEnabled;

    if(booleanSet.contains(index.column()))
        return Qt::ItemIsUserCheckable | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
    else
        return QSortFilterProxyModel::flags(index);
    

    }
    @



  • I can easily (I think) make a column readonly with the same logic.

    If I wanted to have password fields (in the sense where the actual data is not displayed), would I need to use a delegate, or can it somehow be done with the proxy model?



  • The pure display can be done in the same way, just return some stars and not the text.
    But to enable password like editing, you need a delegate.



  • Readonly works fine.
    If I add another list (eg. readonlySet)

    in the flags function, all I need to add is
    @
    else if (readonlySet.contains(index.column()))
    return Qt::ItemIsSelectable;
    @

    For the password thing, all I've been able to do is return '***' when the columns is in the passwordSet list. When the user clicks on it, the actual text is shown.



  • One thing still not working is that the checkboxes are not aligned in the center.
    How should I handle that?



  • Another issue:
    it doesn't work with a QSqlRelationalTableModel (I've been using a QSqlTableModel).

    The lookup value is displayed correctly, but it is an editable text box, instead of a combo box.
    I am using
    @
    myTableVw->setItemDelegate(new QSqlRelationalDelegate(myTableVw));
    @
    Any ideas?



  • For moving the check box to the center, a custom delegate is needed.
    Also for editing the Password stuff with a password edit.

    by the way, you should make a code sniuppet of this and add it to the wiki :-)



  • I guess the checkbox can stay to the left if it will save me a delegate :)
    The issue with the SqlRelationalTableModel however is much more serious, and a deal breaker if it can't be resolved.



  • I can't resist the urge to complain:
    It looks like another issue that will never be resolved (like other questions I've asked here and in the mailing list)...
    I mean are my questions that stupid? Most things desktop related and especially SQL stuff, seem to be so underdeveloped, you'd think it was developed by a different company.
    I've learnt to live with that, but the fact that noone from the Nokia developers (that read these message) bothers to acknowledge, concern or help with desktop issues, is very frustrating.

    I see pointless, endless discussions about the best way to draw a pixel, or the ill-fated mobile stuff, which would be all good, if such major gaps didn't exist.

    Look at this simple class I've almost done here: why doesn't it exist as part of Qt? There are hundreds (if not thousands) of messages/questions of people asking how to do checkboxes in a tableView.
    Each implements it differently, depending on Google's mood on the day they search for info.



  • Hey hsfougaris,

    a checkable cell is described in the docs with the Qt::ItemIsUserCheckable flag. This is also described in the docs and perhaps in the examples. What else should they do? They have a standard way.

    And as a side note: this is a community forum, not a nokia developers forum. If spome of the Trolls hang around here, it's in their free time.



  • Well, how can you easily use it when using any QSql** models (which is a very common case one would think)? You have to subclass every time...

    I also know this is a community forum, that people like you devote a lot of resources to, and manage to help many people.
    What annoys me is they seem to respond to many questions about almost everything, except desktop and database related stuff.

    thanks,
    harry



  • Back to the main issue, I found this post http://lists.qt.nokia.com/pipermail/qt-interest/2009-January/001833.html which is pretty much about the same thing.

    It seems to be a bug in QSqlRelationalDelegate , and a workaround for the createEditor is discussed.
    But I can't seem to find the code.
    In src\sql\models\qsqlrelationaldelegate.cpp there is only the following
    @
    /*!
    \fn QWidget *QSqlRelationalDelegate::createEditor(QWidget *parent,
    const QStyleOptionViewItem &option,
    const QModelIndex &index) const
    \reimp
    */
    @
    Does anyone know where the actual implementation is?



  • [quote author="hsfougaris" date="1301549859"]Well, how can you easily use it when using any QSql** models (which is a very common case one would think)? You have to subclass every time...
    [/quote]

    Where should the relational model know from if the cell should be checkable. Depending on the returned value (the tyüpe of the QVariant), the editor widget is choosen. What is a default editor for, lets say a boolean, depends whome you ask. It could be a check box, or a combo with true/false.



  • That's why the QTableView class should be much richer in my opinion (like almost every other grid developed). It could have properties like setColumnWidget(...) ...

    The fact of the matter is that trying to use QSql*** with a QTableView with anything other than text is a pain, and there is no straightforward way to handle very common needs and scenarios.
    You can't even center align a column without a delegate!



  • [quote author="hsfougaris" date="1301550103"]Back to the main issue, I found this post http://lists.qt.nokia.com/pipermail/qt-interest/2009-January/001833.html which is pretty much about the same thing.

    It seems to be a bug in QSqlRelationalDelegate , and a workaround for the createEditor is discussed.
    But I can't seem to find the code.
    In src\sql\models\qsqlrelationaldelegate.cpp there is only the following
    @
    /*!
    \fn QWidget *QSqlRelationalDelegate::createEditor(QWidget *parent,
    const QStyleOptionViewItem &option,
    const QModelIndex &index) const
    \reimp
    */
    @
    Does anyone know where the actual implementation is?
    [/quote]

    It seems the code is in the header... :)



  • [quote author="hsfougaris" date="1301551606"]That's why the QTableView class should be much richer in my opinion (like almost every other grid developed). It could have properties like setColumnWidget(...) ...

    The fact of the matter is that trying to use QSql*** with a QTableView with anything other than text is a pain, and there is no straightforward way to handle very common needs and scenarios.
    You can't even center align a column without a delegate![/quote]

    If you need a richer table view, look at some of the commercial solutions that exist. I also found some things I would like to have in a TableView, but I'm sure, not 90% of the user would like to have it, perhaps on 10 %. And I think, it's the same with the SQL stuff. In our company, we use many tables, but not a single QSqlXXX class. We have custom data providers.

    And I think, a QSFPM as "Man in the middle" to achieve this, is not the worst solution.

    And via the delegates, you achieve exaclty the same as with setColumnWidget. Create a delegate and use setColumnDelegate. Model - View - Delegate is a very good pattern indeed. It's a derivate of the Model-View-Controler which is very common.



  • Is there anything other that one ICS makes? (because that is priced insanely...)
    I had a look at one from DevMachines, which looks promising but is not ready for prime time yet.

    I haven't been able to find other ones, so I would appreciate any directions.



  • Also if QSFPM means QSortFilterProxyModel, I agree it's not bad as a solution (especially after what I managed to build with a little help); I just wish I could do a few more things with it...



  • Ok, I created my own subclass of QSqlRelationalDelegate and now everyhting works.

    Here is the related code:
    @

    QWidget *mySqlRelationalDelegate::createEditor(QWidget *aParent, const QStyleOptionViewItem &option, const QModelIndex &index) const {

    const QSqlRelationalTableModel *sqlModel = qobject_cast<const QSqlRelationalTableModel *>(index.model());
    QSqlTableModel *childModel = sqlModel ? sqlModel->relationModel(index.column()) : 0;
    
    if (!childModel )
    {
        const QSortFilterProxyModel* proxyModel = qobject_cast<const QSortFilterProxyModel *>(index.model());
        if (proxyModel)
        {
            sqlModel = qobject_cast<const QSqlRelationalTableModel *>(proxyModel->sourceModel());
            childModel = sqlModel ? sqlModel->relationModel(index.column()) : 0;
        }
    }
    
    if (!childModel)
    {
        return QItemDelegate::createEditor(aParent, option, index);
    }
    
    QComboBox *combo = new QComboBox(aParent);
    combo->setModel(childModel);
    combo->setModelColumn(childModel->fieldIndex(sqlModel->relation(index.column()).displayColumn()));
    combo->installEventFilter(const_cast<mySqlRelationalDelegate *>(this));
    
    return combo;
    

    }

    void mySqlRelationalDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
    {
    QString strVal = "";
    const QSqlRelationalTableModel *sqlModel = qobject_cast<const QSqlRelationalTableModel >(index.model());
    if (!sqlModel )
    {
    const QSortFilterProxyModel
    proxyModel = qobject_cast<const QSortFilterProxyModel *>(index.model());
    if (proxyModel) {
    strVal = proxyModel->data(index).toString();
    }
    } else {
    strVal = sqlModel->data(index).toString();
    }

    QComboBox *combo = qobject_cast<QComboBox *>(editor);
    if (strVal.isEmpty() || !combo) {
        QItemDelegate::setEditorData(editor, index);
        return;
    }
    
    combo->setCurrentIndex(combo->findText(strVal));
    

    }

    void mySqlRelationalDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
    {
    if (!index.isValid())
    return;

    QSqlRelationalTableModel *sqlModel = qobject_cast<QSqlRelationalTableModel *>(model);
    QSortFilterProxyModel* proxyModel = NULL;
    if (!sqlModel )
    {
        proxyModel = qobject_cast<QSortFilterProxyModel *>(model);
        if (proxyModel)
             sqlModel = qobject_cast<QSqlRelationalTableModel *>(proxyModel->sourceModel());
    }
    
    QSqlTableModel *childModel = sqlModel ? sqlModel->relationModel(index.column()) : 0;
    QComboBox *combo = qobject_cast<QComboBox *>(editor);
    if (!sqlModel || !childModel || !combo) {
        QItemDelegate::setModelData(editor, model, index);
        return;
    }
    
    int currentItem = combo->currentIndex();
    int childColIndex = childModel->fieldIndex(sqlModel->relation(index.column()).displayColumn());
    int childEditIndex = childModel->fieldIndex(sqlModel->relation(index.column()).indexColumn());
    
    
    if (proxyModel) {
        proxyModel->setData(index, childModel->data(childModel->index(currentItem, childColIndex), Qt::DisplayRole), Qt::DisplayRole);
        proxyModel->setData(index, childModel->data(childModel->index(currentItem, childEditIndex), Qt::EditRole), Qt::EditRole);
    } else {
        sqlModel->setData(index, childModel->data(childModel->index(currentItem, childColIndex), Qt::DisplayRole), Qt::DisplayRole);
        sqlModel->setData(index, childModel->data(childModel->index(currentItem, childEditIndex), Qt::EditRole), Qt::EditRole);
    }
    

    }
    @

    I'll try making it a Wiki!



  • Thanks for posting back the code!



  • [quote author="hsfougaris" date="1301553694"]Also if QSFPM means QSortFilterProxyModel, I agree it's not bad as a solution (especially after what I managed to build with a little help); I just wish I could do a few more things with it...[/quote]

    Yes, QSFPM is a (here) common abbreveation for QSortFilterProxyModel :-)

    Perhaps we should add a wiki page with common abbreveation sused here :-)



  • <dreaming>
    We should have a feature where such abbreviations are automatically underlined with a dotted line (perhaps only for the first use in a post), which show an explanation on the meaning of that abbreviation (perhaps automagically retreived from that wiki page you talk about).
    </dreaming>



  • I tried to post the source as a wiki ( I created 2 separate pages - one for the delegate and one for the sortproxy thing), but I'm sure I messed it up.
    Am I supposed to add them to a category, or does an admin do that?

    I tried adding the [[Category... thing but it appeared as plain text.



  • The category thing should work, but you do need to remove spaces between the brackets. Problem is: that is hard to show in the wiki itself, because the text will then be interpreted as formatting...

    If you want: post a link, and I'll have a look.





  • author="Andre" date="1301563925"]<dreaming>
    We should have a feature where such abbreviations are automatically underlined with a dotted line (perhaps only for the first use in a post), which show an explanation on the meaning of that abbreviation (perhaps automagically retreived from that wiki page you talk about).
    </dreaming>
    [/quote]

    Make a feature request from this in Jira :-) The idea is cool.

    [quote author="hsfougaris" date="1301564361"]I tried to post the source as a wiki ( I created 2 separate pages - one for the delegate and one for the sortproxy thing), but I'm sure I messed it up.
    Am I supposed to add them to a category, or does an admin do that?

    I tried adding the [[Category... thing but it appeared as plain text.[/quote]

    I added the category for you. Just have a look with edit :-)



  • Thanks... Do you have any references for suppliers of tableView alternatives?



  • Hi,

    not more than you already found: devmachines and ICS. I'm sure, there are more, but I don't know who. You can search "Qt apps":http://www.qt-apps.org/

    One side note to your wiki:

    I found one error:

    the data/setdata and flags methods should be public, as they are public in the base class. Making the protected in your derived class leads to strange behavior:

    People using the base class pointers are able to call them, people using the pointers of type of your class may not. Please make them public.





  • Did you already do it in the wiki? Because I just looked at the wiki, and it's been removed.



  • [quote author="Andre" date="1301565818"]
    [quote author="hsfougaris" date="1301564749"]http://developer.qt.nokia.com/wiki/QSortFilterProxyModel_subclass_for_readonly_columns_columns_with_checkboxes_and_password_columns

    http://developer.qt.nokia.com/wiki/QSqlRelationalDelegate_subclass_that_works_with_QSqlRelationalTableModel

    [/quote]

    Pages, including categories, look fine to me.

    [/quote]

    Yes, Gerolf was nice enough to fix them for me.



  • [quote author="hsfougaris" date="1301565899"]

    Yes, Gerolf was nice enough to fix them for me.[/quote]

    Damn, that guy is fast ;-)


Log in to reply
 

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