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

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

2.实现代码

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

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

#pragma once
#include <QWidget>
#include "CuteComponentExport.h"
#include "CuteBasicComboContainer.h"
#include "CuteBasicComboModel.h"
#include "CuteBasicComboPopup.h"
#include "CuteBasicComboView.h"
class QLineEdit;
class QPushButton;
class QHBoxLayout;
class QTimer;

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

    // 边框 size,自定义属性方便 qss 设置
    int getBorderWidth() const;
    void setBorderWidth(int px);

    // 弹框可见
    bool getPopupVisible() const;
    void setPopupVisible(bool visible);

    // 当前行
    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);
    // 弹出框
    CuteBasicComboPopup *getPopup() const;

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

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

signals:
    void borderWidthChanged(int px);
    void popupVisibleChanged(bool visible);
    void currentIndexChanged(int index);
    void currentTextChanged(const QString text);

private:
    // 边框
    int borderWidth{ 3 };
    // 弹框当前可见
    bool popupVisible{ false };

    // 文本框
    QLineEdit *boxEdit{ nullptr };
    // 下拉按钮
    QPushButton *boxDown{ nullptr };
    // 布局
    QHBoxLayout *boxLayout{ nullptr };
    // 弹框
    CuteBasicComboPopup *boxPop{ nullptr };
    // 定时器
    QTimer *editTimer{ nullptr };
};
#include "CuteBasicComboBox.h"
#include <QLineEdit>
#include <QPushButton>
#include <QHBoxLayout>
#include <QTimer>
#include <QDesktopWidget>
#include <QKeyEvent>
#include <QFocusEvent>
#include <QMouseEvent>
#include <QDebug>

CuteBasicComboBox::CuteBasicComboBox(QWidget *parent)
    : QWidget{parent}
    , boxEdit{new QLineEdit(this)}
    , boxDown{new QPushButton(this)}
    , boxLayout{new QHBoxLayout(this)}
    , boxPop{new CuteBasicComboPopup(this)}
    , editTimer{new QTimer(this)}
{
    // 支持样式表
    setAttribute(Qt::WA_StyledBackground);
    setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);

    initComponent();
}

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

int CuteBasicComboBox::getBorderWidth() const
{
    return borderWidth;
}

void CuteBasicComboBox::setBorderWidth(int px)
{
    if (borderWidth == px)
        return;
    borderWidth = px;
    boxLayout->setContentsMargins(borderWidth, borderWidth, borderWidth, borderWidth);
    emit borderWidthChanged(borderWidth);
}

bool CuteBasicComboBox::getPopupVisible() const
{
    return popupVisible;
}

void CuteBasicComboBox::setPopupVisible(bool visible)
{
    // qDebug() << __FUNCTION__ << popupVisible << visible;
    if (popupVisible == visible)
        return;
    popupVisible = visible;
    emit popupVisibleChanged(popupVisible);
}

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

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

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

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

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

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

CuteBasicComboPopup *CuteBasicComboBox::getPopup() const
{
    return boxPop;
}

bool CuteBasicComboBox::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 == boxDown) {
        // 过滤按钮事件
    } else if(watched == boxPop) {
        // 过滤弹框事件
    }
    return false;
}

void CuteBasicComboBox::initComponent()
{
    // 按钮设置
    boxDown->setObjectName("down");
    boxDown->setFocusPolicy(Qt::NoFocus);
    boxDown->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
    boxDown->installEventFilter(this);

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

    // 弹框容器设置
    boxPop->attachTarget(this);
    boxPop->installEventFilter(this);
    initContainer();
    connect(boxPop, &CuteBasicComboPopup::containerChanged, this, &CuteBasicComboBox::initContainer);
    connect(boxPop, &CuteBasicComboPopup::visibleChanged, this, &CuteBasicComboBox::setPopupVisible);

    // 布局
    boxLayout->setContentsMargins(borderWidth, borderWidth, borderWidth, borderWidth);
    boxLayout->setSpacing(0);
    boxLayout->addWidget(boxEdit);
    boxLayout->addWidget(boxDown);
    // 点击按钮,弹出
    // 这里有个问题,切换焦点导致弹框自动关闭,再进入该逻辑则visible已经是false,于是又弹出了
    connect(boxDown, &QPushButton::pressed, [this]() {
        // qDebug() << "toggled" << getPopupVisible();
        if (getPopupVisible()) {
            boxPop->hidePopup();
        } else {
            boxPop->showPopup();
        }

    });
}

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

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

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

