Ingrid Wu
Ingrid Wu

Reputation: 81

Qt - QTreeView and custom model with checkbox columns

I wanted to have a tree view which shows the item name, the item description, and two related Boolean values in respective columns. I started by modifying the Editable Tree Mode example, so there's a TreeModel that keeps track of a group of TreeItems, each of which not only has a list of child TreeItems, but also a list of QVariants which stores a set of values that can later be displayed in columns in the QTreeView.

I managed to add two more columns for two Boolean values. I also searched through the net on how to add checkboxes for QTreeView and QAbstractItemModel. I managed to have the checkboxes on the two Boolean columns working okay, as well as the rest of the tree hierarchy. Yet all the items in each column renders a checkbox and a line of text now.

Qt TreeView with Checkboxes

Here's the parts where I've modified from the example, mainly within TreeModel.

treemodel.cpp:

bool TreeModel::isBooleanColumn( const QModelIndex &index ) const
{
    bool bRet = false;
    if ( !index.isValid() )
    {
    }
    else
    {
        bRet = ( index.column() == COLUMN_BOL1 ) || ( index.column() == COLUMN_ BOL2 );
    }
    return bRet;
}

Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return 0;
    if ( isBooleanColumn( index ) )
    {
        return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
    }
    else
    {
        return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
    }
}

QVariant TreeModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();
    if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::CheckStateRole )
        return QVariant();
    TreeItem *item = getItem(index);
    if ( role == Qt::CheckStateRole && isBooleanColumn( index ) )
    {
        Qt::CheckState eChkState = ( item->data( index.column() ).toBool() ) ? Qt::Checked : Qt::Unchecked;
        return eChkState;
    }
    return item->data(index.column());
}

bool TreeModel::setData(const QModelIndex &index, const QVariant &value,
                        int role)
{
    if (role != Qt::EditRole && role != Qt::CheckStateRole )
        return false;
    TreeItem *item = getItem(index);
    bool result;
    if ( role == Qt::CheckStateRole && isBooleanColumn( index ) )
    {
        Qt::CheckState eChecked = static_cast< Qt::CheckState >( value.toInt() );
        bool bNewValue = eChecked == Qt::Checked;
        result = item->setData( index.column(), bNewValue );
    }
    else
    {
        result = item->setData(index.column(), value);
    }
    if (result)
        emit dataChanged(index, index);
    return result;
}

mainwindow.cpp:

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
    …
    QStringList headers;
    headers << tr("Title") << tr("Description") << tr("Hide") << tr("Lock");
    QFile file(":/default.txt");
    file.open(QIODevice::ReadOnly);
    TreeModel *model = new TreeModel(headers, file.readAll());
    file.close();

    …
}

The checkboxes under the non-Boolean columns don't respond to user input, and the text under the Boolean columns aren't editable. So functionality-wise there's nothing wrong, but it's still bothersome as far as UI goes.

I'm moving onto having QTreeWidget do the same thing. Meanwhile, I'm couldn't help but wonder if there's something else I'm missing here. I heard one solution is to have a custom delegate; is that the only option?

If there's anyone who can point out what else I need to do, or provide a similar example, I will greatly appreciate it.

Upvotes: 8

Views: 16438

Answers (3)

Ryan Hope
Ryan Hope

Reputation: 512

The reason this is happening is related to a "bug" in peoples implementation of the models data method.

In the example below, only column 2 should show a checkbox.

Problem code:

if role == Qt.CheckStateRole:
    if index.column() == 2:
        if item.checked:
            return Qt.Checked
        else:
            return Qt.Unchecked

Correct code:

if role == Qt.CheckStateRole:
    if index.column() == 2:
        if item.checked:
            return Qt.Checked
        else:
            return Qt.Unchecked
    return None

In the problematic code, table cells that should not have a checkbox decorator were getting missed and processed by a catch all role handler farther down in the code.

Upvotes: 0

user3605398
user3605398

Reputation: 11

I had this problem. It occurred in TreeModel::parent() method due to passing child.column() value to createIndex() method. It should be 0 instead. So, instead of

createIndex(parentItem->childNumber(), child.column(), parentItem);

should be

createIndex(parentItem->childNumber(), 0, parentItem);

Upvotes: 1

user2832323
user2832323

Reputation: 163

I think the problem is in the Data method. You should return QVariant() When the role is CheckStateRole but the column is not boolean.

Upvotes: 1

Related Questions