Qt QComboBox 下拉復選多選

Qt 中,QComboBox 默認只支持單選,但實際使用過程中,我們經常會碰到需要多選的情況,但是通過一些直接或者曲折的方法還是可以實現的。

1、通過 QListWidget 間接實現

這種方式是網上搜索最多的一種方式,也是相對來說比較簡單的一種方法。

首先,自定義ComboBox類并繼承自 QComboBox,在類內并定義一個QListWidget對象。

class MultiWdgComboBox : public QComboBox
{Q_OBJECT
public:using QComboBox::QComboBox;MultiComboBox(QWidget* parent = nullptr);void addItem(const QString &text, const QVariant &userData /* = QVariant() */);private:QListWidget* m_view_ptr{ nullptr };QString m_text{ QString() };
};

如上所示,定義好自定義之后,在其構造函數中對ComboBox的 view 和 model 重新設置。因為多選情況下控件顯示的內容可能需要自定義,QComboBox默認是不支持的,所以我們可以使用QComboBox自帶的QLineEdit 來實現自定義格式的數據顯示。

MultiWdgComboBox::MultiWdgComboBox(QWidget* parent) : QComboBox(parent)
{m_view_ptr = new QListWidget;m_view_ptr->setContentsMargins(QMargins(15, 0, 0, 0));setEditable(true);lineEdit()->setReadOnly(true);setModel(m_view_ptr->model());setView(m_view_ptr); }

緊接著,我們需要重寫基類的 addItem 函數,讓其能滿足我們自定義的類。下面的這種方式也就是在我們定義的 QListWdiget 對象中 insert 一個item,并對 item 設置對應的QWidget

void MultiWdgComboBox::addItem(const QString &text, const QVariant &userData)
{QListWidgetItem *pItem = new QListWidgetItem;QCheckBox* checkBox = new QCheckBox(this);checkBox->setText(text);pItem->setData(Qt::UserRole, userData);m_view_ptr->addItem(pItem);m_view_ptr->setItemWidget(pItem, checkBox);...
}

為了方便我們在點擊 QCheckBox 時能實時改變顯示的數據,我們需要實現一個信號槽的連接。

void MultiWdgComboBox::addItem(const QString &text, const QVariant &userData)
{...QCheckBox* checkBox = new QCheckBox(this);...connect(checkBox, &QCheckBox::clicked, this, [this](bool checked){auto box = static_cast<QCheckBox*>(sender());QStringList texts = m_text.isEmpty() ? QStringList() : m_text.split(";");if (checked){texts.append(box->text());}else{texts.removeOne(box->text());}m_text = texts.join(";");this->setEditText(m_text);});
}

有了上面的步驟,基本上一個簡單的多選 ComboBox 控件就初具雛形了。

如果初始狀態下,已經有部分的item被選中了,那我們該如何設置對應的item狀態呢?

void MultiWdgComboBox::setData(const QVariant& data)
{QStringList text;QVariantList datas = data.value<QVariantList>();for (int index = 0; index < m_view_ptr->count(); ++index){auto item = m_view_ptr->item(index);auto ptr = static_cast<QCheckBox*>(m_view_ptr->itemWidget(item));if (datas.contains(item->data(Qt::UserRole))){if (ptr != nullptr && ptr->isEnabled()){ptr->setChecked(true);text.push_back(ptr->text());}}}m_text = text.join(";");
}

上面的這個函數是根據item的userData來進行設置的,當然也可以通過item的text來進行設置。

同理,獲取已經選中的item數據是一樣的道理。

QVariant MultiWdgComboBox::data() const
{QVariantList datas;for (int index = 0; index < m_view_ptr->count(); ++index){auto item = m_view_ptr->item(index);auto ptr = static_cast<QCheckBox*>(m_view_ptr->itemWidget(item));if (ptr != nullptr && ptr->isChecked()){datas.push_back(item->data(Qt::UserRole));}}return datas;}

當然如果 item 的 userData 沒有實際的意義,只是想標識一下被勾選的item,也可以用 二進制的方式來實現,最后可通過循環右移一位的方式遍歷獲得勾選的全部item項。也可以使用 Qt 的 QFlags 屬性來實現。

