Qt自定义ComboBox

引言

本身Qt有下拉菜单的实现QComboBox,但其中有相当多的限制,简单界面使用没有问题,但复杂的界面就经常达不到设计效果。

第一个问题就是QComboBox内部有单独的View和Model,在setView时会将原有View的Model剥离替换为内部Model,导致需要分别设置。在本人想法中View和Model本身就是一体的,而下拉菜单只是View一个容器,View被安装在其中,不应该改变原有的东西。

第二个问题在于showPopup,当下拉菜单被点击后就会关闭,这适用于单选操作,但多余多选情况并不有效。在前文《QTreeView复选框的实现》中,如果将自定义的View放在QComboBox就会遇到上述困扰,组合框的实现本身并不难,因此索性自定义一个。

ComboBox本身就是编辑框、下拉按钮以及选项框的组合,交互逻辑大概如下:

  1. 正常状态是编辑框以及下拉按钮
  2. 点击下拉按钮后弹出选项框
  3. 点击选项框中子项后,编辑框根据选中状态变化
  4. 展开选项框子项后,选项框显示高度可自动拉伸,但不能超过最大展示数量
  5. 点击选项框外任意范围,选项框恢复收起状态

效果如下:
在这里插入图片描述

代码实现

代码中以CustomTreeView为例子,但对于View并没有特殊要求,将其替换为任意View都是可行的,实现思路和方法不会发生变化。

选项框的高度调整adjustHeight函数参考了QComboBox::showPopup源码,大概思路是从根节点开始便利子项,visualRect(…)若存在高度,则证明为非隐藏节点,将其纳入listHeight中,当个数超过maxVisibleItems()时则跳出循环结束遍历。

关于上述交互逻辑的第四点"点击选项框外任意范围,选项框恢复收起状态"的实现无需过分关注,将popupWidget的窗口标志设置为Qt::Popup即可。

样式表:

QAbstractItemView#general_popup_list {
	border-radius: 4px;
	padding-top: 10px;
	padding-bottom: 10px;
	background: rgb(50,50,53);
	alternate-background-color: rgb(50,50,53);
	border: 2px solid rgb(11,11,12);
	/*阴影*/
	margin-bottom: 3px;
}

QAbstractItemView#general_popup_list::item {
	height: 34px;
	padding-left: 10px;
	background-color: transparent;
	border: 2px solid transparent;
}

QAbstractItemView#general_popup_list::item:selected{
	border: 2px solid transparent;
	background-color: rgb(11,11,12);
}

QAbstractItemView#general_popup_list::item:hover {
	color: rgb(255,255,255);
	border: 2px solid rgb(89,89,89);
	background-color: transparent;
}

QAbstractItemView#general_popup_list::item:selected:hover {
	color: rgb(255,255,255);
	border: 2px solid rgb(89,89,89);
	background-color: rgb(11,11,12);
}

头文件:

class CustomComboBox : public QWidget
{
    Q_OBJECT

public:
    CustomComboBox(QWidget *parent = nullptr);
    virtual ~CustomComboBox();

    CustomTreeView* view();

    int maxVisibleItems();
    void setMaxVisibleItems(int maxItems);

private:
    void adjustHeight();// 参考QComboBox::showPopup源码实现
    void adjustPostion();

private slots:
    void slot_showPopup();
    void slot_refreshLineEdit();
    void slot_expanded();

private:
    QLineEdit* m_lineEdit;
    QPushButton* m_pushButton;

    CustomTreeView* m_view;
    QFrame* m_popupWidget;

    int m_maxVisibleItems;
};

具体实现:

