在Qt的model/view中,QStandardItem是可以設定複選效果的,在QTreeView和QTableView等中以QCheckBox的樣子顯示出來。
item->setCheckable(true); // 設定是否能複選(預設隻有√和×兩種形态)
item->setTristate(true); // 設定在複選效果中,是否能出現三态(即部分選中的■)
在低版本的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;
}