QT5自定义下拉框为QTreeView类型(树形分上下级)的下拉框(QComboBox)(超详细步骤)

本文详细介绍了如何在C++项目中自定义QComboBox,使其支持树形结构的选择,包括创建MyComboBox类、使用QTreeView和QAbstractItemModel构建模型以及处理事件过滤和绘制事件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

项目开发中,很可能简单的QComboBox满足不了需求,就需要自定义QComboBox。

先看效果。

自定义ComboBox

1、先建立一个project,命名为CustomComboBox,建立一个project的过程不细说了。建立后的工程目录如下图:

在这里插入图片描述

2、在项目名CustomComboBox右击鼠标,新建一个c++class

在这里插入图片描述

然后在弹出的页面设置class的名字为MComboBox,设置class的基类为QComboBox,然后点击下一步,在弹出的页面点击完成即可创建完成。

在这里插入图片描述

3、创建完成后的项目目录结构如下。然后双击widget.ui文件,在UI文件里添加控件QComboBox.

在这里插入图片描述

在这里插入图片描述

4、右键控件QComboBox,点击提升为,在弹出的页面提升的类名称写入MyComboBox,点击添加,就会在提升的类中就会出现已经提升的类MyComboBox。最后点击提升。QComboBox就变成了MyComboBox。

在这里插入图片描述

添加后的页面变成如下所示:

在这里插入图片描述

QComboBox提升前:

在这里插入图片描述

QComboBox提升后:

提升后

5、在mycombobox.h和mycombobox.cpp文件内编写代码。
mycombobox.h



#ifndef MYCOMBOBOX_H
#define MYCOMBOBOX_H

#include <QComboBox>
#include <QTreeView>

class MyComboBox : public QComboBox
{
    Q_OBJECT
public:
    explicit MyComboBox( QWidget *parent = nullptr);
    void showPopup() override;
    void hidePopup() override;


    void setCustomText(const QString &text,QIcon icon = QIcon());
    void setView(QAbstractItemView *itemView);

protected:
    bool eventFilter(QObject *object, QEvent *event) override;
    void paintEvent(QPaintEvent *e) override;

private:
    bool m_isPermitHidePopup;
    QString m_customString;
    QIcon m_customIcon;
    bool m_bCustomLineEdit;
};

#endif // MYCOMBOBOX_H
mycombobox.h



#include "mycombobox.h"
#include <QHeaderView>
#include <QLineEdit>
#include <QMouseEvent>
#include <QStylePainter>
MyComboBox::MyComboBox(QWidget *parent)
    : QComboBox(parent),
    m_isPermitHidePopup(true),
    m_bCustomLineEdit(false)
{
    QTreeView *view = new QTreeView(this);
    view->header()->setVisible(false);
    setView(view);
    connect (view, &QTreeView::doubleClicked, [=](const QModelIndex&){
        m_isPermitHidePopup = true;
        hidePopup();
    });
    view->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    QFrame* frame = this->findChild<QFrame*>();
    if(frame != nullptr)
    {
        frame->installEventFilter(this);
    }
    frame->setFixedHeight(200);
}

void MyComboBox::showPopup()
{
    QComboBox::showPopup();
    m_isPermitHidePopup = false;
}

void MyComboBox::hidePopup()
{
    if(m_isPermitHidePopup)
    {
        QComboBox::hidePopup();
    }
}

void MyComboBox::setCustomText(const QString &text,QIcon icon)
{
    m_bCustomLineEdit = true;
    m_customString = text;
    m_customIcon = icon;
}

bool MyComboBox::eventFilter(QObject *object, QEvent *event)
{
    QFrame* frame = this->findChild<QFrame*>();
    if(frame && frame == object)
    {
        if(event->type() == QEvent::MouseButtonPress)
        {
            QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
            QPoint globalPos = mouseEvent->globalPos();
            QRect rect(frame->mapToGlobal(QPoint(0,0)), QSize(frame->width(), frame->height()));
            //鼠标按下位置不在下拉框内则关闭下拉框
            if(!rect.contains(globalPos))
            {
                m_isPermitHidePopup = true;
            }
        }
    }
    return QComboBox::eventFilter(object,event);

}

