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());
        }
    }
}

 

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