问题描述


这两天在开发过程中,重写了 QComboBox 这个控件,参考这篇博客 Qt 组合框QComboBox定制颜色选择框 实现的

在这里插入图片描述

但是发现了一个奇怪的问题,就是在加载数据后,初始化是正确的

但是在多次点击下拉按钮后,或者是从某次点击某一 item 开始,下拉菜单的显示便开始错乱,如下图所示

在这里插入图片描述
这个图显示的是末尾几项缺失

再或者如下图

在这里插入图片描述

这个图显示的是开始几项缺失

与这个类似的,自定义多选QComboBox 这篇博客也存在同样的问题

问题分析


导致出现该问题可能的操作:多次点击下拉菜单 或者是 从某一次点击开始后就错乱

但是这两个操作都是最普通不过的操作,所以感觉很诡异

猜测可能是因为 QListWidget 的问题,换成 QListView 好像就没问题了

但是核心问题是什么,尚且不清楚,欢迎读者指出问题所在

临时解决方案


好在网上大神多,我又找了几篇博客,找到了一篇刻印临时解决的方案

具体见博客 教你如何实现带复选框的ComboBox(自定义QComboBox)

或者采用以下简单的方法,在不提升 QComboBox 的情况下直接使用

QPixmap image(30, 30);
image.fill(QColor(255, 0, 0));
combobox->addItem(QIcon(image),
                  QString::fromLocal8Bit("Content"));

代码整理


以下类的使用都相同,都是首先在界面拖拽一个 QComboBox,然后将其提升为以下类中的某一个,然后添加数据进行测试

1. 错误案例:Qt 组合框QComboBox定制颜色选择框

QColorCombobox.h

#pragma once
#include <QLineEdit>
#include <QCombobox>

class QLabel;
class QListWidget;

class QColorWidget : public QLineEdit {
    Q_OBJECT
public:
    QColorWidget(QWidget *parent = Q_NULLPTR);
    ~QColorWidget();

    void updateColor(const QColor &color);

    void mousePressEvent(QMouseEvent *event);

signals:
    void click(const QColor &color);
private:
    QLabel *m_pLabel;
    QLabel *m_pRgbLabel;
    QColor m_color;
};

class QColorCombobox : public QComboBox {
    Q_OBJECT
public:
    QColorCombobox(QWidget *parent = Q_NULLPTR);
    ~QColorCombobox();

    void appendItem(const QColor &color);

private slots:
    void onClickColorWidget(const QColor &color);
private:
    QColorWidget *m_pLineEdit;
    QListWidget *m_pListWidget;
};

QColorCombobox.cpp

#include "QColorCombobox.h"
#include <QLabel>
#include <QHBoxLayout>
#include <QListWidget>

QColorWidget::QColorWidget(QWidget *parent /*= Q_NULLPTR*/)
    : QLineEdit(parent)
    , m_color(255, 0, 0) {
    m_pLabel = new QLabel(this);
    m_pRgbLabel = new QLabel(this);

    m_pLabel->setFixedSize(15, 15);

    QHBoxLayout *layout = new QHBoxLayout();
    layout->addWidget(m_pLabel);
    layout->addWidget(m_pRgbLabel);
    layout->setContentsMargins(5, 0, 0, 2);
    setLayout(layout);
    setReadOnly(true);

    setStyleSheet("QLineEdit{border: none;}QLineEdit:hover{background-color:rgb(0,125,255);}");
}


QColorWidget::~QColorWidget() {

}

void QColorWidget::updateColor(const QColor &color) {
    m_color = color;

    QString strstyle = QString("border:1px solid black;background-color:rgb(%1,%2,%3);").arg(QString::number(color.red()), QString::number(color.green()), QString::number(color.blue()));
    QString strText = QString("%1,%2,%3").arg(QString::number(color.red()), QString::number(color.green()), QString::number(color.blue()));
    m_pLabel->setStyleSheet(strstyle);
    m_pRgbLabel->setText(strText);
}

void QColorWidget::mousePressEvent(QMouseEvent *event) {
    emit click(m_color);
}




QColorCombobox::QColorCombobox(QWidget *parent /*= Q_NULLPTR*/)
    : QComboBox(parent) {
    m_pLineEdit = new QColorWidget(this);
    m_pListWidget = new QListWidget(this);    m_pLineEdit->setStyleSheet("");
    setContextMenuPolicy(Qt::NoContextMenu);//禁用菜单
    m_pListWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);//禁用垂直滚动条
    m_pListWidget->setStyleSheet("QListWidget::Item:hover{background-color:rgb(0,125,255);}");
    setLineEdit(m_pLineEdit);
    setModel(m_pListWidget->model());
    setView(m_pListWidget);

}

QColorCombobox::~QColorCombobox() {
}