根據上面的內容,我們基本上已經實現了一個ComboBox 的基本功能,但是由于使用了 QComboBox的edit屬性,所以存在一個不好的體驗,就是在點擊下拉的時候,響應區域只有下拉箭頭表示的那部分范圍,而在 其 QLineEdit 所在的范圍并不響應。

所以,根據以前 《QComboBox文字居中的幾種實現方式》這篇文章的內容,對自定義ComboBox的顯示部分做了重新繪制。

重寫 paintEvent 事件函數即可。

void MultiWdgComboBox::paintEvent(QPaintEvent* e)
{QStylePainter painter(this);painter.setPen(palette().color(QPalette::Text));QStyleOptionComboBox opt;initStyleOption(&opt);painter.drawComplexControl(QStyle::CC_ComboBox, opt);if (opt.editable){painter.drawControl(QStyle::CE_ComboBoxLabel, opt);return;}QRect editRect = this->style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this);QStyleOptionButton buttonOpt;buttonOpt.initFrom(this);buttonOpt.direction = Qt::LeftToRight;buttonOpt.rect = editRect;buttonOpt.text = m_text;buttonOpt.icon = opt.currentIcon;buttonOpt.iconSize = opt.iconSize;painter.drawControl(QStyle::CE_CheckBoxLabel, buttonOpt);}

2、另辟蹊徑,使用 QPushBututon 的 setMenu 方法,set一個 自定義Action的Menu。

這種方法呢,是我在做其他需求的時候偶然發現的,原來一些比較復雜的控件都可以用原生的控件可以實現。
首先,自定義一個繼承自 QPushButton的子類 MultiButtonComboBox

class MultiButtonComboBox : public QPushButton
{Q_OBJECT
public:MultiButtonComboBox(QWidget* parent = nullptr);void addItem(const QString& text, const QVariant& data = QVariant());void setData(const QVariant& data);QVariant data() const;private:MultiListWidget* m_ptr{ nullptr };
};

如上所示,定義好自定義之后,在其構造函數中設置一個自定義界面的menu。

MultiButtonComboBox::MultiButtonComboBox(QWidget* parent) : QPushButton(parent)
{auto menu = new QMenu(this);m_ptr = new MultiListWidget(this);QWidgetAction *action = new QWidgetAction(this);action->setDefaultWidget(m_ptr);menu->addAction(action);setMenu(menu);connect(m_ptr, &MultiListWidget::signal_text, this, [this](const QString& text){setText(text);});}

設置好自定義的菜單界面之后,只需要根據需要實現自定義的菜單界面就行了。可以用 QListWidget, 也可以用 QListView,我這邊用的是 QListView 和 自定義listviewitem 代理實現的。

class MultiListWidget : public QWidget
{Q_OBJECTpublic:MultiListWidget(QWidget *parent = nullptr);~MultiListWidget();void addItem(const QString& text, const QVariant& userData);void setData(const QVariant& data);QVariant data() const;private:void initPage();signals:void signal_text(const QString& text);private:Ui::MultiListWidget *ui;QStandardItemModel* m_pModel{ nullptr };QString m_text{ QString() };};

這種方式的實現是比較簡單的,對 QListView 設置 item 代理及 model 之后,后續的操作只是對 model 的數據操作。為了圖方便,使用了標準的 QStandardItemModel 類,當然可以自定義 model 類,通過重寫 自定義類的 data 函數,能更好的滿足自定義功能的需求。

void MultiListWidget::initPage()
{m_pModel = new QStandardItemModel();auto *delegate = new CmbBoxItemDelegate(this);ui->listView->setItemDelegate(delegate);ui->listView->setModel(m_pModel);...
}

自定義 QListView 的 item 代理后,可以通過重寫它的 editorEvent 函數來控制item的編輯事件。那我們就可以通過實現 代理與 當前類的信號槽來實現我們已經選擇、顯示的item 數據。

void MultiListWidget::initPage()
{...auto *delegate = new CmbBoxItemDelegate(this);...connect(delegate, &CmbBoxItemDelegate::signal_btn_clicked, this, [this](const QModelIndex& index){QStringList texts = m_text.isEmpty() ? QStringList() : m_text.split(";");bool checked = !index.data(Qt::UserRole + 1).toBool();m_pModel->setData(index, checked, Qt::UserRole + 1);if (checked){texts.append(index.data(Qt::DisplayRole).toString());}else{texts.removeOne(index.data(Qt::DisplayRole).toString());}m_text = texts.join(";");emit signal_text(m_text);});
}