#include <QApplication>
#include <QDesktopWidget>
#include <QStack>
#include <QScrollBar>
CustomComboBox::CustomComboBox(QWidget *parent)
    : QWidget(parent)
    , m_maxVisibleItems(8)
{
    m_lineEdit = new QLineEdit(this);

    m_pushButton = new QPushButton("v", this);
    connect(m_pushButton, &QPushButton::clicked, this, &CustomComboBox::slot_showPopup);

    m_view = new CustomTreeView(this);
    m_view->setObjectName("general_popup_list");
    m_view->setHeaderHidden(true);
    m_view->setCheckable();

    connect(m_view, &QTreeView::clicked, this, &CustomComboBox::slot_refreshLineEdit);
    connect(m_view, &QTreeView::expanded, this, &CustomComboBox::slot_expanded);
    //connect(m_view, &QTreeView::collapsed, this, &CustomComboBox::slot_expanded);

    m_popupWidget = new QFrame(this);
    m_popupWidget->setWindowFlags(Qt::Popup | Qt::FramelessWindowHint);
    m_popupWidget->setAttribute(Qt::WA_TranslucentBackground);
    m_popupWidget->hide();

    // 布局
    auto popupLayout = new QHBoxLayout(m_popupWidget);
    popupLayout->setMargin(0);
    popupLayout->addWidget(m_view);

    auto mainLayout = new QHBoxLayout(this);
    mainLayout->setMargin(0);
    mainLayout->setSpacing(0);
    mainLayout->addWidget(m_lineEdit);
    mainLayout->addWidget(m_pushButton);
}

CustomComboBox::~CustomComboBox()
{

}

CustomTreeView *CustomComboBox::view()
{
    return m_view;
}

int CustomComboBox::maxVisibleItems()
{
    return m_maxVisibleItems;
}

void CustomComboBox::setMaxVisibleItems(int maxItems)
{
    m_maxVisibleItems = maxItems;
}

void CustomComboBox::adjustHeight()
{
    auto tmpModel = view()->model();

    int listHeight = 0;
    int count = 0;
    QStack<QModelIndex> toCheck;
    toCheck.push(view()->rootIndex());

    QTreeView *treeView = qobject_cast<QTreeView*>(view());
    if (treeView && treeView->header() && !treeView->header()->isHidden())
        listHeight += treeView->header()->height();

    while (!toCheck.isEmpty()) {
        QModelIndex parent = toCheck.pop();
        for (int i = 0, end = tmpModel->rowCount(parent); i < end; ++i) {
            QModelIndex idx = tmpModel->index(i, 0, parent);
            if (!idx.isValid())
                continue;

            int tmpHeight = view()->visualRect(idx).height();
            if (tmpHeight) {
                listHeight += tmpHeight;
                ++count;
                if (count >= maxVisibleItems()) {
                    toCheck.clear();
                    break;
                }
            }

            if (tmpModel->hasChildren(idx) && treeView && treeView->isExpanded(idx))
                toCheck.push(idx);
        }
    }

    int spaceing = 28;// 与样式表有关
    if(m_popupWidget->isVisible() && m_popupWidget->height() > spaceing + listHeight){
        return;
    }
    m_popupWidget->setFixedHeight(spaceing + listHeight);
}

void CustomComboBox::adjustPostion()
{
    QPoint pos = mapToGlobal(QPoint(0, 0));
    pos.setY(pos.y() + height() + 5);

    //屏幕越界判断
    QRect screenRect = QApplication::desktop()->screenGeometry();
    if (pos.y() + m_popupWidget->height() > screenRect.height())
        pos.setY(mapToGlobal(QPoint(0, 0)).y() - m_popupWidget->height() - 5);

    m_popupWidget->move(pos);
}

void CustomComboBox::slot_showPopup()
{
    m_popupWidget->setFixedWidth(width());

    if (m_popupWidget->isHidden()) {
        adjustHeight();
        adjustPostion();
        m_popupWidget->show();
    }
    else {
        m_popupWidget->hide();
    }
}

void CustomComboBox::slot_refreshLineEdit()
{
    QStringList strList;
    for (QModelIndex tmpIndex : m_view->currentIndexes()) {
        strList << tmpIndex.data(Qt::DisplayRole).toString();
    }

    m_lineEdit->setText(strList.join(","));
    m_lineEdit->setToolTip(strList.join("\n"));
}

void CustomComboBox::slot_expanded()
{
    adjustHeight();
    adjustPostion();
}
  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Arui丶

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

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

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

打赏作者

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

抵扣说明:

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

余额充值