0.前言
给下拉框增加按钮是常见的功能,如 账号输入框的下拉:

网上有不少 Qt 实现的例子,实现方式也很多,在参照了别人的思路后我也实现了选项带按钮的下拉框。中间遇到不少坑,最后效果也不完美,后来没用到就不了了之了,仅供参考。
1.实现过程
本文选的是 QListWidget + QListWidgetItem 的方式,因为感觉相对简单点,而且和样式表更好搭配。这种方式主要是把 QListWidget 作为 QComboBox 的 View ,List 的 model 也设置为 QComboBox 的 model,这样展现出来的选项就是一个 QListWidgetItem,我们只需要把按钮设置到 QListWidgetItem 的 widget 中就行了。后来感觉 QComboBox 和 QListWidget 太多内置的规则,细节完善任重道远。
首先,要让 QComboBox 读取到选项的文本,需要给 QListWidgetItem 设置 DisplayRole:
QListWidgetItem* item_widget = new QListWidgetItem(); //设置显示的data,这样combox才有文字 item_widget->setData(Qt::DisplayRole,"text");
然后来看一下比较简单的实现:
QListWidget *item_list=new QListWidget(this); ui->comboBoxA->setModel(item_list->model()); ui->comboBoxA->setView(item_list); //添加选项 for(int i=0;i<5;i++) { //组合一个带按钮的widget QWidget *item_widget=new QWidget(); QHBoxLayout *layout=new QHBoxLayout(item_widget); layout->addStretch(); //弹簧 QPushButton *btn=new QPushButton(item_widget); layout->addWidget(btn); layout->setMargin(0); layout->setSpacing(0); QListWidgetItem* item_wrap = new QListWidgetItem(item_list); //测试长文字 QString text=(i==0)?"text long long long":"text"; //设置显示的data,这样combox才有文字 item_wrap->setData(Qt::DisplayRole,text); item_list->setItemWidget(item_wrap,item_widget); connect(btn,&QPushButton::clicked,this,[=](){ ui->comboBoxA->hidePopup(); //没有刷新弹框大小 item_list->takeItem(item_list->row(item_wrap)); delete item_wrap; }); }
很快就发现了问题,文字足够长时,选项整体宽度被拉长了,导致按钮位置超出了显示范围(下图蓝色框部分):

(按钮位置我是配合样式表的 margin 调整的,可见源代码)
索性我就继承了 QListWidget,重写了他的 visualRect 接口,在有滚动条时,rect 就去掉滚动条的宽度:
QRect ComboView::visualRect(const QModelIndex &index) const { QRect rect=QListWidget::visualRect(index); int width=this->width(); if(verticalScrollBar()->isVisible()){ width-=verticalScrollBar()->width(); } rect.setWidth(width); return rect; }
这下就能把选项挤过来了:

(2022-09-04 补充)之前有评论指出下拉滚动条滚动后,再次弹出时按钮和选项没对齐,拖动窗口大小后弹出又能恢复对齐,应该是WidgetItem的坐标同步问题。

