QComboBoxの複数選択


注目を集める絵
(おそらく投稿に関連)

QComboBoxウィジェットに複数の選択肢があると便利な場合があります。 この短いチュートリアルでは、その方法を示します。

主なアイデアは、QComboBoxで使用されるモデルの要素がQt :: ItemIsUserCheckableチェックボックスを上げる必要があるため、 フラグが立てられることです。 また、ウィジェット上のマークされたアイテムのリストも処理します。

MultiListWidgetクラスを宣言します(プロパティと対応するcheckedItemsメソッドは、以前に設定した要素またはユーザーがマークした要素のリストへのアクセスを提供し、collectCheckedItemsメソッドはマークされたモデル要素をmCheckedItemsに保存します):
class MultiListWidget : public QComboBox { Q_OBJECT Q_PROPERTY(QStringList checkedItems READ checkedItems WRITE setCheckedItems) public: MultiListWidget(); virtual ~MultiListWidget(); QStringList checkedItems() const; void setCheckedItems(const QStringList &items); private: QStringList mCheckedItems; void collectCheckedItems(); }; 

QComboBoxモデルにはいくつかの信号が必要です。

また、itemChanged(QStandardItem * item)も役立ちます。これは、フラグが設定またはチェック解除されたときに(ユーザーまたはプログラムによって)出力されます。

これらの信号のスロットを宣言します。
 private slots: void slotModelRowsInserted(const QModelIndex &parent, int start, int end); void slotModelRowsRemoved(const QModelIndex &parent, int start, int end); void slotModelItemChanged(QStandardItem *item); 

そして、シグナルをコンストラクターのスロットに関連付けます(model()はQAbstractItemModelへのポインターを返し、itemChangedシグナルはQStandardItemModelで発行されるため、ここでキャストが必要です)。
 MultiListWidget::MultiListWidget() { connect(model(), SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(slotModelRowsInserted(QModelIndex,int,int))); connect(model(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(slotModelRowsRemoved(QModelIndex,int,int))); QStandardItemModel *standartModel = qobject_cast<QStandardItemModel*>(model()); connect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*))); } MultiListWidget::~MultiListWidget() { } 