void QColorCombobox::appendItem(const QColor &color) {
    QColorWidget *pWid = new QColorWidget(this);
    pWid->updateColor(color);
    connect(pWid, SIGNAL(click(const QColor &)), this, SLOT(onClickColorWidget(const QColor &)));
    QListWidgetItem *pItem = new QListWidgetItem(m_pListWidget);

    m_pListWidget->addItem(pItem);
    m_pListWidget->setItemWidget(pItem, pWid);

}

void QColorCombobox::onClickColorWidget(const QColor &color) {
    m_pLineEdit->updateColor(color);
    hidePopup();
}

MainWindow.cpp

ui.comboBox->appendItem(QColor(255, 0, 0));
ui.comboBox->appendItem(QColor(255, 255, 0));
ui.comboBox->appendItem(QColor(255, 0, 255));
ui.comboBox->appendItem(QColor(0, 255, 255));
ui.comboBox->appendItem(QColor(0, 255, 0));

2. 错误案例:自定义多选QComboBox

MutiComboBox.h

#ifndef MUTICOMBOBOX_H
#define MUTICOMBOBOX_H
 
#include <QWidget>
#include <QListWidget>
#include <QLineEdit>
#include <QComboBox>
 
 
class  MutiComboBox : public QComboBox
{
	Q_OBJECT
 
public:
	MutiComboBox(QWidget *parent = 0);
	MutiComboBox(const QMap<int,QString> &mapData,QWidget *parent = 0);
	~MutiComboBox();
 
public:
	void setModelData(const QMap<int,QString> &mapData);
	void setCheckedItems(const QString &data);
	QString getSelectedItemDatas();
 
 
private slots:
	void stateChanged(int state);
	void textChanged(const QString &text);
 
private:
	void clear();
 
private:
	QListWidget *pListWidget;
	QLineEdit *pLineEdit;
	QString strSelectedText;
	bool bSelected;
};
 
#endif // MUTICOMBOBOX_H

MutiComboBox.cpp

#include "muticombobox.h"
 
#include <QCheckBox>
#include <QDebug>
 
 
MutiComboBox::MutiComboBox(QWidget *parent)
	: QComboBox(parent)
{
	pListWidget = new QListWidget(this);
	pListWidget->setObjectName("MutiComboBoxListWidget");
	pLineEdit = new QLineEdit(this);
	//this->setModel(pListWidget->model());
	//this->setView(pListWidget);
	this->setLineEdit(pLineEdit);
	pLineEdit->setReadOnly(true);
	connect(pLineEdit,SIGNAL(textChanged(const QString &)),this,SLOT(textChanged(const QString &)));
}
 
MutiComboBox::MutiComboBox(const QMap<int,QString> &mapData,QWidget *parent)
:QComboBox(parent)
{
	pListWidget = new QListWidget(this);
	pLineEdit = new QLineEdit(this);
	QMapIterator<int,QString> it(mapData);
	while(it.hasNext()){
		it.next();
		QListWidgetItem *pItem = new QListWidgetItem(pListWidget);
		pListWidget->addItem(pItem);
		pItem->setData(Qt::UserRole,it.key());
		QCheckBox *pCheckBox = new QCheckBox(this);
		pCheckBox->setText(it.value());
		pListWidget->addItem(pItem);
		pListWidget->setItemWidget(pItem,pCheckBox);
		connect(pCheckBox,SIGNAL(stateChanged(int)),this,SLOT(stateChanged(int)));
	}
 
	this->setModel(pListWidget->model());
	this->setView(pListWidget);
	this->setLineEdit(pLineEdit);
	pLineEdit->setReadOnly(true);
	connect(pLineEdit,SIGNAL(textChanged(const QString &)),this,SLOT(textChanged(const QString &)));
}
 
MutiComboBox::~MutiComboBox()
{
	delete pListWidget;
	delete pLineEdit;
}
void MutiComboBox::setModelData(const QMap<int,QString> &mapData)
{
	int count = pListWidget->count();
	for (int row = 0; row < count;++row)
	{
		QListWidgetItem *pItem = pListWidget->takeItem(0);
		delete pItem;
	}
	
	QMapIterator<int,QString> it(mapData);
	while(it.hasNext()){
		it.next();
		QListWidgetItem *pItem = new QListWidgetItem(pListWidget);
		pListWidget->addItem(pItem);
		pItem->setData(Qt::UserRole,it.key());
		QCheckBox *pCheckBox = new QCheckBox(this);
		pCheckBox->setText(it.value());
		pListWidget->addItem(pItem);
		pListWidget->setItemWidget(pItem,pCheckBox);
		connect(pCheckBox,SIGNAL(stateChanged(int)),this,SLOT(stateChanged(int)));
	}
	this->setModel(pListWidget->model());
	this->setView(pListWidget);
}
 