void MyComboBox::paintEvent(QPaintEvent *e)
{
    if (m_bCustomLineEdit)
    {
        QStylePainter painter(this);
        painter.setPen(palette().color(QPalette::Text));
        QStyleOptionComboBox opt;
        initStyleOption(&opt);
        opt.currentText = m_customString;
        opt.currentIcon = m_customIcon;
        painter.drawComplexControl(QStyle::CC_ComboBox, opt);
        painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
    }
    else
    {
        QComboBox::paintEvent(e);
    }
}

void MyComboBox::setView(QAbstractItemView *itemView)
{
    QComboBox::setView(itemView);
    if (itemView == nullptr)
    {
        return;
    }
    itemView->setSelectionMode(QAbstractItemView::ExtendedSelection);
    QWidget *w = itemView->parentWidget();
    if (w != nullptr)
    {
        itemView->removeEventFilter(w);
        itemView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    }
}

6、接着,在工程中再添加数的节点的两个文件:mytreeitem.h,mytreeitem.cpp
mytreeitem.h


#include<QVariant>

class MyTreeItem
{
public:
    explicit MyTreeItem(const QString &data, MyTreeItem *parent = nullptr);

    ~MyTreeItem();

    void appendChild(MyTreeItem *child);

    MyTreeItem *child(int row);

    int childCount() const;

    int columnCount() const;

    QVariant data(int column) const;

    int row() const;

    int setRow() const;

    MyTreeItem *parent();

private:
    QList<MyTreeItem*> childItems;
    QString itemData;
    MyTreeItem *parentItem;
};
mytreeitem.cpp


#include "mytreeitem.h"

MyTreeItem::MyTreeItem(const QString &data, MyTreeItem *parent)
    : parentItem(parent), itemData(data)
{

}


MyTreeItem::~MyTreeItem()
{
    qDeleteAll(childItems);
}

void MyTreeItem::appendChild(MyTreeItem *child)
{
    childItems.append(child);
}

MyTreeItem *MyTreeItem::child(int row)
{
    return childItems.value(row);
}

int MyTreeItem::childCount() const
{
    return childItems.count();
}

int MyTreeItem::columnCount() const
{
    return 1;
}

QVariant MyTreeItem::data(int column) const
{
    return itemData;
}

int MyTreeItem::row() const
{
    if (parentItem)
    {
        return parentItem->childItems.indexOf(const_cast<MyTreeItem*>(this));
    }
    return 0;
}

MyTreeItem *MyTreeItem::parent()
{
    return parentItem;
}
7、接着在工程中添加树的model的文件:mytreemodel.h, mytreemodel.cpp
mytreemodel.h


#include "mytreeitem.h"
#include <QAbstractItemModel>

class MyTreeModel : public QAbstractItemModel
{
public:
    explicit MyTreeModel(QString header, QMap<QString, QStringList>& data, QObject *parent = nullptr);
    ~MyTreeModel();

    QVariant data(const QModelIndex &index, int role) const override;
    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
    QModelIndex parent(const QModelIndex &index) const override;
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;

private:
    MyTreeItem *rootItem;
};
mytreemodel.cpp


#include "mytreemodel.h"

MyTreeModel::MyTreeModel(const QString header, QMap<QString, QStringList> &data, QObject *parent)
{
    rootItem = new MyTreeItem(header);
    for (auto folder : data.keys())
    {
        MyTreeItem* folderItem = new MyTreeItem(folder, rootItem);
        rootItem->appendChild(folderItem);

        for(auto file: data.value(folder))
        {
            MyTreeItem *fileItem = new MyTreeItem(file, folderItem);
            folderItem->appendChild(fileItem);
        }
    }
}

