示例代码
上述代码是本文的主要参考代码,但是它会产生如下问题:
当下拉框条目较多,出现右侧滑动条后,将滑动条拉到最后,收起下拉框,再点开下拉框,上方的所有控件都会丢失,下方出现对应个数的空白区域。反复这样操作:每次将滑动条拉到最底下收起再弹出,可以发现最终所有条目都会消失,只留下一堆空白

由于采用了自定义的视图,即用QListWidget替换了QComboBox的默认视图,导致内部关联缺失。
可以发现,在每次弹出下拉框时,右侧的滑动条都会默认置位最顶端,而左侧视图部分依旧是上次收起下拉框时所显示的视图条目,因此视图范围和滑动条直接的关联断开了,故出现该问题。
上问所提博客的代码中的条目采用QCheckBox,但是由于可能会存在的一些问题:
所以推荐继承QCheckBox进行稍微修改以克服,相关代码在最后的附录。
代码中鼠标释放事件必须屏蔽,至于另外两个为什么要屏蔽,当然你也可以不屏蔽,跑跑看,满足效果就行。

下面的解决方法都是基于采用自定义MyCheckBox才成立的,如果依旧使用系统的QCheckBox,则行为不可预测
最完美的解决方法是将这种关联正确连接,但是折腾很久目前未能实现
重写hidePopup函数,在其收起的前一刻强制将滑动条复位:
void MCC::hidePopup(){
......
this->list_widget_->verticalScrollBar()->setSliderPosition(0);
QComboBox::hidePopup();
}
这样就可以让视图位置和滑动条位置一致,该方法的缺陷就是每次弹出下拉框,都会显示第一行内容,无法记录上一刻收起的位置
重写hidePopup函数,在其收起的前一刻记录滑动条位置,再重写showPopup,在其弹出后重置滑动条位置
void MCC::hidePopup(){
......
this->scroll_value = this->list_widget_->verticalScrollBar()->value();
QComboBox::hidePopup();
}
void MCC::showPopup(){
QComboBox::showPopup();
this->list_widget_->verticalScrollBar()->setSliderPosition(this->scroll_value);
}
该方法看起来美好,但是实际使用下会出现一个问题:如果仅仅是滑动,确实能够保留上次收起的位置,但是如果点击任意一个条目后,下次弹出又是从头开始(其实重置位置是有效的,但是重置后的150ms后,下拉框的视图居然诡异的重置了!),和方法3.2效果一样了,这个问题我用了一天采用各种事件、信号过滤器解决,差点走火入魔,但是转念一想,用户选择条目后往往要从头开始,很可能要用到搜索框,所以这很明显不是BUG而是特性(如果实在要保持一致性,可以在 )showPopup最后加个定时器延迟200毫秒再次执行一次滑动条置位

