因为工作的原因,需要在Qt里面用到multi-select combo box,就是那种可以在下拉框中选中多个条目并展示出来的combo box。Qt design里面自带的combo box只能选择一个选项,于是又去了一下,并没有发现有特别符合自己的需求的,于是就自己动手实现了一个MultiSelectComboBox,先上结果,代码和效果图都放在github上了:Multi-select Combo Box
最终的结果如下图所示:

整个控件继承于Qt原生的Combobox,在原生Combobox的基础上主要做了两处修改,分别如上图中1,2所示。1中原生Combobox主体部分换成了一个line edit,用于写入选择的结果,这里我用";"做多个选择之间的分隔符,下文中我们将统称这部分为mLineEdit; 2中combobox的popup部分,这里主要用两种自定义控件来构成,一是位于popup第一排的一个line edit,用于接收用户输入后搜索并调整下方popup条目显示,二是剩下的所有条目,以check box的方式显示每一条预设选项,有“选中”和“未选中”两种状态,下文中我们将统称这里的条目为item。
下面讲一下我认为实现这个控件时需要注意的一些细节:
1. mLineEdit要设置成readonly,因为只需要显示选中的条目,不需要任何的编辑;
2. 上图1中的部分被设置成mLineEdit以后,会变成在popup收起状态时,只能通过点击最右边的向下的三角形才能显示出popup,这样的话会有一个比较不好的体验,因为点那个小三角形其实还是挺费劲的,就是这个地方:

我们期望的效果应该是点这个小三角形和line edit都能呼出popup,所以做了如下修改:
2.1 重写了MultiSelectComboBox的eventFilter,单独处理这个line edit的鼠标点击事件:
bool MultiSelectComboBox::eventFilter(QObject* aObject, QEvent* aEvent) { if(aObject == mLineEdit && aEvent->type() == QEvent::MouseButtonRelease) { showPopup(); return false; } return false; }2.2 在MultiSelectComboBox的构造函数中,为此line edit安装该event filter:
mLineEdit->installEventFilter(this);
3. 在原生combobox中,因为是单选,所以选中一个item后popup就会收起,但如果是多选的话,我们显然不希望这样,我们希望的是在完成多个条目的选择之后再收起,那么我们就需要对原本的hidePopup函数做一些修改,即在选中2中的条目时,不hide popup,其余时候正常hide popup,于是重写hidePopup函数如下:
void MultiSelectComboBox::hidePopup()
{
int width = this->width();
int height = mListWidget->height();
int x = QCursor::pos().x() - mapToGlobal(geometry().topLeft()).x() + geometry().x();
int y = QCursor::pos().y() - mapToGlobal(geometry().topLeft()).y() + geometry().y();
if (x >= 0 && x <= width && y >= this->height() && y <= height + this->height())
{
// Item was clicked, do not hide popup
}
else
{
QComboBox::hidePopup();
}
}
4. 在item被选中时,需要通知mLineEdit选中的条目有变化,显示的信息需要修改,这里我以“;”作为分隔符,各位可根据自己的需求自行更改:
void MultiSelectComboBox::stateChanged(int aState)
{
Q_UNUSED(aState);
QString selectedData("");
int count = mListWidget->count();
for (int i = 1; i < count; ++i)
{
QWidget *widget = mListWidget->itemWidget(mListWidget->item(i));
QCheckBox *checkBox = static_cast<QCheckBox *>(widget);
if (checkBox->isChecked())
{
selectedData.append(checkBox->text()).append(";");
}
}
if (selectedData.endsWith(";"))
{
selectedData.remove(selectedData.count() - 1, 1);
}
if (!selectedData.isEmpty())
{
mLineEdit->setText(selectedData);
}
else
{
mLineEdit->clear();
}
mLineEdit->setToolTip(selectedData);
emit selectionChanged();
}
5. 原生的combobox中有一个currentText()函数,用于以QString的类型获取选中的结果,但是对于多选来说,选中结果会有多条,所以这里重新写了一个currentText(),使其返回类型为QStringList,方便后续处理:
QStringList MultiSelectComboBox::currentText()
{
QStringList emptyStringList;
if(!mLineEdit->text().isEmpty())
{
emptyStringList = mLineEdit->text().split(';');
}
return emptyStringList;
}
6. 原生的count函数会返回目前的popup中有多少条item,这个也需要重写,因为有个search bar,那一条不应该算在count里面:
int MultiSelectComboBox::count() const
{
int count = mListWidget->count() - 1;// Do not count the search bar
if(count < 0)
{
count = 0;
}
return count;
}
7. MultiSelectCombobox的鼠标滚轮事件和按键事件应该被屏蔽掉,不然会造成一些意外的异常:
void MultiSelectComboBox::wheelEvent(QWheelEvent* aWheelEvent)
{
// Do not handle the wheel event
Q_UNUSED(aWheelEvent);
}
void MultiSelectComboBox::keyPressEvent(QKeyEvent* aEvent)
{
// Do not handle key event
Q_UNUSED(aEvent);
}
基本上上述就是一些比较核心的点了,希望上述内容能够对读到这篇文章的人有所帮助。





