QLineEdit shows different text than set by setText() in checkable QComboBox for special case
-
Hello,
the app below works fine except for one special case. All debugging points to QLineEdit's setText() function, which works fine but silently fails in one and only one case. QLineEdit's text() function ALWAYS returns correct text (even for the problematic case). However, incorrect text is rendered in the problematic case.
The app resp. Minimal Working Example consists of one main window with one QComboBox. The combo box has few checkable items. Also, the combo box has been made editable, thus owns a QLineEdit.
The QLineEdit shows 3 possible labels "All", "Filtered" and "None" based on checked items. Its read only and changed only programatically.
The checkable combo box items follow classic filter logic:
- first item labeled "All" has 3 states (checked, partially checked, unchecked), all other items have 2-states (checked, unchecked)
- when all 2-state items are checked by user, the program updates item "All" to checked state and shows "All" label
- when all 2-state items are unchecked by user, the program updates item "All" to unchecked state and shows "None" label
- when at least one 2-state item is checked and one 2-state item is unchecked, the program sets item "All" as partially checked and shows "Filtered" label
- when user checks item "All", the program updates all 2-state items to checked state and shows "All" label
- when user unchecks item "All", the program updates all 2-state items to unchecked state and shows label "None"
The last case does NOT work.
Precondition: all checkboxes of the combo box are checked
Action: user unchecks item "All"
Expected result: QLineEdit shows "None" and all items are unchecked
Actual result: QLineEdit shows "All" and all items are uncheckedOS Linux Mint 22, Qt version 6.8.2
I have tried all my debugging tricks. Narrowed it down in the example. Googled for days. Nothing
Any help is appreciated
mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>MainWindow</class> <widget class="QMainWindow" name="MainWindow"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>800</width> <height>600</height> </rect> </property> <property name="windowTitle"> <string>MainWindow</string> </property> <widget class="QWidget" name="centralwidget"> <widget class="QComboBox" name="comboBox"> <property name="geometry"> <rect> <x>240</x> <y>150</y> <width>211</width> <height>27</height> </rect> </property> <property name="styleSheet"> <string notr="true">QCheckBox::indicator { width: 40px; height: 40px; }; QComboBox::indicator { width: 40px; height: 40px; }; </string> </property> <item> <property name="text"> <string>Item1</string> </property> </item> <item> <property name="text"> <string>Item2</string> </property> </item> </widget> </widget> <widget class="QMenuBar" name="menubar"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>800</width> <height>20</height> </rect> </property> </widget> <widget class="QStatusBar" name="statusbar"/> </widget> <resources/> <connections/> </ui>
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QStandardItemModel> #include "comboboxfiltermodel.h" QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); public slots: void slot_changed(const QModelIndex&, const QModelIndex&); protected: ComboBoxFilterModel* Model; private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H
mainwindow.cpp
#include <QtGui> #include "mainwindow.h" #include "./ui_mainwindow.h" #include "comboboxfiltermodel.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); Model = new ComboBoxFilterModel(); connect(Model, SIGNAL(dataChanged ( const QModelIndex&, const QModelIndex&)), this, SLOT(slot_changed(const QModelIndex&, const QModelIndex&))); Model->setFilterWidget(ui->comboBox); Model->updateFilterModel(); Model->selectAllChannels(); } MainWindow::~MainWindow() { delete ui; } void MainWindow::slot_changed(const QModelIndex& topLeft, const QModelIndex& bottomRight) { Model->updateFilterCheckStates(topLeft.row()); // true because change coming from user }
comboboxfiltermodel.h
#ifndef COMBOBOXFILTERMODEL_H #define COMBOBOXFILTERMODEL_H #include <QObject> #include <QStandardItemModel> #include <QComboBox> class ComboBoxFilterModel : public QStandardItemModel { Q_OBJECT public: ComboBoxFilterModel(); QComboBox* filterComboBox; std::vector<QString*> dataProvider; void setFilterWidget(QComboBox* filterCMB); void updateFilterModel(); void updateFilterCheckStates(int itemRow = -1); void automaticAllByRemaining(); void setCustomText(const QString newText); void selectAllChannels(); }; #endif // COMBOBOXFILTERMODEL_H
comboboxfiltermodel.cpp
#include <QLineEdit> #include "comboboxfiltermodel.h" #include "subclassofqstyleditemdelegate.h" #define INDEX_OF_ITEM_ALL 0 ComboBoxFilterModel::ComboBoxFilterModel() { filterComboBox = nullptr; dataProvider.push_back(new QString("First Item")); dataProvider.push_back(new QString("Second Item")); dataProvider.push_back(new QString("Third Item")); } void ComboBoxFilterModel::selectAllChannels(){ // select all channels coming from provided BLF file for(unsigned int i = 1; i < rowCount(); i++){ QStandardItem* thisItem = item(i); thisItem->setCheckState(Qt::Checked); } }; void ComboBoxFilterModel::setCustomText(const QString newText){ QLineEdit* newLineEdit = filterComboBox->lineEdit(); if(newLineEdit == nullptr) return; newLineEdit->setText(newText); newLineEdit->setReadOnly(true); filterComboBox->setLineEdit(newLineEdit); } void ComboBoxFilterModel::automaticAllByRemaining(){ // (de)select item "All", based on remaining items // if all remaining items are checked, check "All" item // if all remaining items are unchecked, uncheck "All" item // if at least one remaining item is unchecked and one checked, set partially check on "All" item if(rowCount() == 0) return; bool allChecked = true; bool allUnchecked = true; QStandardItem* itemAll = item(INDEX_OF_ITEM_ALL); for(int i = 1; i < rowCount(); i++){ // skip first special "ALL" item QStandardItem* it = item(i); if(it->checkState() == Qt::Checked) allUnchecked = false; if(it->checkState() == Qt::Unchecked) allChecked = false; } if(allChecked == true){ itemAll->setCheckState(Qt::Checked); setCustomText("All"); } if(allUnchecked == true){ itemAll->setCheckState(Qt::Unchecked); setCustomText("None"); } if(allChecked == false && allUnchecked == false){ itemAll->setCheckState(Qt::PartiallyChecked); setCustomText("Filtered"); } } void ComboBoxFilterModel::setFilterWidget(QComboBox* filterCMB){ filterComboBox = filterCMB; if(filterComboBox != nullptr){ filterComboBox->setEditable(true); filterComboBox->lineEdit()->setReadOnly(true); filterComboBox->setModel(this); SubclassOfQStyledItemDelegate *delegate = new SubclassOfQStyledItemDelegate(); filterComboBox->setItemDelegate(delegate); } } void ComboBoxFilterModel::updateFilterCheckStates(int itemRow){ // synchronize file filter check statuses with internal flags and all filter rules // expects changeFromComboBox true when change coming from user interaction with ComboBox // expcts changeFromComboBox false when change coming from other program component // expects itemRow pointing to single changed item or -1 to reload all if(rowCount() == 0) return; if(itemRow >= 0){ QStandardItem* currentItem = item(itemRow); if(currentItem->text() == "All"){ if(currentItem->checkState() == Qt::Checked){ setCustomText("All"); for(int i = 1; i < rowCount(); i++){ QStandardItem* it = item(i); it->setCheckState(Qt::Checked); } } if(currentItem->checkState() == Qt::Unchecked){ filterComboBox->lineEdit()->clear(); setCustomText("None"); for(int i = 1; i < rowCount(); i++){ QStandardItem* it = item(i); it->setCheckState(Qt::Unchecked); } } }else if(currentItem->text() != "All"){ automaticAllByRemaining(); } } } void ComboBoxFilterModel::updateFilterModel(){ // ensure Filter Model corresponds to current data from dataProvider // validity assumption if(filterComboBox == nullptr) return; // ensure empty list if no data available if(dataProvider.size() == 0){ clear(); filterComboBox->setDisabled(true); return; } filterComboBox->setDisabled(false); // add special item "All" if missing if(findItems("All").size() == 0){ QStandardItem* item = new QStandardItem(); item->setText("All"); item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); item->setUserTristate(false); item->setData(Qt::Unchecked, Qt::CheckStateRole); insertRow(INDEX_OF_ITEM_ALL, item); } for(unsigned long i = 0; i < dataProvider.size(); i++){ QString itemName = (*dataProvider[i]); QStandardItem* item = new QStandardItem(); item->setText(itemName); item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); item->setData(Qt::Unchecked, Qt::CheckStateRole); insertRow(i+1, item); } }
subclassofqstyleditemdelegate.h
#ifndef SUBCLASSOFQSTYLEDITEMDELEGATE_H #define SUBCLASSOFQSTYLEDITEMDELEGATE_H #include <QStyledItemDelegate> class SubclassOfQStyledItemDelegate : public QStyledItemDelegate { public: SubclassOfQStyledItemDelegate(); void paint(QPainter * painter_, const QStyleOptionViewItem & option_, const QModelIndex & index_) const override; }; #endif // SUBCLASSOFQSTYLEDITEMDELEGATE_H
subclassofqstyleditemdelegate.cpp
#include "subclassofqstyleditemdelegate.h" SubclassOfQStyledItemDelegate::SubclassOfQStyledItemDelegate() {} void SubclassOfQStyledItemDelegate::paint(QPainter * painter_, const QStyleOptionViewItem & option_, const QModelIndex & index_) const { QStyleOptionViewItem & refToNonConstOption = const_cast<QStyleOptionViewItem &>(option_); refToNonConstOption.showDecorationSelected = false; QStyledItemDelegate::paint(painter_, refToNonConstOption, index_); }
main.cpp
#include "mainwindow.h" #include <QApplication> #include <QMetaMethod> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
-
Hello,
the app below works fine except for one special case. All debugging points to QLineEdit's setText() function, which works fine but silently fails in one and only one case. QLineEdit's text() function ALWAYS returns correct text (even for the problematic case). However, incorrect text is rendered in the problematic case.
The app resp. Minimal Working Example consists of one main window with one QComboBox. The combo box has few checkable items. Also, the combo box has been made editable, thus owns a QLineEdit.
The QLineEdit shows 3 possible labels "All", "Filtered" and "None" based on checked items. Its read only and changed only programatically.
The checkable combo box items follow classic filter logic:
- first item labeled "All" has 3 states (checked, partially checked, unchecked), all other items have 2-states (checked, unchecked)
- when all 2-state items are checked by user, the program updates item "All" to checked state and shows "All" label
- when all 2-state items are unchecked by user, the program updates item "All" to unchecked state and shows "None" label
- when at least one 2-state item is checked and one 2-state item is unchecked, the program sets item "All" as partially checked and shows "Filtered" label
- when user checks item "All", the program updates all 2-state items to checked state and shows "All" label
- when user unchecks item "All", the program updates all 2-state items to unchecked state and shows label "None"
The last case does NOT work.
Precondition: all checkboxes of the combo box are checked
Action: user unchecks item "All"
Expected result: QLineEdit shows "None" and all items are unchecked
Actual result: QLineEdit shows "All" and all items are uncheckedOS Linux Mint 22, Qt version 6.8.2
I have tried all my debugging tricks. Narrowed it down in the example. Googled for days. Nothing
Any help is appreciated
mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>MainWindow</class> <widget class="QMainWindow" name="MainWindow"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>800</width> <height>600</height> </rect> </property> <property name="windowTitle"> <string>MainWindow</string> </property> <widget class="QWidget" name="centralwidget"> <widget class="QComboBox" name="comboBox"> <property name="geometry"> <rect> <x>240</x> <y>150</y> <width>211</width> <height>27</height> </rect> </property> <property name="styleSheet"> <string notr="true">QCheckBox::indicator { width: 40px; height: 40px; }; QComboBox::indicator { width: 40px; height: 40px; }; </string> </property> <item> <property name="text"> <string>Item1</string> </property> </item> <item> <property name="text"> <string>Item2</string> </property> </item> </widget> </widget> <widget class="QMenuBar" name="menubar"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>800</width> <height>20</height> </rect> </property> </widget> <widget class="QStatusBar" name="statusbar"/> </widget> <resources/> <connections/> </ui>
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QStandardItemModel> #include "comboboxfiltermodel.h" QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); public slots: void slot_changed(const QModelIndex&, const QModelIndex&); protected: ComboBoxFilterModel* Model; private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H
mainwindow.cpp
#include <QtGui> #include "mainwindow.h" #include "./ui_mainwindow.h" #include "comboboxfiltermodel.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); Model = new ComboBoxFilterModel(); connect(Model, SIGNAL(dataChanged ( const QModelIndex&, const QModelIndex&)), this, SLOT(slot_changed(const QModelIndex&, const QModelIndex&))); Model->setFilterWidget(ui->comboBox); Model->updateFilterModel(); Model->selectAllChannels(); } MainWindow::~MainWindow() { delete ui; } void MainWindow::slot_changed(const QModelIndex& topLeft, const QModelIndex& bottomRight) { Model->updateFilterCheckStates(topLeft.row()); // true because change coming from user }
comboboxfiltermodel.h
#ifndef COMBOBOXFILTERMODEL_H #define COMBOBOXFILTERMODEL_H #include <QObject> #include <QStandardItemModel> #include <QComboBox> class ComboBoxFilterModel : public QStandardItemModel { Q_OBJECT public: ComboBoxFilterModel(); QComboBox* filterComboBox; std::vector<QString*> dataProvider; void setFilterWidget(QComboBox* filterCMB); void updateFilterModel(); void updateFilterCheckStates(int itemRow = -1); void automaticAllByRemaining(); void setCustomText(const QString newText); void selectAllChannels(); }; #endif // COMBOBOXFILTERMODEL_H
comboboxfiltermodel.cpp
#include <QLineEdit> #include "comboboxfiltermodel.h" #include "subclassofqstyleditemdelegate.h" #define INDEX_OF_ITEM_ALL 0 ComboBoxFilterModel::ComboBoxFilterModel() { filterComboBox = nullptr; dataProvider.push_back(new QString("First Item")); dataProvider.push_back(new QString("Second Item")); dataProvider.push_back(new QString("Third Item")); } void ComboBoxFilterModel::selectAllChannels(){ // select all channels coming from provided BLF file for(unsigned int i = 1; i < rowCount(); i++){ QStandardItem* thisItem = item(i); thisItem->setCheckState(Qt::Checked); } }; void ComboBoxFilterModel::setCustomText(const QString newText){ QLineEdit* newLineEdit = filterComboBox->lineEdit(); if(newLineEdit == nullptr) return; newLineEdit->setText(newText); newLineEdit->setReadOnly(true); filterComboBox->setLineEdit(newLineEdit); } void ComboBoxFilterModel::automaticAllByRemaining(){ // (de)select item "All", based on remaining items // if all remaining items are checked, check "All" item // if all remaining items are unchecked, uncheck "All" item // if at least one remaining item is unchecked and one checked, set partially check on "All" item if(rowCount() == 0) return; bool allChecked = true; bool allUnchecked = true; QStandardItem* itemAll = item(INDEX_OF_ITEM_ALL); for(int i = 1; i < rowCount(); i++){ // skip first special "ALL" item QStandardItem* it = item(i); if(it->checkState() == Qt::Checked) allUnchecked = false; if(it->checkState() == Qt::Unchecked) allChecked = false; } if(allChecked == true){ itemAll->setCheckState(Qt::Checked); setCustomText("All"); } if(allUnchecked == true){ itemAll->setCheckState(Qt::Unchecked); setCustomText("None"); } if(allChecked == false && allUnchecked == false){ itemAll->setCheckState(Qt::PartiallyChecked); setCustomText("Filtered"); } } void ComboBoxFilterModel::setFilterWidget(QComboBox* filterCMB){ filterComboBox = filterCMB; if(filterComboBox != nullptr){ filterComboBox->setEditable(true); filterComboBox->lineEdit()->setReadOnly(true); filterComboBox->setModel(this); SubclassOfQStyledItemDelegate *delegate = new SubclassOfQStyledItemDelegate(); filterComboBox->setItemDelegate(delegate); } } void ComboBoxFilterModel::updateFilterCheckStates(int itemRow){ // synchronize file filter check statuses with internal flags and all filter rules // expects changeFromComboBox true when change coming from user interaction with ComboBox // expcts changeFromComboBox false when change coming from other program component // expects itemRow pointing to single changed item or -1 to reload all if(rowCount() == 0) return; if(itemRow >= 0){ QStandardItem* currentItem = item(itemRow); if(currentItem->text() == "All"){ if(currentItem->checkState() == Qt::Checked){ setCustomText("All"); for(int i = 1; i < rowCount(); i++){ QStandardItem* it = item(i); it->setCheckState(Qt::Checked); } } if(currentItem->checkState() == Qt::Unchecked){ filterComboBox->lineEdit()->clear(); setCustomText("None"); for(int i = 1; i < rowCount(); i++){ QStandardItem* it = item(i); it->setCheckState(Qt::Unchecked); } } }else if(currentItem->text() != "All"){ automaticAllByRemaining(); } } } void ComboBoxFilterModel::updateFilterModel(){ // ensure Filter Model corresponds to current data from dataProvider // validity assumption if(filterComboBox == nullptr) return; // ensure empty list if no data available if(dataProvider.size() == 0){ clear(); filterComboBox->setDisabled(true); return; } filterComboBox->setDisabled(false); // add special item "All" if missing if(findItems("All").size() == 0){ QStandardItem* item = new QStandardItem(); item->setText("All"); item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); item->setUserTristate(false); item->setData(Qt::Unchecked, Qt::CheckStateRole); insertRow(INDEX_OF_ITEM_ALL, item); } for(unsigned long i = 0; i < dataProvider.size(); i++){ QString itemName = (*dataProvider[i]); QStandardItem* item = new QStandardItem(); item->setText(itemName); item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); item->setData(Qt::Unchecked, Qt::CheckStateRole); insertRow(i+1, item); } }
subclassofqstyleditemdelegate.h
#ifndef SUBCLASSOFQSTYLEDITEMDELEGATE_H #define SUBCLASSOFQSTYLEDITEMDELEGATE_H #include <QStyledItemDelegate> class SubclassOfQStyledItemDelegate : public QStyledItemDelegate { public: SubclassOfQStyledItemDelegate(); void paint(QPainter * painter_, const QStyleOptionViewItem & option_, const QModelIndex & index_) const override; }; #endif // SUBCLASSOFQSTYLEDITEMDELEGATE_H
subclassofqstyleditemdelegate.cpp
#include "subclassofqstyleditemdelegate.h" SubclassOfQStyledItemDelegate::SubclassOfQStyledItemDelegate() {} void SubclassOfQStyledItemDelegate::paint(QPainter * painter_, const QStyleOptionViewItem & option_, const QModelIndex & index_) const { QStyleOptionViewItem & refToNonConstOption = const_cast<QStyleOptionViewItem &>(option_); refToNonConstOption.showDecorationSelected = false; QStyledItemDelegate::paint(painter_, refToNonConstOption, index_); }
main.cpp
#include "mainwindow.h" #include <QApplication> #include <QMetaMethod> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
Hi and welcome!
Just by reading your problem description: The bug is in your code.
In the last case, the user clicks on the All-checkbox. You intercept the click and set the current text to "None" I guess. When that's done, the checkbox processes the click as well and interprets as a selection of the item "All". That sets the current text back to "All".If you add
connect(ui->comboBox, &QComboBox::currentTextChanged, this, [](const QString &str){ qDebug() << "Text changed to:" << str; });
somewhere e.g. in the c'tor of
MainWindow
, I bet you will see, that the current text is set to "None" before it bounces back to "All".Narrowed it down in the example.
Nope. Way too complicated. See here.