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