天天看点

QTreeView三态复选

QTreeView三态复选

在Qt的model/view中,QStandardItem是可以设置复选效果的,在QTreeView和QTableView等中以QCheckBox的样子显示出来。

item->setCheckable(true);    // 设置是否能复选(默认只有√和×两种形态)
item->setTristate(true);     // 设置在复选效果中,是否能出现三态(即部分选中的■)
           
QTreeView三态复选
QTreeView三态复选

在低版本的Qt中,QTreeView实现复选需要手动进行逻辑设置,只设置个setCheckable()是不能实现点击父节点全选子节点的效果的。因此,当任意一个item的选择状态改变时,需要进行逻辑判断,重新设置父/子节点的选择状态。代码实现原理是捕捉QStandardItemModel中的itemchanged()信号,在槽函数中写一下逻辑即可。

// This signal is emitted whenever the data of item has changed.
void QStandardItemModel::itemChanged(QStandardItem *item)
           

 但要注意的是,该信号十分“敏感”,任何一个item的状态改变,都要导致信号发射然后调用槽函数。比如点击父节点会全选子节点,此时如果代码是for循环将子节点全部setCheckState(Qt::Checked),那么每循环一次都会调用该槽函数。。。。。

此外,对于父节点在进行点击操作时,只有“全选/全不选”两种状态,但对子节点进行操作时,需要父节点还具备“部分选中”的第三种状态。网上的大部分代码,都没有针对父节点的点击操作进行优化,导致需要点击两次父节点才能实现全选的操作。

实现如下:

MainWindow.h

private slots :
    void slot_treeitem_changed(QStandardItem * item);               // 逻辑处理函数
    Qt::CheckState getTreeItemCheckStatus(QStandardItem * item);    // 节点状态判断函数

private:
    QPointer<QStandardItemModel> tree_model;
           

MainWindow.cpp 

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    tree_model = new QStandardItemModel(ui->treeView);
    ui->treeView->setModel(tree_model);
    for(int i = 0 ; i < 10; i++)
    {
        QStandardItem * parent_item = new QStandardItem("AAA");
        parent_item->setCheckable(true);
        parent_item->setTristate(true);

        for(int j = 0 ; j < 5; j++)
        {
            QStandardItem * item = new QStandardItem("BBB");
            item->setCheckable(true);
            parent_item->appendRow(item);
        }
        tree_model->appendRow(parent_item);
    }

    // signal and slot
    connect(tree_model, SIGNAL(itemChanged(QStandardItem *)) , this ,SLOT(slot_treeitem_changed(QStandardItem *)));
}

void MainWindow::slot_treeitem_changed(QStandardItem * item)
{
    disconnect(tree_model, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slot_treeitem_changed(QStandardItem*)));

    if (item == NULL || !item->isCheckable())
        return;

    int state = item->checkState();
    if(item->hasChildren())
    {
        Qt::CheckState item_status = (state == Qt::Unchecked ? Qt::Unchecked :  Qt::Checked);
        for(int i = 0; i < item->rowCount(); ++i)
        {
            QStandardItem * childItem = item->child(i);
            if(childItem->isCheckable())
                childItem->setCheckState(item_status);
        }
        if (state == Qt::PartiallyChecked)
            item->setCheckState(Qt::Checked);
    }
    else
    {
        QStandardItem * parent_item = item->parent();
        if(parent_item->isCheckable() == false)
            return;

        int sibling_state = getTreeItemCheckStatus(item);
        if(sibling_state == Qt::PartiallyChecked)
        {
            if(parent_item->isTristate())
                parent_item->setCheckState(Qt::PartiallyChecked);
        }
        else if(sibling_state == Qt::Checked)
            parent_item->setCheckState(Qt::Checked);
        else    parent_item->setCheckState(Qt::Unchecked);
    }

    connect(tree_model, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slot_treeitem_changed(QStandardItem*)));
}

Qt::CheckState MainWindow::getTreeItemCheckStatus(QStandardItem * item)
{
    QStandardItem * parent_item = item->parent();
    if(parent_item == NULL)
        return item->checkState();

    int checkedCount = 0, unCheckedCount = 0, state;
    for(int i = 0; i < parent_item->rowCount(); ++i)
    {
        QStandardItem * siblingItem = parent_item->child(i);
        state = siblingItem->checkState();

        if(state == Qt::PartiallyChecked)
            return Qt::PartiallyChecked;
        else if(state == Qt::Unchecked)
            ++unCheckedCount;
        else    ++checkedCount;
    }

    if(checkedCount == 0)
        return Qt::Unchecked;
    if(checkedCount > 0 && unCheckedCount > 0)
        return Qt::PartiallyChecked;

    return Qt::Checked;
}
           

继续阅读