教你如何实现带复选框的ComboBox(自定义QComboBox)

Qt提供的QComboBox只能选择其中一个选项,无法实现同时选中多个,而实际工程项目中,下拉框中带复选框的需求比比皆是。阅读了网上大量的博客,但是没有发现一个能完美的实现该功能的ComboBox,都存在各种未解决的bug缺陷,样子是那么回事儿,但是里子经不起推敲。经过笔者多次熬夜,接近通宵,工程中反复使用、测试,bug逐渐被解决。

请读者转载时,注明来源,谢谢!!!

废话不多说,下面就教大家如何实现。

一、效果图

1、初始化样式

教你如何实现带复选框的ComboBox(自定义QComboBox)_第1张图片

2、展开样式

教你如何实现带复选框的ComboBox(自定义QComboBox)_第2张图片

3、选择多个item的样式

教你如何实现带复选框的ComboBox(自定义QComboBox)_第3张图片

4、选择后的样式,鼠标hover时,显示被选中的item文本

教你如何实现带复选框的ComboBox(自定义QComboBox)_第4张图片

二、源码展示

1、头文件XComboBox.h

#pragma once

#include 
#include 
#include 
#include 

class QLineEdit;
class QListView;

struct ItemInfo
{
	int idx;
	QString str;
	QVariant userData;
	bool bChecked;

	ItemInfo()
	{
		idx = -1;
		str = QString("");
		userData = QVariant();
		bChecked = false;
	}
};

// 事件过滤器
class KeyPressEater : public QObject
{
    Q_OBJECT
public:
    KeyPressEater(QObject* parent=nullptr):QObject(parent) {}
    ~KeyPressEater() {}
signals:
    void sigActivated(int idx);

protected:
    bool eventFilter(QObject *obj, QEvent *event)
    {
        if (event->type() == QEvent::KeyPress)
        {
            QKeyEvent *keyEvent = static_cast(event);
            if (keyEvent->key() == Qt::Key_Space)
            {
                QListView* lstV = qobject_cast(obj);
                if (nullptr != lstV)
                {
                    int idx = lstV->currentIndex().row();
                    if (-1 != idx)
                    {
                        emit sigActivated(idx);
                    }
               }
            }
            else if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down)
            {
                return QObject::eventFilter(obj, event);
            }

            return true;
        }
        else {
            // standard event processing
            return QObject::eventFilter(obj, event);
        }
    }
};

class XComboBox : public QComboBox
{
	Q_OBJECT

public:
	XComboBox(QWidget *parent = Q_NULLPTR);
	~XComboBox();

	// 添加item
	void AddItem(const QString& str, bool bChecked = false, QVariant &userData = QVariant());
	void AddItems(const QList& lstItemInfo);
	void AddItems(const QMap& mapStrChk);
	void AddItems(const QList& lstStr);
	// 删除item
	void RemoveItem(int idx);
	// 清空item
	void Clear();
	// 获取选中的数据字符串列表
	QStringList GetSelItemsText();
	// 获取选中item的信息
	QList GetSelItemsInfo();
	// 获取item文本
	QString GetItemText(int idx);
	// 获取item信息
	ItemInfo GetItemInfo(int idx);

signals:
	// popup显示信号
	void showingPopup();
	// popup隐藏信号
	void hidingPopup();

protected:
	void showPopup();
	// 重写QComboBox的hidePopup函数
	// 目的选择过程中,不隐藏listview
	void hidePopup();
	virtual void mousePressEvent(QMouseEvent * event);
	virtual void mouseReleaseEvent(QMouseEvent * event);
	virtual void mouseMoveEvent(QMouseEvent * event);

private:
	void UpdateText();

private slots:
    void sltActivated(int idx);

private:
	QLineEdit* pLineEdit;
    QListView* pListView;
	QStandardItemModel m_model;
};

2、源文件XComboBox.cpp

#include "XComboBox.h"
#include 
#include 
#include 

XComboBox::XComboBox(QWidget *parent)
    : QComboBox(parent)
{
    pLineEdit = new QLineEdit(this);
    pLineEdit->setReadOnly(true);
    this->setLineEdit(pLineEdit);
    this->lineEdit()->disconnect();

    KeyPressEater *keyPressEater = new KeyPressEater(this);
    pListView = new QListView(this);
    pListView->installEventFilter(keyPressEater);
    this->setView(pListView);

    this->setModel(&m_model);

    connect(this, SIGNAL(activated(int)), this, SLOT(sltActivated(int)));
    connect(keyPressEater, SIGNAL(sigActivated(int)), this, SLOT(sltActivated(int)));
}

XComboBox::~XComboBox()
{
}