<think>好的,用户想了解如何处理Qt中QComboBox的下拉框改变事件,特别是示例代码。首先,我需要回忆一下Qt的信号和槽机制。QComboBox的常用信号是currentIndexChanged,当用户选择不同选项时会触发这个信号。用户提到的“change event”可能指的是这个信号。 接下来,我应该检查用户提供的引用内容。引用[1]明确提到currentIndexChanged信号,说明这是处理选项变更的方法。引用[2]和[3]虽然讨论了QComboBox的扩展功能,但主要关注多选和样式,可能不直接相关,但需要确认是否有其他相关信息。 用户需要示例代码,所以我应该提供两种连接方式:使用设计器UI连接和手动代码连接。同时,要区分带参数和不带参数的信号重载,可能需要使用lambda函数来处理。此外,可能还需要覆盖changeEvent来处理特定事件,比如弹出或关闭下拉框,但根据用户的问题,这可能不是重点,但可以作为补充信息。 需要确保代码语法正确,比如在C++中使用正确的connect语法,以及在Python中使用PyQt或PySide的语法。用户提供的引用中有Python示例,但Qt通常用C++,所以可能需要两种语言的示例?不过用户之前的问题中有Python代码示例,但Qt主要用C++,所以最好以C++为例,并可能补充Python版本。 另外,用户可能希望知道如何获取当前文本或索引,所以在示例中应该包括这些内容。需要确保示例代码正确无误,比如在currentIndexChanged信号连接时,正确传递参数。 最后,生成相关问题时要覆盖信号与槽机制、自定义QComboBox、多选处理等,基于提供的引用内容。例如,引用[3]提到自定义添加删除按钮,可以作为相关问题之一。</think>### Qt QComboBox 下拉框 change event 处理方式 在 Qt 中,处理 `QComboBox` 下拉框的选项变更事件主要通过信号与槽机制实现。核心步骤如下: 1. **使用 `currentIndexChanged` 信号** 当用户选择下拉框中的不同选项时,`QComboBox` 会发射 `currentIndexChanged` 信号。此信号有两种重载形式: - `currentIndexChanged(int index)`:传递当前选中项的索引 - `currentIndexChanged(const QString &text)`:传递当前选中项的文本 2. **连接信号与槽函数** 通过 `connect` 方法将信号与自定义的槽函数绑定。 #### 示例代码(C++) ```cpp // 方式1:通过设计器UI自动连接(推荐) // 在Qt Designer中右键QComboBox -> "转到槽" -> 选择currentIndexChanged(int) void MainWindow::on_comboBox_currentIndexChanged(int index) { QString text = ui->comboBox->itemText(index); qDebug() << "当前选中项索引:" << index << "文本:" << text; } // 方式2:手动代码连接 QComboBox *comboBox = new QComboBox(this); connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index){ qDebug() << "索引变更:" << index; }); connect(comboBox, &QComboBox::currentTextChanged, [=](const QString &text){ qDebug() << "文本变更:" << text; }); ``` #### 示例代码(Python/PyQt6) ```python from PyQt6.QtWidgets import QComboBox combo = QComboBox() combo.addItems(["选项1", "选项2", "选项3"]) # 连接索引变化信号 combo.currentIndexChanged.connect(lambda index: print(f"索引: {index}")) # 连接文本变化信号 combo.currentTextChanged.connect(lambda text: print(f"文本: {text}")) ``` 3. **特殊事件处理** 如果需要处理下拉框弹出/关闭事件,可重写 `QComboBox` 的 `showPopup()` 和 `hidePopup()` 方法,或监听 `QEvent::Show` 和 `QEvent::Hide` 事件[^1][^3]。 --- ###
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龚建波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值