緊接著,實現 addItem 函數。

void MultiListWidget::addItem(const QString& text, const QVariant& userData)
{QStandardItem *pItem = new QStandardItem;pItem->setData(false, Qt::UserRole + 1);pItem->setData(text, Qt::DisplayRole);pItem->setData(userData, Qt::UserRole);m_pModel->appendRow(pItem);
}

setData函數。

void MultiListWidget::setData(const QVariant& data)
{QStringList texts;auto datas = data.toList();for (int index = 0; index < m_pModel->rowCount(); ++index){auto mIndex = m_pModel->index(index, 0);if (datas.contains(mIndex.data(Qt::UserRole))){m_pModel->setData(mIndex, true, Qt::UserRole + 1);texts.append(mIndex.data().toString());}}m_text = texts.join(";");emit signal_text(m_text);
}

data 函數。

QVariant MultiListWidget::data() const
{QVariantList datas;for (int index = 0; index < m_pModel->rowCount(); ++index){auto mIndex = m_pModel->index(index, 0);if (mIndex.data(Qt::UserRole + 1).toBool()){datas.append(mIndex.data(Qt::UserRole));}}return datas;
}

3、自定義QComboBox 的 item delegate

這種方式跟上面第二種來說是差不多的,只不過上面的第二種方法實現的比較曲折,而QComboBox的view也可以是個ListView。所以我們取了第一種方法的繪制顯示text的方法和第二種的item代理,結合就有了第三種方法。相對來說,這種方法更簡單直接些。

首先自定義繼承自QComboBox的類并設置代理,實現代理的信號槽函數。

MultiWdgComboBox::MultiViewComboBox(QWidget* parent) : QComboBox(parent)
{auto delegate = new CmboBoxItemDelegate(this);setItemDelegate(delegate);connect(delegate, &CmboBoxItemDelegate::signal_btn_clicked, this, [this](const QModelIndex& index){auto checked = !index.data(Qt::UserRole + 1).toBool();this->model()->setData(index, checked, Qt::UserRole + 1);QStringList texts = m_text.isEmpty() ? QStringList() : m_text.split(";");if (checked){texts.append(index.data().toString());}else{texts.removeOne(index.data().toString());}m_text = texts.join(";");});
}

這種方式也是唯一一種不需要重寫 addItem 方法的實現形式,后面的 setData 和 data 與上面第二種方式的完全一樣。

這種方式要避免多次設置item的代理,否則代理可能不生效。

如果我們使用的是第一種方法,鼠標點擊的范圍如果超過了每行QCheckBox的實際范圍,實際效果下來是,既沒有選中某行,comboBox的下拉框也會收起。

同樣的,如果使用的是第三種,自定義了QComboBox的 item 代理,點擊起哄一行也會將下拉框收起,這樣我們要完成多選的話就得點擊好幾次。

所以,我們可以通過重寫hidePopup函數來避免這個問題。

void MultiViewComboBox::hidePopup()
{QWidget *popup = this->findChild<QFrame*>();if(!popup->geometry().contains(QCursor::pos())){QComboBox::hidePopup();}
}

4、注意事項

在第一種使用 QListWidget 的時候,測試時出現了下面這種情況。

在這里插入圖片描述
這個問題的原因是我們給QListWidget設置了model,為什么呢?因為根據Qt已經有的那種 model/view的結構,QListWidget已經是對應的結構了,它有自己的model,如果想要通過重寫自己的model來實現數據的話,建議使用QlistView

但是在這個里面,我們已經選擇了QListWidget,那有沒有什么解決辦法。

其實我們只需要在給ComboBox設置view之前設置model就可以了,也就是說,下面的這兩行代碼順序不能互換。

MultiComboBox::MultiComboBox(QWidget* parent) : QComboBox(parent)
{...setModel(m_view_ptr->model());setView(m_view_ptr); }

測試代碼

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/75952.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/75952.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/75952.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Selenium自動化:玩轉瀏覽器,搞定動態頁面爬取

嘿&#xff0c;各位爬蟲愛好者和自動化達人們&#xff01;是不是經常遇到這種情況&#xff1a;信心滿滿地寫好爬蟲&#xff0c;requests一把梭&#xff0c;結果抓下來的HTML里&#xff0c;想要的數據空空如也&#xff1f;定睛一看&#xff0c;原來數據是靠JavaScript動態加載出…