void MutiComboBox::setCheckedItems(const QString &data)
{
	clear();
 
	if(data.isEmpty())
		return;
 
	int count = pListWidget->count();
	QStringList list(data.split(";"));
	QStringListIterator it(list);
	while(it.hasNext())
	{
		QString da = it.next();
		for(int i=0;i<count;++i)
		{
			QListWidgetItem *pItem = pListWidget->item(i);
			if(pItem->data(Qt::UserRole).toString() == da)
			{
				QWidget *pWidget = pListWidget->itemWidget(pItem);
				QCheckBox *pCheckBox = (QCheckBox*) pWidget;
				pCheckBox->setChecked(true);
				break;
			}
		}
	}
}
QString MutiComboBox::getSelectedItemDatas()
{
	int nCount = pListWidget->count();
	QString strSeltectedData("");
	for(int i=0; i < nCount; ++i)
	{
		QListWidgetItem *pItem = pListWidget->item(i);
		QWidget *pWidget = pListWidget->itemWidget(pItem);
		QCheckBox *pCheckBox = (QCheckBox*) pWidget;
		if(pCheckBox->isChecked()){
			QString strText = pItem->data(Qt::UserRole).toString().trimmed();
			strSeltectedData.append(strText).append(";");
		}
	}
 
	if(strSeltectedData.endsWith(";"))
	{
		strSeltectedData.remove(strSeltectedData.count()-1,1);
	}
	return strSeltectedData;
}
 
 
void MutiComboBox::stateChanged(int state)
{
	bSelected = true;
	QString strSelectedData("");
	strSelectedText.clear();
	QObject *object= QObject::sender();
	QCheckBox *pSenderCheckBox = static_cast<QCheckBox*>(object);
	int nCount = pListWidget->count();
	for(int i=0; i < nCount; ++i)
	{
		QListWidgetItem *pItem = pListWidget->item(i);
		QWidget *pWidget = pListWidget->itemWidget(pItem);
		QCheckBox *pCheckBox = (QCheckBox*) pWidget;
		if(pCheckBox->isChecked()){
			QString strText = pCheckBox->text();
			strSelectedData.append(strText).append(";");
		}
	}
 
	if(strSelectedData.endsWith(";"))
	{
		strSelectedData.remove(strSelectedData.count()-1,1);
	}
 
	if(!strSelectedData.isEmpty())
	{
		strSelectedText = strSelectedData;
		pLineEdit->setText(strSelectedData);
		pLineEdit->setToolTip(strSelectedData);
	}else{
		pLineEdit->clear();
	}
	bSelected = false;
}
 
void MutiComboBox::textChanged(const QString &text)
{
	if(!bSelected)
	{
		pLineEdit->setText(strSelectedText);
	}
}
 
void MutiComboBox::clear(){
	int nCount = pListWidget->count();
	for(int i=0; i < nCount; ++i)
	{
		QListWidgetItem *pItem = pListWidget->item(i);
		QWidget *pWidget = pListWidget->itemWidget(pItem);
		QCheckBox *pCheckBox = (QCheckBox*) pWidget;
		if(pCheckBox->isChecked()){
			pCheckBox->setChecked(false);
		}
	}
	strSelectedText = "";
	pLineEdit->setText("");
}

MainWindow.cpp

QMap<int, QString> qmap = {
    {1, "111"},
    {2, "222"},
    {3, "333"},
    {4, "444"},
    {5, "555"},
    {11, "111"},
    {12, "222"},
    {13, "333"},
    {14, "444"},
    {15, "555"},
    {21, "111"},
    {22, "222"},
    {23, "333"},
    {24, "444"},
    {25, "555"},
};

ui->combobox->setModelData(qmap);

3. 正确案例:教你如何实现带复选框的ComboBox(自定义QComboBox)

XComboBox.h

#pragma once
 
#include <QComboBox>
#include <QStandardItemModel>
#include <QListView>
#include <QKeyEvent>
 
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<QKeyEvent *>(event);
            if (keyEvent->key() == Qt::Key_Space)
            {
                QListView* lstV = qobject_cast<QListView*>(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<ItemInfo>& lstItemInfo);
	void AddItems(const QMap<QString, bool>& mapStrChk);
	void AddItems(const QList<QString>& lstStr);
	// 删除item
	void RemoveItem(int idx);
	// 清空item
	void Clear();
	// 获取选中的数据字符串列表
	QStringList GetSelItemsText();
	// 获取选中item的信息
	QList<ItemInfo> 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;
};

XComboBox.cpp

#include "XComboBox.h"
#include <QLineEdit>
#include <QMouseEvent>
#include <QDebug>
 
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<ItemInfo>& lstItemInfo)
{
    for (auto a : lstItemInfo)
    {
        AddItem(a.str, a.bChecked, a.userData);
    }
}
 
void XComboBox::AddItems(const QMap<QString, bool>& mapStrChk)
{
    for (auto it = mapStrChk.begin(); it != mapStrChk.end(); ++it)
    {
        AddItem(it.key(), it.value());
    }
}
 
void XComboBox::AddItems(const QList<QString>& 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<ItemInfo> XComboBox::GetSelItemsInfo()
{
    QList<ItemInfo> 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();
}

MainWindow.cpp

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

ui->combobox->AddItems(lstStr);
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