#ifndef MYCHECKBOX_H
#define MYCHECKBOX_H
#include
class MyCheckBox : public QCheckBox {
Q_OBJECT
public:
explicit MyCheckBox(QWidget *parent = 0);
protected:
void mousePressEvent(QMouseEvent *e);
void mouseReleaseEvent(QMouseEvent *e);
void mouseMoveEvent(QMouseEvent *);
signals:
public slots:
private:
QObject* ptr;
};
#endif // MYCHECKBOX_H
#include "mycheckbox.h"
#include "mcc.h"
#include
#include
#include
#include
#define cout qDebug() << "[" << __FILE__ <<":" << __LINE__ << "]"
MyCheckBox::MyCheckBox(QWidget *parent) : QCheckBox(parent) {
ptr = parent;
}
void MyCheckBox::mousePressEvent(QMouseEvent *e){
// QCheckBox::mousePressEvent(e);
Q_UNUSED(e);
}
void MyCheckBox::mouseReleaseEvent(QMouseEvent *e){
// QCheckBox::mouseReleaseEvent(e);
Q_UNUSED(e);
this->setChecked(!this->isChecked());
// 其他需要处理的函数
}
void MyCheckBox::mouseMoveEvent(QMouseEvent *e){
// QCheckBox::mouseMoveEvent(e);
Q_UNUSED(e);
}
#ifndef MCC_H
#define MCC_H
#include
#include
#include
#include
class MCC : public QComboBox {
Q_OBJECT
public:
MCC(QWidget *parent = Q_NULLPTR);
~MCC();
//隐藏下拉框
virtual void hidePopup();
virtual void showPopup();
//添加一条选项
void addItem(const QString& _text, const QVariant& _variant = QVariant());
//添加多条选项
void addItems(const QStringList& _text_list);
//返回当前选中选项
QStringList currentText();
//返回当前选项条数
int count()const;
//设置搜索框默认文字
void SetSearchBarPlaceHolderText(const QString _text);
//设置文本框默认文字
void SetPlaceHolderText(const QString& _text);
//下拉框状态恢复默认
void ResetSelection();
//清空所有内容
void clear();
//文本框内容清空
void TextClear();
//设置选中文本--单
void setCurrentText(const QString& _text);
//设置选中文本--多
void setCurrentText(const QStringList& _text_list);
//设置搜索框是否禁用
void SetSearchBarHidden(bool _flag);
protected:
//事件过滤器
virtual bool eventFilter(QObject *watched,QEvent *event);
//滚轮事件
virtual void wheelEvent(QWheelEvent *event);
//按键事件
virtual void keyPressEvent(QKeyEvent *event);
private slots:
//槽函数:文本框文本变化
void stateChange(int _row);
//槽函数:搜索框文本变化
void onSearch(const QString& _text);
//槽函数:点击下拉框选项
void itemClicked(int _index);
signals:
//信号:发送当前选中选项
void selectionChange(const QString _data);
private:
//下拉框
QListWidget* list_widget_;
//文本框,搜索框
QLineEdit* line_edit_, *search_bar_;
//搜索框显示标志
bool hidden_flag_;
//下拉框显示标志
bool show_flag_;
int scroll_value = 0;
};
#endif // MCC_H
#include "mcc.h"
#include "mycheckbox.h"
#include
MCC::MCC(QWidget *parent)
: QComboBox(parent)
, hidden_flag_(true)
, show_flag_(false) {
list_widget_ = new QListWidget();
line_edit_ = new QLineEdit();
search_bar_ = new QLineEdit();
/*设置搜索框*/
QListWidgetItem* currentItem = new QListWidgetItem(list_widget_);
//设置搜索框提示信息
search_bar_->setPlaceholderText("Search.........");
//显示清除按钮
search_bar_->setClearButtonEnabled(true);
list_widget_->addItem(currentItem);
list_widget_->setItemWidget(currentItem, search_bar_);
/*设置文本框*/
//设为只读,因为该输入框只用来显示选中的选项,称为文本框更合适些
line_edit_->setReadOnly(true);
//把当前对象安装(或注册)为事件过滤器,当前也称为过滤器对象。事件过滤器通常在构造函数中进行注册。
line_edit_->installEventFilter(this);
//设置禁用样式,因为不受样式表控制,临时这样解决
line_edit_->setStyleSheet("QLineEdit:disabled{background:rgb(233,233,233);}");
this->setModel(list_widget_->model());
this->setView(list_widget_);
this->setLineEdit(line_edit_);
connect(search_bar_, SIGNAL(textChanged(const QString&)), this, SLOT(onSearch(const QString&)));
connect(this, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &MCC::itemClicked);
}
MCC::~MCC() {}
void MCC::hidePopup(){
QAbstractItemView* ptv = this->view();
QPoint cursorPoint = ptv->mapFromGlobal(QCursor::pos());
QRect rec = ptv->rect();
if (rec.contains(cursorPoint)){}
else {
this->scroll_value = this->list_widget_->verticalScrollBar()->value();
QComboBox::hidePopup();
}
}
void MCC::showPopup() {
QComboBox::showPopup();
this->list_widget_->verticalScrollBar()->setSliderPosition(this->scroll_value);
}
void MCC::addItem(const QString& _text, const QVariant& _variant /*= QVariant()*/) {
Q_UNUSED(_variant);
QListWidgetItem* item = new QListWidgetItem(list_widget_);
MyCheckBox* checkbox = new MyCheckBox(this);
checkbox->setStyleSheet("QCheckBox::hover {background: gray;} QCheckBox {padding:0px 0px 0px 4px;}");
checkbox->setText(_text);
list_widget_->addItem(item);
list_widget_->setItemWidget(item, checkbox);
connect(checkbox, &MyCheckBox::stateChanged, this, &MCC::stateChange);
}
void MCC::addItems(const QStringList& _text_list) {
for (const auto& text_one : _text_list) {
addItem(text_one);
}
}
QStringList MCC::currentText() {
QStringList text_list;
if (!line_edit_->text().isEmpty()) {
//以;为分隔符分割字符串
text_list = line_edit_->text().split(':');
}
return text_list;
}
int MCC::count() const {
int count = list_widget_->count() - 1;
if (count < 0) {
count = 0;
}
return count;
}
void MCC::SetSearchBarPlaceHolderText(const QString _text) {
search_bar_->setPlaceholderText(_text);
}
void MCC::SetPlaceHolderText(const QString& _text) {
line_edit_->setPlaceholderText(_text);
}
void MCC::ResetSelection() {
int count = list_widget_->count();
for (int i = 1; i < count; i++) {
//获取对应位置的QWidget对象
QWidget *widget = list_widget_->itemWidget(list_widget_->item(i));
//将QWidget对象转换成对应的类型
MyCheckBox *check_box = static_cast<MyCheckBox*>(widget);
check_box->setChecked(false);
}
}
void MCC::clear() {
line_edit_->clear();
list_widget_->clear();
QListWidgetItem* currentItem = new QListWidgetItem(list_widget_);
search_bar_->setPlaceholderText("Search.........");
search_bar_->setClearButtonEnabled(true);
list_widget_->addItem(currentItem);
list_widget_->setItemWidget(currentItem, search_bar_);
SetSearchBarHidden(hidden_flag_);
connect(search_bar_, SIGNAL(textChanged(const QString&)), this, SLOT(onSearch(const QString&)));
}
void MCC::TextClear() {
line_edit_->clear();
ResetSelection();
}
void MCC::setCurrentText(const QString& _text) {
int count = list_widget_->count();
for (int i = 1; i < count; i++) {
//获取对应位置的QWidget对象
QWidget *widget = list_widget_->itemWidget(list_widget_->item(i));
//将QWidget对象转换成对应的类型
MyCheckBox *check_box = static_cast<MyCheckBox*>(widget);
if (_text.compare(check_box->text()))
check_box->setChecked(true);
}
}
void MCC::setCurrentText(const QStringList& _text_list) {
int count = list_widget_->count();
for (int i = 1; i < count; i++) {
//获取对应位置的QWidget对象
QWidget *widget = list_widget_->itemWidget(list_widget_->item(i));
//将QWidget对象转换成对应的类型
MyCheckBox *check_box = static_cast<MyCheckBox*>(widget);
if (_text_list.contains(check_box->text()))
check_box->setChecked(true);
}
}
void MCC::SetSearchBarHidden(bool _flag) {
hidden_flag_ = _flag;
list_widget_->item(0)->setHidden(hidden_flag_);
}
bool MCC::eventFilter(QObject *watched, QEvent *event) {
//设置点击输入框也可以弹出下拉框
if (watched == line_edit_ && event->type() == QEvent::MouseButtonRelease && this->isEnabled()) {
showPopup();
return true;
}
return false;
}
void MCC::wheelEvent(QWheelEvent *event) {
//禁用QComboBox默认的滚轮事件
Q_UNUSED(event);
}
void MCC::keyPressEvent(QKeyEvent *event) {
QComboBox::keyPressEvent(event);
}
void MCC::stateChange(int _row) {
Q_UNUSED(_row);
QString selected_data("");
int count = list_widget_->count();
for (int i = 1; i < count; i++) {
QWidget *widget = list_widget_->itemWidget(list_widget_->item(i));
MyCheckBox *check_box = static_cast<MyCheckBox*>(widget);
if (check_box->isChecked()) {
selected_data.append(check_box->text()).append(";");
}
}
selected_data.chop(1);
if (!selected_data.isEmpty()) {
line_edit_->setText(selected_data);
}
else{
line_edit_->clear();
}
line_edit_->setToolTip(selected_data);
emit selectionChange(selected_data);
}
void MCC::onSearch(const QString& _text){
for (int i = 1; i < list_widget_->count(); i++){
MyCheckBox *check_box = static_cast<MyCheckBox*>(list_widget_->itemWidget(list_widget_->item(i)));
//文本匹配则显示,反之隐藏
//Qt::CaseInsensitive模糊查询
if (check_box->text().contains(_text, Qt::CaseInsensitive))
list_widget_->item(i)->setHidden(false);
else
list_widget_->item(i)->setHidden(true);
}
}
void MCC::itemClicked(int _index) {
if (_index != 0) {
MyCheckBox *check_box = static_cast<MyCheckBox*>(list_widget_->itemWidget(list_widget_->item(_index)));
check_box->setChecked(!check_box->isChecked());
}
}