商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > Qt实现带多选功能的组合复选框

Qt实现带多选功能的组合复选框

  发布于2026-07-05 阅读(0)

扫一扫,手机访问

1.整体功能解析

先看看实际效果长什么样:

Qt实现带多选功能的组合复选框

这个QMaskComboBox继承自QComboBox,主要干的事就是给下拉框加上复选框多选能力。它的核心特性包括:

  • uint64_t位掩码来管理选项,天然支持多选
  • 下拉列表里塞的是QListWidget + QCheckBox,勾选体验和普通复选框一样
  • 文本框会拼接显示所有选中的项,工具提示则展示完整列表
  • 专门提供了一个selectMaskPopup信号,下拉框隐藏的时候会把当前掩码值传出去

2.核心模块拆解

2.1.头文件结构(QMaskComboBox.h)

class QMaskComboBox : public QComboBox
{
    Q_OBJECT
public:
    using mask_vector = QVector>;

    explicit QMaskComboBox(QWidget* parent = nullptr);
    explicit QMaskComboBox(const mask_vector& maskList, uint64_t val = 0, QWidget* parent = nullptr);

    const mask_vector& mask() const;
    void setMask(const mask_vector& val);
    void setMask(const mask_vector& val, uint64_t select);

    uint64_t selectMask() const noexcept;
    void setSelectMask(uint64_t val);

    void hidePopup() override;
    bool eventFilter(QObject* watched, QEvent* evt) override;

Q_SIGNALS:
    void selectMaskPopup(qulonglong);

private:
    void refresh();
private slots:
    void onStateChanged(uint64_t bits, int state);

private:
    QListWidget* m_pListWidget;
    mask_vector m_mask;
    uint64_t m_selectMask;
};
  • mask_vector:存的是“掩码值 - 显示文本”的映射关系
  • m_selectMask:记录当前哪些位被选中
  • refresh():核心刷新函数,每次调用都会重建下拉列表
  • onStateChanged:复选框状态发生变化时的回调,负责更新掩码和文本框显示

2.2.关键实现解析

1. 构造函数与事件过滤

QMaskComboBox::QMaskComboBox(QWidget* parent)
    : QComboBox(parent)
    , m_pListWidget(nullptr)
    , m_selectMask(0)
{
    setEditable(true);
    lineEdit()->installEventFilter(this);
}

bool QMaskComboBox::eventFilter(QObject* watched, QEvent* evt)
{
    if (watched == lineEdit() && evt->type() == QEvent::MouseButtonPress)
    {
        showPopup();
        return true;
    }
    return QComboBox::eventFilter(watched, evt);
}

一个关键细节:开启了setEditable(true),然后在refresh()里把lineEdit设为只读——这样用户没法直接修改文本,但点击文本框时又能触发下拉弹出。事件过滤器就是为了干这个:拦截MouseButtonPress事件,直接调用showPopup(),行为跟原生QComboBox保持一致。

2. refresh() 函数——整个控件的核心战场

void QMaskComboBox::refresh()
{
    clear();
    delete model();
    delete view();

    lineEdit()->setReadOnly(true);
    m_pListWidget = new QListWidget(this);
    m_pListWidget->setObjectName("listView");

    QString selectData;
    QString tipData;

    for (auto& kv : m_mask)
    {
        uint64_t bits = kv.first;
        QString desc = kv.second;

        QListWidgetItem* pItem = new QListWidgetItem(m_pListWidget);
        pItem->setData(Qt::UserRole, QVariant::fromValue(bits));

        QCheckBox* pCheckBox = new QCheckBox(this);
        pCheckBox->setText(desc);
        bool checked = (bits & m_selectMask) == bits;
        pCheckBox->setChecked(checked);

        if (checked)
        {
            if (!selectData.isEmpty())
            {
                selectData.append(";");
                tipData.append("n");
            }
            selectData.append(kv.second);
            tipData.append(kv.second);
        }

        connect(pCheckBox, &QCheckBox::stateChanged, 
                [this, bits](int state) { onStateChanged(bits, state); });

        m_pListWidget->addItem(pItem);
        m_pListWidget->setItemWidget(pItem, pCheckBox);
    }

    setModel(m_pListWidget->model());
    setView(m_pListWidget);

    if (!selectData.isEmpty())
    {
        lineEdit()->setText(selectData);
        lineEdit()->setToolTip(tipData);
    }
    else
    {
        lineEdit()->clear();
    }
}