天梯賽 L2-023 圖著色問題

使用vector<vector<int>> g(N)去存儲邊&#xff0c;然后每次判斷每個節點的鄰節點是不是相同的顏色&#xff0c;需要注意的是不同的顏色一定需要為K種&#xff0c;不能多也不能少。 #include<bits/stdc.h> using namespace std; int main(){int n,m,k;cin&g…

在ubuntu24上裝ubuntu22

實驗室上有一臺只裝了ubuntu24的電腦&#xff0c;但是項目要求在22上進行 搞兩個ubuntu系統&#xff01; 步驟一&#xff1a;制作22的啟動盤 步驟二&#xff1a;進入bios安裝界面 步驟三&#xff1a;選擇try or install ubuntu 步驟四&#xff1a;選擇try ubuntu 步驟五&…

【PVR Review】《Review of Deep Learning Methods for Palm Vein Recognition》

[1]譚振林,劉子良,黃藹權,等.掌靜脈識別的深度學習方法綜述[J].計算機工程與應用,2024,60(06):55-67. 文章目錄 1、Background and Motivation2、數據采集3、掌脈圖像預處理3.1、ROI提取算法3.2、圖像濾波與增強 4、掌脈識別算法4.1、基于深度學習的方法4.2、其他方法 5、融合識…

【CSP】202403-1詞頻統計

文章目錄 算法思路1. 數據結構選擇2. 輸入處理3. 統計出現的文章數4. 輸出結果 代碼示例代碼優化 樣例輸入 4 3 5 1 2 3 2 1 1 1 3 2 2 2 2 3 2樣例輸出 2 3 3 6 2 2算法思路 1. 數據結構選擇 vector<int>&#xff1a;用于存儲每篇文章的單詞列表&#xff08;可能包含…

Docker基礎1

本篇文章我將從系統的知識體系講解docker的由來和在linux中的安裝下載 隨后的文章會介紹下載鏡像、啟動新容器、登錄新容器 如需轉載&#xff0c;標記出處 docker的出現就是為了節省資本和服務器資源 當企業需要一個新的應用程序時&#xff0c;需要為它買臺全新的服務器。這樣…

Linux系統學習Day04 阻塞特性,文件狀態及文件夾查詢

知識點4【文件的阻塞特性】 文件描述符 默認為 阻塞 的 比如&#xff1a;我們讀取文件數據的時候&#xff0c;如果文件緩沖區沒有數據&#xff0c;就需要等待數據的到來&#xff0c;這就是阻塞 當然寫入的時候&#xff0c;如果發現緩沖區是滿的&#xff0c;也需要等待刷新緩…

vue 3 從零開始到掌握

vue3從零開始一篇文章帶你學習 升級vue CLI 使用命令 ## 查看vue/cli版本&#xff0c;確保vue/cli版本在4.5.0以上 vue --version ## 安裝或者升級你的vue/cli npm install -g vue/cli ## 創建 vue create vue_test ## 啟動 cd vue_test npm run servenvm管理node版本&#…

Mysql專題篇章

一、事務的四大特性&#xff1f; 1、原子性&#xff1a;是指事務包含的所有操作要么全部成功&#xff0c;要么全部失敗回滾。 2、一致性&#xff1a;是指一個事務執行之前和執行之后都必須處于一致性狀態。比如a與b賬戶共有100塊&#xff0c;兩人之間轉賬之后無論成功還是失敗…

CAD插件實現:自動遞增編號(前綴、后綴、位數等)——CADc#實現

cad中大量輸入一定格式的遞增編號時&#xff0c;可用插件實現&#xff0c;效果如下&#xff1a; ①本插件可指定數字位數、起始號碼、加前綴、后綴、文字顏色等&#xff08;字體樣式和文字所在圖層為cad當前圖層和當前字體樣式&#xff09;。 ②插件采用Jig方式&#xff0c;即…

k8s1.24升級1.28

0、簡介 這里只用3臺服務器來做一個簡單的集群&#xff0c;當前版本是1.24.17目標升級到1.28.17 地址主機名192.168.160.40kuber-master-1192.168.160.41kuber-master-2192.168.160.42kuber-node-1 因為1.24已經更換過了容器運行時&#xff0c;所以之后的升級相對就會簡單&am…

