Qt自定义一个下拉框(使用基础组件组合)

0.前言

Qt提供了一个QComboBox下拉框组件,但是对于一些自定义样式的需求实现起来并不方便,很多东西还得去倒腾源码,还不如直接用基础的组件自己来实现一个下拉框。不过,自己组合的组件对样式表的支持不是很好,而且水平不够通用性差,所以我只在一些定制化程度高的需求才使用这种方式。

1.实现思路与问题

首先是下拉框的文本框和按钮,我使用QLineEdit+QPushButton;然后弹出框我分为两部分,一是弹出框Widget容器,二是弹出框中的内容为了后续的自定义抽象了一个简单的基类,使用的时候继承基类实现自己的弹出框样式即可。剩下的就是模拟一些QComboBox的接口和效果了。

遇到的问题一,弹出框QWidget子类设置为Popup后,没法设置背景半透明。后来发现可以给要展示的Widget的parent或者更上层的parent设置透明属性,这样展示的Widget就可以半透明了。

//背景透明FramelessWindowHint+WA_TranslucentBackground
//这样才能给上面的组件设置透明色
setWindowFlags(Qt::Popup|Qt::FramelessWindowHint);
setAttribute(Qt::WA_TranslucentBackground);

遇到的问题二, 弹出后点击标题栏拖动弹框不会消失。开始我以为是焦点的问题,但是设置后也没效果。后来发现因为我弹出动画高度是从0开始,设置成从1开始就没问题了。

resize(width,1);
animation->setStartValue(QSize(width,1));

待实现的需求一:样式表的设置如何反应到布局中,增加自定义属性在QSS种设置值么。

待实现的需求二:弹出框的方向目前直接往下的,应该判断下位置来决定向上还是向下。

2.实现代码

完整代码链接(RBasicComboBox类):https://github.com/gongjianbo/RectangleComponent

部分实现(因为有好几个组成部分,所以只贴了主体类):

#ifndef RBASICCOMBOBOX_H
#define RBASICCOMBOBOX_H

#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QHBoxLayout>
#include <QTimer>

#include "RBasicComboPopup.h"

/**
 * @brief 使用QLineEdit+QPushButton+弹出框组合的下拉框
 * @author 龚建波
 * @date 2020-7-6
 * @history
 * [2020-7-7]
 * 重构弹出框部分,增加可扩展性
 * 基础组件:Box+Popup,继承Container实现接口后设置给Popup
 */
class RBasicComboBox : public QWidget
{
    Q_OBJECT
public:
    explicit RBasicComboBox(QWidget *parent = nullptr);
    ~RBasicComboBox();

    //当前行
    int getCurrentIndex() const;
    void setCurrentIndex(int index);
    //当前文本
    QString getCurrentText() const;
    void setCurrentText(const QString text);
    //数据项
    QList<QString> getItems() const;
    void setItems(const QList<QString> &items);
    //弹出框
    RBasicComboPopup *getPopup() const;

protected:
    //过滤组件事件
    bool eventFilter(QObject *watched, QEvent *event) override;

private:
    //初始化组件设置
    void initComponent();
    //popup-container
    void initContainer();
    //默认样式设置
    void initStyleSheet();
    //编辑后查询对应行并设置model
    void checkTextRow();

signals:
    void currentIndexChanged(int index);
    void currentTextChanged(const QString text);

private:
    //可以使用前置声明,然后把new放到初始化列表去
    //文本框
    QLineEdit *boxEdit=new QLineEdit(this);
    //按钮
    QPushButton *boxButton=new QPushButton(this);
    //布局
    QHBoxLayout *boxLayout=new QHBoxLayout(this);
    //弹框
    RBasicComboPopup *boxPop=new RBasicComboPopup(this);
    //定时器
    QTimer *editTimer=new QTimer(this);
};

#endif // RBASICCOMBOBOX_H
#include "RBasicComboBox.h"

#include <QDesktopWidget>
#include <QKeyEvent>
#include <QFocusEvent>
#include <QMouseEvent>

#include <QDebug>

RBasicComboBox::RBasicComboBox(QWidget *parent)
    : QWidget(parent)
{
    setAttribute(Qt::WA_StyledBackground); //支持样式表
    setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Fixed);

    initComponent();
    initStyleSheet();
}

RBasicComboBox::~RBasicComboBox()
{
    boxPop->hidePopup();
}

int RBasicComboBox::getCurrentIndex() const
{
    if(boxPop->getContainer()){
        return boxPop->getContainer()->getCurrentIndex();
    }
    return -1;
}

void RBasicComboBox::setCurrentIndex(int index)
{
    if(boxPop->getContainer()){
        boxPop->getContainer()->setCurrentIndex(index);
    }
}

QString RBasicComboBox::getCurrentText() const
{
    return boxEdit->text();
}

void RBasicComboBox::setCurrentText(const QString text)
{
    editTimer->stop();
    boxEdit->setText(text);
}

QList<QString> RBasicComboBox::getItems() const
{
    if(boxPop->getContainer()){
        return boxPop->getContainer()->getItems();
    }
    return QList<QString>();
}

void RBasicComboBox::setItems(const QList<QString> &items)
{
    if(boxPop->getContainer()){
        boxPop->getContainer()->setItems(items);
    }
}

RBasicComboPopup *RBasicComboBox::getPopup() const
{
    return boxPop;
}

