• QComboBox多选框的实现(源码):采用QListWidget实现多选后控件丢失的问题(探讨)


    1. 多选下拉框(带搜索功能)的问题

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

    2. 问题分析

    由于采用了自定义的视图,即用QListWidget替换了QComboBox的默认视图,导致内部关联缺失。
    可以发现,在每次弹出下拉框时,右侧的滑动条都会默认置位最顶端,而左侧视图部分依旧是上次收起下拉框时所显示的视图条目,因此视图范围和滑动条直接的关联断开了,故出现该问题。

    3. 解决方法

    上问所提博客的代码中的条目采用QCheckBox,但是由于可能会存在的一些问题:

    • QCheckBox样式中hover背景颜色只有鼠标在最左侧的“框+文本”才显示,右侧空白无法显示,而显示的是QComboBox默认条目的hover背景颜色
    • 鼠标点击QCheckBox左侧“框+文本”与右侧空白所触发的事件很可能是不同的(行为不同)

    所以推荐继承QCheckBox进行稍微修改以克服,相关代码在最后的附录。
    代码中鼠标释放事件必须屏蔽,至于另外两个为什么要屏蔽,当然你也可以不屏蔽,跑跑看,满足效果就行。
    在这里插入图片描述
    下面的解决方法都是基于采用自定义MyCheckBox才成立的,如果依旧使用系统的QCheckBox,则行为不可预测

    3.1 完美方案

    最完美的解决方法是将这种关联正确连接,但是折腾很久目前未能实现

    3.2 滑动条位置复位

    重写hidePopup函数,在其收起的前一刻强制将滑动条复位:

    void MCC::hidePopup(){
    	......
        this->list_widget_->verticalScrollBar()->setSliderPosition(0);
        QComboBox::hidePopup();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这样就可以让视图位置和滑动条位置一致,该方法的缺陷就是每次弹出下拉框,都会显示第一行内容,无法记录上一刻收起的位置

    3.3 重置滑动条位置

    重写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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    该方法看起来美好,但是实际使用下会出现一个问题:如果仅仅是滑动,确实能够保留上次收起的位置,但是如果点击任意一个条目后,下次弹出又是从头开始(其实重置位置是有效的,但是重置后的150ms后,下拉框的视图居然诡异的重置了!),和方法3.2效果一样了,这个问题我用了一天采用各种事件、信号过滤器解决,差点走火入魔,但是转念一想,用户选择条目后往往要从头开始,很可能要用到搜索框,所以这很明显不是BUG而是特性(如果实在要保持一致性,可以在showPopup最后加个定时器延迟200毫秒再次执行一次滑动条置位
    无题

    4. 附录(完整代码)

    4.1 mycheckbox.h

    #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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    4.2 mycheckbox.cpp

    #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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    4.3 mcc.h

    #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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    4.4 mcc.cpp

    #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());
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
  • 相关阅读:
    Python中 whl包、tar.gz包的区别
    产品设计的起点:从企业的角度寻找切入点
    Deadlock found when trying to get lock; try restarting transaction主要要是死锁问题呢怎么解决
    非零基础自学Java (老师:韩顺平) 第6章 数组、排序和查找 6.14 二维数组的应用案例 && 6.15 二维数组使用细节和注意事项
    基于spring的在线家教管理系统
    技术分享 | 如何确保API 的稳定性与正确性?你只需要这一招
    ftl简单demo
    Linux bzip2命令教程:文件压缩与解压缩实战(附案例详解和注意事项)
    交易凭什么取胜?
    产品经理学习路线
  • 原文地址:https://blog.csdn.net/qq_41997274/article/details/126213464