目前想的简单的解决方法有两种,一是弹出时执行一遍resize的逻辑,二是隐藏时恢复弹出时的位置,以思路二为例:
void MyComboBox::hidePopup() { QStyle * const style = this->style(); QStyleOptionComboBox opt; initStyleOption(&opt); view()->scrollTo(view()->currentIndex(), style->styleHint(QStyle::SH_ComboBox_Popup, &opt, this) ? QAbstractItemView::PositionAtCenter : QAbstractItemView::EnsureVisible); QComboBox::hidePopup(); }
其他问题:
1.滚动条占位宽度计算的问题,我的想法是显示或增删选项时去判断是否有滚动条,然后调整按钮的 margin。
2.点击按钮删除选项后,下次弹出可能某个选项处于hover状态。
2.完整代码
github 链接:https://github.com/gongjianbo/MyTestCode/tree/master/Qt/MyComboBox
(demo 没怎么写注释,而且没有应用到自己的项目):
#pragma once #include
#include
#include
#include
#include
#include
class ComboView : public QListWidget { Q_OBJECT public: explicit ComboView(QWidget * parent=nullptr); QRect visualRect(const QModelIndex &index) const override; }; class ComboItem : public QWidget { Q_OBJECT public: explicit ComboItem(const QString &text,QWidget *parent = nullptr); ~ComboItem(); QString text() const; signals: void itemClicked(const QString &text); private: QString textValue; QPushButton *btn; }; class ComboDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit ComboDelegate(QObject *parent=nullptr); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; }; class MyComboBox : public QComboBox { Q_OBJECT public: explicit MyComboBox(QWidget *parent = nullptr); //设置下拉选项 void setRemovableItems(const QStringList &items); //显示弹框 void showPopup() override; void hidePopup() override; signals: void itemRemoved(const QString &text); private: QListWidget *itemList; };
#include "MyComboBox.h" #include
#include
#include
#include
#include
#include
ComboView::ComboView(QWidget *parent) : QListWidget(parent) { } QRect ComboView::visualRect(const QModelIndex &index) const { QRect rect=QListWidget::visualRect(index); int width=this->width(); if(verticalScrollBar()->isVisible()){ width-=verticalScrollBar()->width(); } rect.setWidth(width); return rect; } ComboItem::ComboItem(const QString &text, QWidget *parent) : QWidget(parent), textValue(text), btn(new QPushButton(text,this)) { QHBoxLayout *layout=new QHBoxLayout(this); layout->addStretch(); layout->addWidget(btn); layout->setMargin(0); layout->setSpacing(0); connect(btn,&QPushButton::clicked,[this]{ emit itemClicked(textValue); }); } ComboItem::~ComboItem() { qDebug()<<"~delete"<
setTextElideMode(Qt::ElideNone); setModel(itemList->model()); setView(itemList); setItemDelegate(new ComboDelegate(this)); } void MyComboBox::setRemovableItems(const QStringList &items) { //combox的additem insertitem不是虚函数 //实现里时调用的model->insertRow,但是懒得再去重写listmodel-view,就新增一个接口 itemList->clear(); if(items.isEmpty()) return; for(int i=0;i
setData(Qt::DisplayRole,items.at(i)); //widget_item->setData(Qt::TextAlignmentRole,int(Qt::AlignRight|Qt::AlignVCenter)); //itemList->addItem(widget_item); itemList->setItemWidget(widget_item,item); connect(item,&ComboItem::itemClicked,this,[this,item,widget_item](){ //take移除item后没有刷新弹框大小,干脆隐藏掉先 hidePopup(); itemList->takeItem(itemList->row(widget_item)); delete widget_item; emit itemRemoved(item->text()); }); } } void MyComboBox::showPopup() { QComboBox::showPopup(); } void MyComboBox::hidePopup() { QStyle * const style = this->style(); QStyleOptionComboBox opt; initStyleOption(&opt); view()->scrollTo(view()->currentIndex(), style->styleHint(QStyle::SH_ComboBox_Popup, &opt, this) ? QAbstractItemView::PositionAtCenter : QAbstractItemView::EnsureVisible); QComboBox::hidePopup(); }
#include "mainwindow.h" #include "ui_mainwindow.h" #include "MyComboBox.h" #include
#include
#include
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); //准备下拉选项串 QStringList str_list; for(int i=0;i<20;i++) { str_list.push_back(QString("%1 item").arg(i)); } //测试长文本显示 str_list[0]="0 item: text long long long long long long"; //【1】 QListWidget *item_list=new QListWidget(this); ui->comboBoxA->setModel(item_list->model()); ui->comboBoxA->setView(item_list); //添加选项 for(int i=0;i
addStretch(); //弹簧 QPushButton *btn=new QPushButton(str_list.at(i),item_widget); layout->addWidget(btn); layout->setMargin(0); layout->setSpacing(0); QListWidgetItem* item_wrap = new QListWidgetItem(item_list); //设置显示的data,这样combox才有文字 item_wrap->setData(Qt::DisplayRole,str_list.at(i)); //item_list->addItem(item_wrap); item_list->setItemWidget(item_wrap,item_widget); connect(btn,&QPushButton::clicked,this,[=](){ ui->comboBoxA->hidePopup(); //没有刷新弹框大小 item_list->takeItem(item_list->row(item_wrap)); delete item_wrap; }); } //【2】 //QStringList str_list; ui->comboBoxB->setRemovableItems(str_list); //ui->comboBoxB->setMaxVisibleItems(20); connect(ui->comboBoxB,&MyComboBox::itemRemoved,this,[=](const QString &text){ qDebug()<<"item remove"<
comboBox_2->clear(); }); } MainWindow::~MainWindow() { delete ui; }
/*下拉框*/ QComboBox{ min-width:1px; min-height:26px; padding-left: 5px; padding-right: 2px; color: white; border:1px solid rgb(128, 128, 128); background-color: rgb(100 ,100 ,100); } QComboBox:on{ /*弹出为on*/ } QComboBox:hover{ border:1px solid rgb(255, 170, 0); } QComboBox:disabled{ color: rgb(230, 230, 230); background-color:rgb(150, 150, 150); } QComboBox:editable{ background-color:rgb(100, 100, 100); } QComboBox:editable:disabled{ background-color:rgb(150, 150, 150); } /*下拉按钮-配合贴图*/ QComboBox::drop-down{ min-width:24px; } /*下拉框弹出项*/ QComboBox QAbstractItemView{ font: 15px "宋体"; background-color:rgb(110, 110, 110); } QComboBox QAbstractItemView::item{ height:24px; color:white; } QComboBox QAbstractItemView::item:hover{ background-color: rgb(255, 170, 0); } QComboBox QAbstractItemView::item:selected{ background-color: rgb(255, 170, 0); } /*按钮样式*/ QComboBox QAbstractItemView QPushButton{ max-width:40px; min-width:40px; max-height:20px; min-height:20px; border-radius:2px; border:0; margin-right:20px; background-color:white; } QComboBox QAbstractItemView QPushButton:hover{ background-color:cyan; }
3.参考
博客:QComboBox的代理(订制QComboBox组合框)_Always0nTheWay的博客-CSDN博客
博客:新浪博客
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/209113.html原文链接:https://javaforall.net