次に、checkedItems()およびsetCheckedItems(const QStringList&items)メソッドを実装します。
 QStringList MultiListWidget::checkedItems() const { return mCheckedItems; } void MultiListWidget::setCheckedItems(const QStringList &items) { //   QStandardItemModel *standartModel = qobject_cast<QStandardItemModel*>(model()); //   ,      disconnect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*))); for (int i = 0; i < items.count(); ++i) { //    int index = findText(items.at(i)); if (index != -1) { //    standartModel->item(index)->setData(Qt::Checked, Qt::CheckStateRole); } } //    connect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*))); //     collectCheckedItems(); } 

collectCheckedItems()メソッド内では、すべてが単純です-モデル要素を調べ、チェックされている場合はリストに追加します:
 void MultiListWidget::collectCheckedItems() { QStandardItemModel *standartModel = qobject_cast<QStandardItemModel*>(model()); mCheckedItems.clear(); for (int i = 0; i < count(); ++i) { QStandardItem *currentItem = standartModel->item(i); Qt::CheckState checkState = static_cast<Qt::CheckState>(currentItem->data(Qt::CheckStateRole).toInt()); if (checkState == Qt::Checked) { mCheckedItems.push_back(currentItem->text()); } } } 

モデルに新しい要素を挿入するとき、マークされたユーザーであり、最初はチェックされていないことを示す必要があります。
 void MultiListWidget::slotModelRowsInserted(const QModelIndex &parent, int start, int end) { //     (void)parent; QStandardItemModel *standartModel = qobject_cast<QStandardItemModel*>(model()); disconnect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*))); for (int i = start; i <= end; ++i) { standartModel->item(i)->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); standartModel->item(i)->setData(Qt::Unchecked, Qt::CheckStateRole); } connect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*))); } 

モデルからアイテムを削除する場合、mCheckedItemsからアイテムを削除する必要もあります。 collectCheckedItems()を使用します。
 void MultiListWidget::slotModelRowsRemoved(const QModelIndex &parent, int start, int end) { (void)parent; (void)start; (void)end; collectCheckedItems(); } 

スロットslotModelItemChanged(QStandardItem * item)で、マークされた要素を収集します。
 void MultiListWidget::slotModelItemChanged(QStandardItem *item) { (void)item; collectCheckedItems(); } 

クラス宣言とその実装をそれぞれmultilist.hとmultilist.cppに配置し、MultiListWidgetを実行します(ファイルmain.cpp):
 #include "multilist.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); MultiListWidget *multiList = new MultiListWidget(); multiList->addItems(QStringList() << "One" << "Two" << "Three" << "Four"); multiList->setCheckedItems(QStringList() << "One" << "Three"); QHBoxLayout *layout = new QHBoxLayout(); layout->addWidget(new QLabel("Select items:")); layout->addWidget(multiList, 1); QWidget widget; widget.setWindowTitle("MultiList example"); widget.setLayout(layout); widget.show(); return app.exec(); } 

悪くはありませんが、ウィジェットにマークされた要素のリストを表示することは残っています。 これを行うには、出力テキストを格納する変数、このテキストが描画される四角形のデルタ(以下に説明します)、およびマークされた要素のリストを変更するときに出力テキストを更新するメソッドをクラスで(閉じたセクションで)宣言します:
 QString mDisplayText; const QRect mDisplayRectDelta; void updateDisplayText(); 

mDisplayRectDeltaの初期化をコンストラクターに追加します。
 MultiListWidget::MultiListWidget() : mDisplayRectDelta(4, 1, -25, 0) { ... } 

それでは、updateDisplayText()を詳しく見てみましょう。
 void MultiListWidget::updateDisplayText() { //    , mDisplayRectDelta   ""  //   ,    ,   QRect textRect = rect().adjusted(mDisplayRectDelta.left(), mDisplayRectDelta.top(), mDisplayRectDelta.right(), mDisplayRectDelta.bottom()); QFontMetrics fontMetrics(font()); //   mDisplayText = mCheckedItems.join(", "); //      if (fontMetrics.size(Qt::TextSingleLine, mDisplayText).width() > textRect.width()) { //   ,       while (mDisplayText != "" && fontMetrics.size(Qt::TextSingleLine, mDisplayText + "...").width() > textRect.width()) { mDisplayText.remove(mDisplayText.length() - 1, 1); } //   mDisplayText += "..."; } } 

テキストを描画するには、仮想メソッドpaintEvent(QPaintEvent *イベント)をオーバーライドする必要があります。 また、ウィジェットのサイズが変更されるとテキストの境界が変更されるため、resizeEvent(QResizeEvent * event)メソッドをオーバーライドする必要があります。 これらのメソッドの宣言は次のとおりです。
 protected: virtual void paintEvent(QPaintEvent *event); virtual void resizeEvent(QResizeEvent *event); 

そしてその実装:
 void MultiListWidget::paintEvent(QPaintEvent *event) { (void)event; QStylePainter painter(this); painter.setPen(palette().color(QPalette::Text)); QStyleOptionComboBox option; initStyleOption(&option); //     painter.drawComplexControl(QStyle::CC_ComboBox, option); //     QRect textRect = rect().adjusted(mDisplayRectDelta.left(), mDisplayRectDelta.top(), mDisplayRectDelta.right(), mDisplayRectDelta.bottom()); //   painter.drawText(textRect, Qt::AlignVCenter, mDisplayText); } void MultiListWidget::resizeEvent(QResizeEvent *event) { (void)event; updateDisplayText(); } 

モデル要素のリストを変更した後、表示されたテキストを更新した後にのみ残ります。 collectCheckedItems()の最後にupdateDisplayText()への呼び出しを追加し、ウィジェットを再描画します。
 void MultiListWidget::setCheckedItems(const QStringList &items) { ... updateDisplayText(); repaint(); } 

GTKおよびMacのスタイルでは、展開されたリストのフラグが表示されないバグがあります。 このバグを回避するには、ウィジェットのstyleSheetにcombobox-popup値を設定する必要があります(コンストラクターにこのコードを配置します)。
 setStyleSheet("QComboBox { combobox-popup: 1px }"); 

画像:




ソースコード:

multilist.h
 #ifndef MULTILIST_H #define MULTILIST_H #include <QtGui> class MultiListWidget : public QComboBox { Q_OBJECT Q_PROPERTY(QStringList checkedItems READ checkedItems WRITE setCheckedItems) public: MultiListWidget(); virtual ~MultiListWidget(); QStringList checkedItems() const; void setCheckedItems(const QStringList &items); protected: virtual void paintEvent(QPaintEvent *event); virtual void resizeEvent(QResizeEvent *event); private: QStringList mCheckedItems; void collectCheckedItems(); QString mDisplayText; const QRect mDisplayRectDelta; void updateDisplayText(); private slots: void slotModelRowsInserted(const QModelIndex &parent, int start, int end); void slotModelRowsRemoved(const QModelIndex &parent, int start, int end); void slotModelItemChanged(QStandardItem *item); }; #endif // MULTILIST_H 


multilist.cpp
 #include "multilist.h" MultiListWidget::MultiListWidget() : mDisplayRectDelta(4, 1, -25, 0) { setStyleSheet("QComboBox { combobox-popup: 1px }"); connect(model(), SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(slotModelRowsInserted(QModelIndex,int,int))); connect(model(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(slotModelRowsRemoved(QModelIndex,int,int))); QStandardItemModel *standartModel = qobject_cast<QStandardItemModel*>(model()); connect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*))); } MultiListWidget::~MultiListWidget() { } QStringList MultiListWidget::checkedItems() const { return mCheckedItems; } void MultiListWidget::setCheckedItems(const QStringList &items) { QStandardItemModel *standartModel = qobject_cast<QStandardItemModel*>(model()); disconnect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*))); for (int i = 0; i < items.count(); ++i) { int index = findText(items.at(i)); if (index != -1) { standartModel->item(index)->setData(Qt::Checked, Qt::CheckStateRole); } } connect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*))); collectCheckedItems(); } void MultiListWidget::paintEvent(QPaintEvent *event) { (void)event; QStylePainter painter(this); painter.setPen(palette().color(QPalette::Text)); QStyleOptionComboBox option; initStyleOption(&option); painter.drawComplexControl(QStyle::CC_ComboBox, option); QRect textRect = rect().adjusted(mDisplayRectDelta.left(), mDisplayRectDelta.top(), mDisplayRectDelta.right(), mDisplayRectDelta.bottom()); painter.drawText(textRect, Qt::AlignVCenter, mDisplayText); } void MultiListWidget::resizeEvent(QResizeEvent *event) { (void)event; updateDisplayText(); } void MultiListWidget::collectCheckedItems() { QStandardItemModel *standartModel = qobject_cast<QStandardItemModel*>(model()); mCheckedItems.clear(); for (int i = 0; i < count(); ++i) { QStandardItem *currentItem = standartModel->item(i); Qt::CheckState checkState = static_cast<Qt::CheckState>(currentItem->data(Qt::CheckStateRole).toInt()); if (checkState == Qt::Checked) { mCheckedItems.push_back(currentItem->text()); } } updateDisplayText(); repaint(); } void MultiListWidget::updateDisplayText() { QRect textRect = rect().adjusted(mDisplayRectDelta.left(), mDisplayRectDelta.top(), mDisplayRectDelta.right(), mDisplayRectDelta.bottom()); QFontMetrics fontMetrics(font()); mDisplayText = mCheckedItems.join(", "); if (fontMetrics.size(Qt::TextSingleLine, mDisplayText).width() > textRect.width()) { while (mDisplayText != "" && fontMetrics.size(Qt::TextSingleLine, mDisplayText + "...").width() > textRect.width()) { mDisplayText.remove(mDisplayText.length() - 1, 1); } mDisplayText += "..."; } } void MultiListWidget::slotModelRowsInserted(const QModelIndex &parent, int start, int end) { (void)parent; QStandardItemModel *standartModel = qobject_cast<QStandardItemModel*>(model()); disconnect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*))); for (int i = start; i <= end; ++i) { standartModel->item(i)->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); standartModel->item(i)->setData(Qt::Unchecked, Qt::CheckStateRole); } connect(standartModel, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotModelItemChanged(QStandardItem*))); } void MultiListWidget::slotModelRowsRemoved(const QModelIndex &parent, int start, int end) { (void)parent; (void)start; (void)end; collectCheckedItems(); } void MultiListWidget::slotModelItemChanged(QStandardItem *item) { (void)item; collectCheckedItems(); } 


main.cpp
 #include "multilist.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); MultiListWidget *multiList = new MultiListWidget(); multiList->addItems(QStringList() << "One" << "Two" << "Three" << "Four"); multiList->setCheckedItems(QStringList() << "One" << "Three"); QHBoxLayout *layout = new QHBoxLayout(); layout->addWidget(new QLabel("Select items:")); layout->addWidget(multiList, 1); QWidget widget; widget.setWindowTitle("MultiList example"); widget.setLayout(layout); widget.show(); return app.exec(); } 


ご清聴ありがとうございました!

Source: https://habr.com/ru/post/J215289/


All Articles