void XComboBox::AddItem(const QString& str, bool bChecked /*= false*/, QVariant &userData /*= QVariant()*/)
{
    QStandardItem* item = new QStandardItem(str);
    item->setCheckable(true);
    item->setCheckState(bChecked ? Qt::Checked : Qt::Unchecked);
    item->setData(userData, Qt::UserRole + 1);
    m_model.appendRow(item);

    UpdateText();
}

void XComboBox::AddItems(const QList& lstItemInfo)
{
    for (auto a : lstItemInfo)
    {
        AddItem(a.str, a.bChecked, a.userData);
    }
}

void XComboBox::AddItems(const QMap& mapStrChk)
{
    for (auto it = mapStrChk.begin(); it != mapStrChk.end(); ++it)
    {
        AddItem(it.key(), it.value());
    }
}

void XComboBox::AddItems(const QList& lstStr)
{
    for (auto a : lstStr)
    {
        AddItem(a, false);
    }
}

void XComboBox::RemoveItem(int idx)
{
    m_model.removeRow(idx);
    UpdateText();
}

void XComboBox::Clear()
{
    m_model.clear();
    UpdateText();
}

QStringList XComboBox::GetSelItemsText()
{
    QStringList lst;
    QString str = pLineEdit->text();
    if (str.isEmpty())
    {
        return lst;
    }
    else
    {
        return pLineEdit->text().split(",");
    }
}

QList XComboBox::GetSelItemsInfo()
{
    QList lstInfo;
    for (int i = 0; i < m_model.rowCount(); i++)
    {
        QStandardItem* item = m_model.item(i);
        if (item->checkState() == Qt::Unchecked) continue;

        ItemInfo info;
        info.idx = i;
        info.str = item->text();
        info.bChecked = true;
        info.userData = item->data(Qt::UserRole + 1);

        lstInfo << info;
    }

    return lstInfo;
}

QString XComboBox::GetItemText(int idx)
{
    if (idx < 0 || idx >= m_model.rowCount())
    {
        return QString("");
    }

    return m_model.item(idx)->text();
}

ItemInfo XComboBox::GetItemInfo(int idx)
{
    ItemInfo info;
    if (idx < 0 || idx >= m_model.rowCount())
    {
        return info;
    }

    QStandardItem* item = m_model.item(idx);
    info.idx = idx;
    info.str = item->text();
    info.bChecked = (item->checkState() == Qt::Checked);
    info.userData = item->data(Qt::UserRole + 1);

    return info;
}

void XComboBox::showPopup()
{
    emit showingPopup();
    QComboBox::showPopup();
}

void XComboBox::hidePopup()
{
    int width = this->view()->width();
    int height = this->view()->height();
    int x = QCursor::pos().x() - mapToGlobal(geometry().topLeft()).x() + geometry().x();
    int y = QCursor::pos().y() - mapToGlobal(geometry().topLeft()).y() + geometry().y();

    QRect rectView(0, this->height(), width, height);
    if (!rectView.contains(x, y))
    {
        emit hidingPopup();
        QComboBox::hidePopup();
    }
}

void XComboBox::mousePressEvent(QMouseEvent * event)
{
    QComboBox::mousePressEvent(event);
    event->accept();
}

void XComboBox::mouseReleaseEvent(QMouseEvent * event)
{
    QComboBox::mouseReleaseEvent(event);
    event->accept();
}

void XComboBox::mouseMoveEvent(QMouseEvent * event)
{
    QComboBox::mouseMoveEvent(event);
    event->accept();
}

void XComboBox::UpdateText()
{
    QStringList lstTxt;
    for (int i = 0; i < m_model.rowCount(); ++i)
    {
        QStandardItem* item = m_model.item(i);
        if (item->checkState() == Qt::Unchecked) continue;

        lstTxt << item->text();
    }

    pLineEdit->setText(lstTxt.join(","));
    pLineEdit->setToolTip(lstTxt.join("\n"));
}

void XComboBox::sltActivated(int idx)
{
    QStandardItem* item = m_model.item(idx);
    if (nullptr == item) return;

    Qt::CheckState state = (item->checkState() == Qt::Checked) ? Qt::Unchecked : Qt::Checked;
    item->setCheckState(state);

    UpdateText();
}

3、测试代码

QStringList lstStr;
for (int i=0; i<10; ++i)
{
    lstStr << QString("item %1").arg(i);
}

ui.cmb->AddItems(lstStr);

三、重难点

如何避免原生QComboBox内部信号对自定的XComboBox的影响

四、源码下载链接如下

https://download.csdn.net/download/u013001137/11634199

你可能感兴趣的:(C++,Qt)