每次调用refresh()都会清掉旧的modelview,重新造一个QListWidget。遍历m_mask的时候,对每个选项创建一个QListWidgetItem和一个QCheckBox,并根据m_selectMask初始化复选框勾选状态。同时拼接出文本框显示的字符串和工具提示文本。最后把QListWidget的模型和视图塞回QComboBox里——这招算是取了个巧,但效果很直接。

3. onStateChanged 槽函数——状态的同步与刷新

void QMaskComboBox::onStateChanged(uint64_t bits, int state)
{
    if (state)
        m_selectMask |= bits;
    else
        m_selectMask &= ~bits;

    QString selectData;
    QString tipData;

    int nCount = m_pListWidget->count();
    for (int i = 0; i < nCount; ++i)
    {
        QListWidgetItem* pItem = m_pListWidget->item(i);
        QWidget* pWidget = m_pListWidget->itemWidget(pItem);
        QCheckBox* pCheckBox = qobject_cast(pWidget);

        const auto& kv = m_mask[i];
        bool checked = (kv.first & m_selectMask) == kv.first;
        pCheckBox->setChecked(checked);

        if (checked)
        {
            if (!selectData.isEmpty())
            {
                selectData.append(";");
                tipData.append("n");
            }
            selectData.append(kv.second);
            tipData.append(kv.second);
        }
    }

    if (!selectData.isEmpty())
    {
        lineEdit()->setText(selectData);
        lineEdit()->setToolTip(tipData);
    }
    else
    {
        lineEdit()->clear();
    }
}

逻辑很直白:勾选就按位或,取消就按位与非。然后遍历所有复选框,根据新的m_selectMask同步状态,同时更新文本框和工具提示。注意这里有个细节:遍历时又重新调了一遍setChecked,其实是为了保证多选框之间的一致性(比如通过代码修改掩码后,界面能跟着刷新)。

4. hidePopup() 重写——给外部通风报信

void QMaskComboBox::hidePopup()
{
    emit selectMaskPopup(m_selectMask);
    QComboBox::hidePopup();
}

下拉框一隐藏,就把当前的掩码值通过信号发出去。外部只需连接这个信号,就能实时拿到用户的选择结果。

3.完整代码示例

// QMaskComboBox.h
#ifndef QMASKCOMBOBOX_H
#define QMASKCOMBOBOX_H

#include 
#include 
#include 

class QMaskComboBox : public QComboBox
{
    Q_OBJECT
public:
    using mask_vector = QVector>;

    explicit QMaskComboBox(QWidget* parent = nullptr);
    explicit QMaskComboBox(const mask_vector& maskList, uint64_t val = 0, QWidget* parent = nullptr);

    const mask_vector& mask() const;
    void setMask(const mask_vector& val);
    void setMask(const mask_vector& val, uint64_t select);

    uint64_t selectMask() const noexcept;
    void setSelectMask(uint64_t val);

    void hidePopup() override;
    bool eventFilter(QObject* watched, QEvent* evt) override;

Q_SIGNALS:
    void selectMaskPopup(qulonglong);

private:
    void refresh();
    void updateText();

private slots:
    void onStateChanged(int state);

private:
    mask_vector m_mask;
    uint64_t m_selectMask = 0;
    QListWidget* m_pListWidget = nullptr;
};

#endif // QMASKCOMBOBOX_H
// QMaskComboBox.cpp
#include "QMaskComboBox.h"

QMaskComboBox::QMaskComboBox(QWidget* parent)
    : QComboBox(parent)
{
    setEditable(true);
    lineEdit()->setReadOnly(true);
    lineEdit()->installEventFilter(this);
}

