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

目录

1.整体功能解析

2.核心模块拆解

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

2.2.关键实现解析

3.完整代码示例

4.使用示例


1.整体功能解析

实现效果如下:

带复选框多选功能的掩码组合框 QMaskComboBox ,继承自 QComboBox,核心特性:

  • uint64_t 位掩码管理选项,支持多选
  • 下拉列表用 QListWidget + QCheckBox 实现
  • 文本框显示选中项拼接结果,工具提示显示完整选中项
  • 提供信号 selectMaskPopup,在下拉框隐藏时通知当前掩码值

2.核心模块拆解

2.1.头文件结构(QMaskComboBox.h

cpp 复制代码
class QMaskComboBox : public QComboBox
{
    Q_OBJECT
public:
    using mask_vector = QVector<std::pair<uint64_t, QString>>;

    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.构造函数与事件过滤

cpp 复制代码
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),并设置 lineEdit 为只读(在 refresh() 中),让用户无法直接编辑文本
  • 事件过滤器:点击文本框时直接弹出下拉框,和原生 QComboBox 行为对齐

2.refresh() 函数(核心逻辑)

cpp 复制代码
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,为每个选项创建 QListWidgetItemQCheckBox
  • 根据 m_selectMask 初始化复选框状态,并拼接选中项文本
  • 连接复选框的 stateChanged 信号到 onStateChanged

3.onStateChanged 槽函数

cpp 复制代码
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<QCheckBox*>(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:选中则按位或,取消则按位与非
  • 遍历所有复选框,同步状态并更新文本框显示

4.hidePopup() 重写

cpp 复制代码
void QMaskComboBox::hidePopup()
{
    emit selectMaskPopup(m_selectMask);
    QComboBox::hidePopup();
}

下拉框隐藏时,发射 selectMaskPopup 信号,通知外部当前掩码值

3.完整代码示例

cpp 复制代码
// QMaskComboBox.h
#ifndef QMASKCOMBOBOX_H
#define QMASKCOMBOBOX_H

#include <QComboBox>
#include <QListWidget>
#include <QCheckBox>

class QMaskComboBox : public QComboBox
{
    Q_OBJECT
public:
    using mask_vector = QVector<std::pair<uint64_t, QString>>;

    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
cpp 复制代码
// 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<QCheckBox*>(sender());
    if (!pCheckBox) return;

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

    updateText();
}

4.使用示例

cpp 复制代码
// 定义掩码选项
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;
});
相关推荐
软泡芙6 小时前
【C# 】各种等待大全:从入门到精通
开发语言·c#·log4j
郭源潮16 小时前
从8k嘈杂到16k清晰,我是如何使用RNNoise+libresample构建音频降噪管道的?
c++·音视频·实时音视频
@小码农6 小时前
2026年信息素养大赛【星火征途】图形化编程复赛和决赛模拟题B
开发语言·数据结构·c++·算法
JMchen1236 小时前
NDK新趋势——Rust与Android深度集成实战
android·开发语言·rust·jni·内存安全·android ndk·移动端性能
代码羊羊6 小时前
Rust 闭包全方位详解:语法、捕获规则、Fn 三特征、返回值实战
开发语言·后端·rust
tjl521314_216 小时前
02C++ 静态变量与链接性
java·jvm·c++
柳鲲鹏6 小时前
QT:正确延时调用,Cannot create children for a parent that is in a different thread.
服务器·数据库·qt
(Charon)6 小时前
【C++/Qt】Qt 实现 WebSocket 测试工具:连接、消息收发与通信日志
c++·qt·websocket