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