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