MyTreeModel::~MyTreeModel()
{
    delete rootItem;
}

int MyTreeModel::columnCount(const QModelIndex &parent) const
{
    if (parent.isValid())
    {
        return static_cast<MyTreeItem*>(parent.internalPointer())->columnCount();
    }
    else
    {
        return rootItem->columnCount();
    }
}

QVariant MyTreeModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
    {
        return QVariant();
    }

    if (role != Qt::DisplayRole)
    {
        return QVariant();
    }
    int col = index.column();
    MyTreeItem *item = static_cast<MyTreeItem*>(index.internalPointer());
    return item->data(index.column());
}

QVariant MyTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
    {
        return rootItem->data(section);
    }
    return QVariant();
}

QModelIndex MyTreeModel::index(int row, int column, const QModelIndex &parent) const
{
    if (!hasIndex(row, column, parent))
    {
        return QModelIndex();
    }

    MyTreeItem *parentItem;
    if (!parent.isValid())
    {
        parentItem = rootItem;
    }
    else
    {
        parentItem = static_cast<MyTreeItem*>(parent.internalPointer());
    }

    MyTreeItem *childItem = parentItem->child(row);
    if (childItem)
    {
        return createIndex(row, column, childItem);
    }
    else
    {
        return QModelIndex();
    }
}

QModelIndex MyTreeModel::parent(const QModelIndex &index) const
{
    if (!index.isValid())
    {
        return QModelIndex();
    }

    MyTreeItem *childItem = static_cast<MyTreeItem*>(index.internalPointer());
    MyTreeItem *parentItem = childItem->parent();

    if (parentItem == rootItem)
    {
        return QModelIndex();
    }

    return createIndex(parentItem->row(), 0, parentItem);
}



int MyTreeModel::rowCount(const QModelIndex &parent) const
{
    MyTreeItem *parentItem;

    if (parent.column() > 0)
    {
        return 0;
    }

    if (!parent.isValid())
    {
        parentItem = rootItem;
    }
    else
    {
        parentItem = static_cast<MyTreeItem*>(parent.internalPointer());
    }

    return parentItem->childCount();
}

最后,在widget.cpp中写测试代码
widget.cpp


#include "widget.h"
#include "ui_widget.h"
#include <QTreeView>
#include "mytreemodel.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QTreeView *view = new QTreeView(this);
    ui->userComboBox->setView(view);

    QString header;
    header.append("doucument");

    QString child1 = "folder1";
    QStringList grandson1 = {"file1", "file2"};

    QString child2 = "folder2";
    QStringList grandson2 = {"file3", "file4"};


    QMap<QString, QStringList> data;
    data.insert(child1, grandson1);
    data.insert(child2, grandson2);

    MyTreeModel *model = new MyTreeModel(header, data);
    ui->userComboBox->setModel(model);

    connect(view, &QTreeView::pressed, this, [=](QModelIndex index){
        //让ui->userComboBox上显示当前被点击的index的名称
        ui->userComboBox->setCustomText(index.data(Qt::DisplayRole).toString());
    });

}

Widget::~Widget()
{
    delete ui;
}

以上代码复制至对应文件即可运行。