4.3-2 jenkins

一.登錄jenkins 二.修改密碼 三.配置節點 新建節點 編輯節點名稱 編輯節點配置 激活節點 將jar下載到指定的路徑 再到dos命令下的路徑 E:\az\wx 執行 配置節點成功 四. 安全設置中&#xff0c;勾選代理 五.新建項目 編輯項目名稱 編輯項目執行的 路徑&#xff1a;C:\Users\Ad…

js對象與數組的互轉

js對象與數組的互轉 文章目錄 js對象與數組的互轉一、數組轉對象1.使用forEach,for in,es6展開運算符,assign2. 使用 Object.fromEntries()3. 將數組轉為鍵值對對象4. 使用 reduce()4. 數組元素為對象時提取屬性 二、對象轉數組1. 提取鍵/值/鍵值對2. 轉換為特定結構的數組 三、…

HTTPS在信息傳輸時使用的混合加密機制,以及共享、公開密鑰加密的介紹。

HTTPS在信息傳輸時使用的混合加密機制&#xff0c;其中包括了共享密鑰加密和公開密鑰加密&#xff0c;我們先來介紹一下這兩種加密方式。 共享密鑰加密&#xff08;對稱密鑰&#xff09; 對稱加密是指加密和解密使用的是同一個密鑰。就像家里的門鎖&#xff0c;鑰匙只有一把&…

Oracle 23ai Vector Search 系列之4 VECTOR數據類型和基本操作

文章目錄 Oracle 23ai Vector Search 系列之4 VECTOR數據類型和基本操作VECTOR 數據類型基本語法Vector 維度限制和向量大小向量存儲格式&#xff08;DENSE vs SPARSE&#xff09;1. DENSE存儲2. SPARSE存儲3. 內部存儲與空間計算 Oracle VECTOR數據類型的聲明格式VECTOR基本操…

機器學習——ROC曲線、PR曲線

一、ROC曲線簡介 1.1 ROC曲線的構成 1.橫軸&#xff08;假正率&#xff0c;FPR&#xff09;&#xff1a; 表示負樣本被錯誤分類為正的比例&#xff08;越小越好&#xff09; 2.縱軸&#xff08;真正率&#xff0c;TPR&#xff0c;即召回率&#xff09;&#xff1a; 表示正樣…

IntelliJ IDEA下開發FPGA——FPGA開發體驗提升__上

前言 由于Quartus寫代碼比較費勁&#xff0c;雖然新版已經有了代碼補全&#xff0c;但體驗上還有所欠缺。于是使用VS Code開發&#xff0c;效果如下所示&#xff0c;代碼樣式和基本的代碼補全已經可以滿足開發&#xff0c;其余工作則交由Quartus完成 但VS Code的自帶的git功能&…

昂貴的DOM操作:一次DOM導致的性能問題排查記錄

公司來了一個前端實習生&#xff0c;踏實&#xff0c;勤快&#xff0c;很快得到老大的認可&#xff0c;分配給她一個需求&#xff0c;大概如下&#xff1a;構建一個公司產品的評論展示頁面&#xff0c;頁面可以滾動加載新的內容&#xff0c;同時如果已經加載的內容發生變化&…

前端服務配置詳解:從入門到實戰

前端服務配置詳解&#xff1a;從入門到實戰 一、環境配置文件&#xff08;.env&#xff09; 1.1 基礎結構 在項目根目錄創建 .env 文件&#xff1a; # 開發環境 VUE_APP_API_BASE_URL http://localhost:3000/api VUE_APP_VERSION 1.0.0# 生產環境&#xff08;.env.produc…

【學習筆記】計算機網絡(七)—— 網絡安全

第7章 網絡安全 文章目錄 第7章 網絡安全7.1 網絡安全問題概述7.1.1 計算機網絡面臨的安全性威脅7.1.2 安全的計算機網絡7.1.3 數據加密模型 7.2 兩類密碼體制7.2.1 對稱密鑰密碼體制7.2.2 公鑰密碼體制 7.3 鑒別7.3.1 報文鑒別7.3.2 實體鑒別 7.4 密鑰分配7.4.1 對稱密鑰的分配…