bool RBasicComboBox::eventFilter(QObject *watched, QEvent *event)
{
    if(watched==boxEdit){
        //过滤编辑框事件
        switch (event->type()) {
        case QEvent::KeyRelease:
        {
            if(boxPop->getContainer()){
                //这里只考虑了edit可编辑的情况
                QKeyEvent *key_event=static_cast<QKeyEvent*>(event);
                if(key_event->key()==Qt::Key_Up){
                    setCurrentText(boxPop->getContainer()->getPrevText());
                }else if(key_event->key()==Qt::Key_Down){
                    setCurrentText(boxPop->getContainer()->getNextText());
                }
            }
        }
            break;
        case QEvent::FocusAboutToChange:
        case QEvent::FocusIn:
        {
            //获得焦点时全选
            QFocusEvent *focus_event=static_cast<QFocusEvent*>(event);
            if(focus_event->gotFocus()){
                QTimer::singleShot(20,boxEdit,&QLineEdit::selectAll);
            }
        }
            break;
        default:
            break;
        }
    }else if(watched==boxButton){
        //过频按钮事件
    }else if(watched==boxPop){
        //过滤弹框事件
        if(event->type()==QEvent::Show){
            boxButton->setChecked(true);
        }else if(event->type()==QEvent::Hide){
            boxButton->setChecked(false);
        }
    }
    return false;
}

void RBasicComboBox::initComponent()
{
    //按钮设置
    boxButton->setObjectName("button");
    boxButton->setFixedWidth(20);
    boxButton->setCheckable(true); //用选中来表示弹框弹出
    boxButton->setFocusPolicy(Qt::NoFocus);
    boxButton->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
    boxButton->installEventFilter(this);

    //编辑框设置
    boxEdit->setObjectName("edit");
    boxEdit->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
    boxEdit->installEventFilter(this);
    //index和text两个属性可以看成独立的
    connect(boxEdit,&QLineEdit::textChanged,this,&RBasicComboBox::currentTextChanged);
    //编辑时,延迟一会儿进行查询-currentindex
    connect(boxEdit,&QLineEdit::textEdited,[this]{
        editTimer->start(300);
    });
    //编辑结束,进行查询-currentindex
    connect(boxEdit,&QLineEdit::editingFinished,this,&RBasicComboBox::checkTextRow);
    editTimer->setSingleShot(true);
    connect(editTimer,&QTimer::timeout,this,&RBasicComboBox::checkTextRow);

    //弹框容器设置
    boxPop->attachTarget(this);
    boxPop->installEventFilter(this);
    initContainer();
    connect(boxPop,&RBasicComboPopup::containerChanged,this,&RBasicComboBox::initContainer);

    //布局
    boxLayout->setMargin(1);
    boxLayout->setSpacing(1);
    boxLayout->addWidget(boxEdit);
    boxLayout->addWidget(boxButton);
    //点击按钮,弹出
    //这里有个问题,已经弹出时,点击别的地方会隐藏,
    //然后我事件过滤又把按钮设置成了非选中,所以点击按钮只会触发弹出
    connect(boxButton,&QPushButton::toggled,[this](bool checked){
        if(checked){
            boxPop->showPopup();
        }else{
            boxPop->hidePopup();
        }
    });
}

void RBasicComboBox::initContainer()
{
    if(!boxPop->getContainer())
        return;

    connect(boxPop->getContainer(),&RBasicComboContainer::currentIndexChanged,this,&RBasicComboBox::currentIndexChanged);
    connect(boxPop->getContainer(),&RBasicComboContainer::updateData,[this]{
        setCurrentText(boxPop->getContainer()->getCurrentText());
    });
}

void RBasicComboBox::initStyleSheet()
{
    setStyleSheet(R"(
                  RBasicComboBox{
                  color:black;
                  background: rgb(160,160,160);
                  }
                  RBasicComboBox:disable{
                  color:rgb(160,160,160);
                  background: rgb(160,160,160);
                  }
                  RBasicComboBox:hover{
                  background: rgb(0,120,215);
                  }
                  RBasicComboBox #edit{
                  border:0;
                  margin:0;
                  padding:0px 3px;
                  color:black;
                  background:white;
                  }
                  RBasicComboBox #button{
                  border:5px solid white;
                  padding:0;
                  margin:0;
                  min-width:12px;
                  max-width:12px;
                  background-color: rgb(200,200,200);
                  }
                  RBasicComboBox #button:hover,
                  RBasicComboBox #button:checked{
                  background-color: rgb(180,215,243);
                  }
                  RBasicComboContainer{
                  background: transparent;
                  border:1px solid rgb(0,120,215);
                  }
                  RBasicComboView{
                  background:white;
                  border:0;
                  font:13px "宋体";
                  }
                  RBasicComboView::item{
                  height:20px;
                  color:black;
                  background:white;
                  }
                  RBasicComboView::item:selected,
                  RBasicComboView::item:hover{
                  color:white;
                  background:rgb(0,120,215);
                  }
                  )");
}

void RBasicComboBox::checkTextRow()
{
    //如果model中有匹配的文本,就修改view的currentIndex
    if(boxPop->getContainer()){
        if(boxPop->getContainer()->checkTextRow(boxEdit->text())>=0){
            setCurrentText(boxPop->getContainer()->getCurrentText());
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章