[Qt] 实现Multi-select Combo Box

因为工作的原因,需要在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);
}

基本上上述就是一些比较核心的点了,希望上述内容能够对读到这篇文章的人有所帮助。

评论 7
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ThisIsClark

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值