天天看點

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;
}
           

繼續閱讀