<think>嗯,用户想实现QComboBox的下拉菜单多层级显示。首先,我需要回忆一下QComboBox的常规用法。根据引用[1]和[2],标准QComboBox只能显示单层列表,不支持复杂结构。所以用户的需求超出了默认功能,必须进行定制化开发。 接下来,我应该考虑如何扩展QComboBox来实现多级菜单。可能的方法包括使用QTreeView作为下拉视图,或者自定义模型。引用[2]提到需要继承和重写QComboBox的方法,这可能涉及到设置自定义视图,比如QTreeView,这样可以在下拉时显示层级结构。 然后,示例代码的结构需要规划。可能需要创建一个继承自QComboBox的子类,重写showPopup方法,设置一个QTreeView作为弹出视图,并处理点击事件。同时,数据模型可能需要用QStandardItemModel来构建层级结构,每个节点可以包含子项。 还要考虑信号和槽的连接,确保选择子项后能正确更新QComboBox的显示内容。比如,当用户点击树视图中的子节点时,需要触发currentIndexChanged信号,并更新当前显示的文本。 另外,引用[3]提到了多个QComboBox之间的数据同步,虽然当前问题不涉及同步,但数据模型的处理可能相关,比如确保模型的数据结构正确,以便在不同控件之间共享。 需要注意的是,Qt的模型/视图架构是否适合这种扩展。使用QStandardItemModel可以方便地添加层级结构,而QTreeView能够展示这种结构。因此,在自定义QComboBox时,设置其视图为QTreeView,并为其提供带有层级结构的模型应该是可行的。 最后,验证示例代码的正确性,确保在点击父节点时不会直接关闭下拉菜单,而是展开子项,只有选择叶子节点时才更新组合框的显示并关闭下拉。这可能需要处理QTreeView的点击事件,判断是否为叶子节点,并手动关闭弹出窗口。</think>根据Qt控件特性与现有技术方案,实现QComboBox多级菜单需要继承QComboBox自定义视图组件。以下是实现步骤与示例代码: ### 一、实现原理 1. 使用QTreeView替代默认的下拉列表视图(通过`setView()`方法)[^2] 2. 构建带有层级结构的QStandardItemModel数据模型 3. 通过委托机制处理节点展开/折叠交互 4. 重写事件处理逻辑实现多级菜单保持 ### 二、完整实现代码 ```cpp #include <QComboBox> #include <QTreeView> #include <QStandardItemModel> class MultiLevelComboBox : public QComboBox { public: explicit MultiLevelComboBox(QWidget *parent = nullptr) : QComboBox(parent) { // 创建树形视图 treeView = new QTreeView(this); treeView->setHeaderHidden(true); treeView->setEditTriggers(QAbstractItemView::NoEditTriggers); setView(treeView); // 初始化模型 model = new QStandardItemModel(this); setModel(model); // 连接信号槽 connect(treeView, &QTreeView::clicked, this, &MultiLevelComboBox::onItemClicked); } void addParentItem(const QString &text) { QStandardItem *item = new QStandardItem(text); model->appendRow(item); } void addChildItem(QStandardItem *parent, const QString &text) { QStandardItem *child = new QStandardItem(text); parent->appendRow(child); } private slots: void onItemClicked(const QModelIndex &index) { if (model->itemFromIndex(index)->rowCount() == 0) { // 叶子节点 setCurrentIndex(index.row()); setCurrentText(model->itemFromIndex(index)->text()); hidePopup(); } else { // 父节点 treeView->expand(index); } } private: QTreeView *treeView; QStandardItemModel *model; }; ``` ### 三、使用示例 ```cpp // 创建控件 MultiLevelComboBox *combo = new MultiLevelComboBox(this); // 添加层级数据 QStandardItem *os = new QStandardItem("操作系统"); combo->model()->appendRow(os); combo->addChildItem(os, "Windows"); combo->addChildItem(os, "Linux"); QStandardItem *db = new QStandardItem("数据库"); combo->model()->appendRow(db); combo->addChildItem(db, "MySQL"); combo->addChildItem(db, "PostgreSQL"); ``` ### 四、核心功能说明 1. 视图控制:使用`QTreeView`显示层级结构,通过`expand()`控制节点展开 2. 选择逻辑:仅在点击叶子节点时更新显示值并关闭下拉框 3. 模型管理:使用`QStandardItemModel`存储层级数据,支持动态修改 4. 样式优化:可通过`QSS`自定义节点图标和缩进样式
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

commonbelive

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

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

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

打赏作者

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

抵扣说明:

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

余额充值