MEMO
QComboBoxで複数選択に対応するサンプルですMultiSelectComboBox.h
#pragma once
#include <QMainWindow>
#include <QComboBox>
#include <QListWidget>
#include <QLineEdit>
#include <QCheckBox>
#include <QEvent>
#include <QTimer>
class MultiSelectComboBox : public QComboBox
{
Q_OBJECT
public:
MultiSelectComboBox(QWidget* aParent = nullptr) :
QComboBox(aParent),
mListWidget(new QListWidget(this)),
mLineEdit(new QLineEdit(this))
{
mLineEdit->setReadOnly(true);
mLineEdit->installEventFilter(this);
QStringList items = { "-", "Apple", "Banana", "Orane", "Peach" };
addItems(items);
setCurrentIndex(0); // "-"
mLineEdit->setPlaceholderText("-");
//setStyleSheet("QComboBox {"
// "background: white;"
// "border: none;"
// "padding: 2px 2px 2px 2px;" // 上右下左
// "}");
setModel(mListWidget->model());
setView(mListWidget);
setLineEdit(mLineEdit);
// チェックボックスとその文字以外の部分がクリックされた場合に実行される
connect(this, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, [=](int aIndex) // void itemClicked(int aIndex);
{
// 何かが選択されたときの処理
// 例:"-"以外のチェックをすべて外す、または、1つ以上チェックがあれば"-"の項目チェックを外す
if (aIndex == 0) { // "-"
ResetSelection();
}
else { // "-"以外
// チェックボックスとその文字以外の部分がクリックされた場合は自動的にチェックが変化しないので、ここで処理する。
QWidget* widget = mListWidget->itemWidget(mListWidget->item(aIndex));
QCheckBox *checkBox = static_cast<QCheckBox *>(widget);
bool checked = !checkBox->isChecked();
checkBox->setChecked(checked);
checkBox->setCheckState(checked ? Qt::Checked : Qt::Unchecked);
}
});
}
void addItem(const QString& aText, const QVariant& aUserData = QVariant())
{
Q_UNUSED(aUserData);
QListWidgetItem* listWidgetItem = new QListWidgetItem(mListWidget);
QCheckBox* checkBox = new QCheckBox(this);
checkBox->setText(aText);
mListWidget->addItem(listWidgetItem);
mListWidget->setItemWidget(listWidgetItem, checkBox);
// チェックボックスかその文字の部分がクリックされた場合に実行される
connect(checkBox, &QCheckBox::stateChanged, this, [=](int aState) // void stateChanged(int aState);
{
QWidget* widget;
QCheckBox *checkBox;
if ((aState == Qt::Checked) && (aText == "-")) {
for (int i = 1; i < mListWidget->count(); ++i) {
widget = mListWidget->itemWidget(mListWidget->item(i));
checkBox = static_cast<QCheckBox*>(widget);
checkBox->blockSignals(true);
checkBox->setChecked(false);
checkBox->setCheckState(Qt::Unchecked);
checkBox->blockSignals(false);
}
}
else {
bool checked = true;
// "-"以外に1つ以上チェックがあれば"-"の項目チェックを外す
for (int i = 1; i < mListWidget->count(); ++i) {
widget = mListWidget->itemWidget(mListWidget->item(i));
checkBox = static_cast<QCheckBox*>(widget);
if (checkBox->checkState() == Qt::Checked) {
checked = false;
break;
}
}
widget = mListWidget->itemWidget(mListWidget->item(0));
checkBox = static_cast<QCheckBox*>(widget);
checkBox->blockSignals(true);
checkBox->setChecked(checked);
checkBox->setCheckState(checked ? Qt::Checked : Qt::Unchecked);
checkBox->blockSignals(false);
}
QString selectedData("");
for (int i = 1; i < mListWidget->count(); ++i) {
widget = mListWidget->itemWidget(mListWidget->item(i));
checkBox = static_cast<QCheckBox *>(widget);
if (checkBox->isChecked()) {
selectedData.append(checkBox->text()).append(",");
}
}
if (selectedData.endsWith(",")) { // 行末の","を除去
selectedData.remove(selectedData.length() - 1, 1);
}
if (selectedData.isEmpty()) {
selectedData.append("-");
}
mLineEdit->setText(selectedData);
mLineEdit->setToolTip(selectedData);
emit selectionChanged();
});
}
void addItems(const QStringList& aTexts)
{
for(const auto& string : aTexts) {
addItem(string);
}
}
QList<int> currentIndex() {
QList<int> indexList;
for (int i = 0; i < mListWidget->count(); ++i) {
QWidget* widget = mListWidget->itemWidget(mListWidget->item(i));
QCheckBox* checkBox = static_cast<QCheckBox*>(widget);
if (checkBox->isChecked()) {
indexList.append(i);
}
}
return indexList;
}
void setCurrentIndex(QList<int> indexList) {
for (int i = 0; i < mListWidget->count(); ++i) {
QWidget* widget = mListWidget->itemWidget(mListWidget->item(i));
QCheckBox* checkBox = static_cast<QCheckBox*>(widget);
bool checked = false;
if(indexList.contains(i)) {
checked = true;
}
checkBox->setChecked(checked);
checkBox->setCheckState(checked ? Qt::Checked : Qt::Unchecked);
}
}
void setCurrentIndex(int index) {
QList<int> indexList;
indexList.append(index);
setCurrentIndex(indexList);
}
QStringList currentText()
{
QStringList emptyStringList;
if(!mLineEdit->text().isEmpty()) {
emptyStringList = mLineEdit->text().split(',');
}
return emptyStringList;
}
void setCurrentText(const QString& aText) { Q_UNUSED(aText); }
void setCurrentText(const QStringList& aText)
{
for (int i = 1; i < mListWidget->count(); ++i) {
QWidget* widget = mListWidget->itemWidget(mListWidget->item(i));
QCheckBox* checkBox = static_cast<QCheckBox*>(widget);
QString checkBoxString = checkBox->text();
if(aText.contains(checkBoxString)) {
checkBox->setChecked(true);
checkBox->setCheckState(Qt::Checked);
}
}
}
signals:
void selectionChanged();
void closedPopup();
protected:
void wheelEvent(QWheelEvent* aWheelEvent) override { Q_UNUSED(aWheelEvent); } // Do not handle the wheel event
bool eventFilter(QObject* aObject, QEvent* aEvent) override
{
if(aObject == mLineEdit && aEvent->type() == QEvent::MouseButtonRelease) {
showPopup();
return false;
}
return false;
}
void keyPressEvent(QKeyEvent* aEvent) override { Q_UNUSED(aEvent); } // Do not handle key event
private:
QListWidget* mListWidget;
QLineEdit* mLineEdit;
void ResetSelection()
{
// いったんすべてのチェックを外す
for (int i = 0; i < mListWidget->count(); ++i) {
QWidget* widget = mListWidget->itemWidget(mListWidget->item(i));
QCheckBox* checkBox = static_cast<QCheckBox*>(widget);
checkBox->blockSignals(true);
checkBox->setChecked(false);
checkBox->setCheckState(Qt::Unchecked);
checkBox->blockSignals(false);
}
// "-"のチェックを付ける
QWidget *widget = mListWidget->itemWidget(mListWidget->item(0));
QCheckBox *checkBox = static_cast<QCheckBox *>(widget);
checkBox->setChecked(true);
checkBox->setCheckState(Qt::Checked);
// ここで stateChanged が呼ばれる
}
void hidePopup() override
{
int width = this->width();
int height = mListWidget->height();
int x = QCursor::pos().x() - mapToGlobal(geometry().topLeft()).x() + geometry().x();
int y = QCursor::pos().y() - mapToGlobal(geometry().topLeft()).y() + geometry().y();
if (x >= 0 && x <= width && y >= this->height() && y <= height + this->height()) {
// Item was clicked, do not hide popup
}
else {
bool enabled = QComboBox::isEnabled();
if (!enabled) {
// enabledがtrueからfalseになる時、無駄にイベント発生するので除外する
return;
}
// コンボボックスが閉じる時の通知
emit closedPopup();
// コンボボックスを閉じる
QComboBox::hidePopup();
QTimer::singleShot(0, this, [=]() {
// ADD コンボボックスが閉じた後の処理
});
}
}
};
mainwindow.cpp
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "MultiSelectComboBox.h"
#include <QPalette>
#include <QColor>
#include <QTableWidget>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 背景色
QPalette pal = palette();
QColor color = QColor(255, 255, 192, 255);
pal.setColor(QPalette::Window, color); // QPallete::Window -> background
this->setAutoFillBackground(true);
this->setPalette(pal);
this->show();
// アイテム追加
ui->multiSelectComboBox->addItem("Kiwi");
ui->multiSelectComboBox->addItem("Grape");
ui->multiSelectComboBox->addItem("Mango");
ui->multiSelectComboBox->setFixedWidth(100);
QTableWidget *t = ui->tableWidget;
int column = 1;
// Qt Designerで作った表の2列目各セルに、コンボボックスを設定する
for (int row = 0; row < t->rowCount(); row++) {
QComboBox *comboBox = new QComboBox();
comboBox->addItem("-");
comboBox->addItem("Kiwi");
comboBox->addItem("Grape");
comboBox->addItem("Mango");
t->setCellWidget(row, column, comboBox);
// コンボボックスが閉じる時の処理
connect(comboBox, &QComboBox::currentIndexChanged, this, [=](int index) {
QString text = comboBox->currentText();
qDebug() << Q_FUNC_INFO << "row:" << row << "column:" << column << index << text;
});
}
// Qt Designerで作った表の3列目各セルに、複数選択対応のコンボボックスを設定する
column = 2; // 3列目に設定する
for (int row = 0; row < t->rowCount(); row++) {
MultiSelectComboBox *comboBox = new MultiSelectComboBox();
t->setCellWidget(row, column, comboBox);
// コンボボックスが閉じる時の処理
connect(comboBox, &MultiSelectComboBox::closedPopup, this, [=]() {
// 現在チェックされているインデックスのリストを取得
QList<int> indexList = comboBox->currentIndex();
QStringList textList = comboBox->currentText();
qDebug() << Q_FUNC_INFO << "row:" << row << "column:" << column << indexList << textList;
// ADD 幅の調整とか
});
}
}
MainWindow::~MainWindow()
{
delete ui;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(MultiSelectComboBox VERSION 0.1 LANGUAGES CXX)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
set(PROJECT_SOURCES
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
MultiSelectComboBox.h
)