QMaskComboBox::QMaskComboBox(const mask_vector& maskList, uint64_t val, QWidget* parent)
    : QMaskComboBox(parent)
{
    m_mask = maskList;
    m_selectMask = val;
    refresh();
}

const QMaskComboBox::mask_vector& QMaskComboBox::mask() const
{
    return m_mask;
}

void QMaskComboBox::setMask(const mask_vector& val)
{
    m_mask = val;
    refresh();
}

void QMaskComboBox::setMask(const mask_vector& val, uint64_t select)
{
    m_mask = val;
    m_selectMask = select;
    refresh();
}

uint64_t QMaskComboBox::selectMask() const noexcept
{
    return m_selectMask;
}

void QMaskComboBox::setSelectMask(uint64_t val)
{
    m_selectMask = val;
    updateText();
    refresh();
}

bool QMaskComboBox::eventFilter(QObject* watched, QEvent* evt)
{
    if (watched == lineEdit() && evt->type() == QEvent::MouseButtonPress)
    {
        showPopup();
        return true;
    }
    return QComboBox::eventFilter(watched, evt);
}

void QMaskComboBox::hidePopup()
{
    emit selectMaskPopup(m_selectMask);
    QComboBox::hidePopup();
}

void QMaskComboBox::refresh()
{
    // 清理旧的 view 和 model
    if (m_pListWidget)
    {
        delete m_pListWidget;
        m_pListWidget = nullptr;
    }

    clear();
    setModel(nullptr);
    setView(nullptr);

    m_pListWidget = new QListWidget(this);
    m_pListWidget->setObjectName("listView");

    for (auto& kv : m_mask)
    {
        uint64_t bits = kv.first;
        QString desc = kv.second;

        QListWidgetItem* pItem = new QListWidgetItem(m_pListWidget);
        QCheckBox* pCheckBox = new QCheckBox(desc, m_pListWidget);
        pCheckBox->setProperty("maskBits", bits);
        bool checked = (bits & m_selectMask) == bits;
        pCheckBox->setChecked(checked);

        connect(pCheckBox, &QCheckBox::stateChanged, this, &QMaskComboBox::onStateChanged);

        m_pListWidget->setItemWidget(pItem, pCheckBox);
    }

    setModel(m_pListWidget->model());
    setView(m_pListWidget);
    updateText();
}

void QMaskComboBox::updateText()
{
    QString selectData;
    QString tipData;

    for (auto& kv : m_mask)
    {
        if ((kv.first & m_selectMask) == kv.first)
        {
            if (!selectData.isEmpty())
            {
                selectData.append(";");
                tipData.append("n");
            }
            selectData.append(kv.second);
            tipData.append(kv.second);
        }
    }

    if (!selectData.isEmpty())
    {
        lineEdit()->setText(selectData);
        lineEdit()->setToolTip(tipData);
    }
    else
    {
        lineEdit()->clear();
        lineEdit()->setToolTip("");
    }
}

void QMaskComboBox::onStateChanged(int state)
{
    QCheckBox* pCheckBox = qobject_cast(sender());
    if (!pCheckBox) return;

    uint64_t bits = pCheckBox->property("maskBits").toULongLong();
    if (state)
        m_selectMask |= bits;
    else
        m_selectMask &= ~bits;

    updateText();
}

4.使用示例

// 定义掩码选项
QMaskComboBox::mask_vector masks = {
    {0x01, "选项A"},
    {0x02, "选项B"},
    {0x04, "选项C"},
    {0x08, "选项D"}
};

// 创建控件并设置选项
QMaskComboBox* combo = new QMaskComboBox(this);
combo->setMask(masks, 0x01 | 0x04); // 默认选中选项A和C

// 监听掩码变化
connect(combo, &QMaskComboBox::selectMaskPopup, this, [](qulonglong mask) {
    qDebug() << "当前选中掩码:" << mask;
});
本文转载于:https://www.jb51.net/program/366599cpq.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注