QSortFilterProxyModel strange behavior with QRegExp
-
In my constructor of mainwindow I have the following code:
[...] mFungusProxyModel = new QSortFilterProxyModel(); mFungusModel = mDatabaseManager->fungusTable(); //mFungusModel is a QSqlTableModel mFungusProxyModel->setSourceModel(mFungusModel); ui->listView->setModel(mFungusProxyModel); ui->listView->setModelColumn(1);
Now I have a QLineEdit that I want to use as a search bar. So I created the following function:
void MainWindow::on_searchbar_textChanged(const QString &name) { QRegExp regExp; ui->selection->setCurrentIndex(0); if(ui->selection->currentIndex() == 0) { mFungusProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); mFungusProxyModel->setFilterKeyColumn(1); if(name.contains(QRegExp("\\s"))) { QStringList word = name.split(QRegExp("\\s")); regExp.setPattern("^" + QRegExp::escape(word[0]) + "\\w*\\s" + QRegExp::escape(word[1]) + "\\w*"); mFungusProxyModel->setFilterRegExp(regExp.pattern()); } else { regExp.setPattern("^" + QRegExp::escape(name) + "\\w*"); QString myPattern = regExp.pattern(); mFungusProxyModel->setFilterRegExp(myPattern); } ui->listView->setModel(mFungusProxyModel); ui->listView->setModelColumn(1); } }
This works so far, except for the first search. As an example:
I want to search after the name "Boletus edulis Bulliard".Unfortunately, when I start my program now and enter "bol" nothing happens. However, if I enter "bol" + a space, i.e. "bol ", everything that starts with "bol" appears. If I enter "bol edu" it also works. If I then delete " edu" again, so that I only have "bol" again, everything that starts with "bol" also appears. Only if I enter "bol" to start the program, the search does not work.
Does anyone have an explanation for this?
-
Hi,
@Gabber said in QSortFilterProxyModel strange behavior with QRegExp:
ui->listView->setModel(mFungusProxyModel);
ui->listView->setModelColumn(1);Why not have these lines in the class constructor ?
@Gabber said in QSortFilterProxyModel strange behavior with QRegExp:
ui->selection->setCurrentIndex(0);
if(ui->selection->currentIndex() == 0)
{Any reason for that if to fail ?
Also note that QRegExp is deprecated in Qt 5 and removed in Qt 6.
On a side note, if you have regular expressions that you are going to reuse often you should declare them once rather than recreating them all the time.
-
Hi,
@Gabber said in QSortFilterProxyModel strange behavior with QRegExp:
ui->listView->setModel(mFungusProxyModel);
ui->listView->setModelColumn(1);Why not have these lines in the class constructor ?
@Gabber said in QSortFilterProxyModel strange behavior with QRegExp:
ui->selection->setCurrentIndex(0);
if(ui->selection->currentIndex() == 0)
{Any reason for that if to fail ?
Also note that QRegExp is deprecated in Qt 5 and removed in Qt 6.
On a side note, if you have regular expressions that you are going to reuse often you should declare them once rather than recreating them all the time.
I have this two lines in my constructor, too:
ui->listView->setModel(mFungusProxyModel); ui->listView->setModelColumn(1);
This
ui->selection->setCurrentIndex(0); if(ui->selection->currentIndex() == 0) {
works, too. Only the search function does not work at the first input until I type a blank, then it works.
What I want to implement is a search function, which search some like this:
bol edu -> returns "Boletus edulis bulliard"
art aus -> returns "Arthrinium austriacum Petrak"Do you understand how the search works?
bol -> returns every word with "Bol*"
bol edu -> returns every String with "Bol* edu*"
and so on.That is the function I want to implement with QRegExp and Q QSortFilterProxyModel. If you have a better idea you can tell me.
-
I understand what you want to achieve.
I would start by checking the regex that is generated the first time compared to the others.
By the way, is the proxy properly applied on top of the model it shall filter ?
On a side note, there's no need for the conversion back to string from QRegExp. QSortFilterProxyModel has overload that takes it.
You can also remove code duplication by moving the call to setFilterRegExp out of the if.
-
@SGaist said in QSortFilterProxyModel strange behavior with QRegExp:
By the way, is the proxy properly applied on top of the model it shall filter ?
How can I check this?
@SGaist said in QSortFilterProxyModel strange behavior with QRegExp:
On a side note, there's no need for the conversion back to string from QRegExp. QSortFilterProxyModel has overload that takes it.
You can also remove code duplication by moving the call to setFilterRegExp out of the if.Thanks for that tip. I have remove redundant code.
@SGaist said in QSortFilterProxyModel strange behavior with QRegExp:
I would start by checking the regex that is generated the first time compared to the others.
Some time ago I wrote this code, which gave me the desired result, so I don't understand why my regular expression doesn't give me any information on the first input:
QList<QListWidgetItem*> DatabaseManager::searchFungusNameList(const QString &name) const { QList<QListWidgetItem*> names; QSqlQuery query; QRegExp regExp; query.prepare("SELECT Pilze.Vollname, Pilze.ID FROM Pilze WHERE lower(Pilze.Vollname) REGEXP :regexp"); if(name.contains(QRegExp("\\s"))) { QStringList word = name.split(QRegExp("\\s")); regExp.setPattern("^" + QRegExp::escape(word[0].toLower()) + "\\w*\\s" + QRegExp::escape(word[1].toLower()) + "\\w*"); query.bindValue(":regexp", regExp.pattern()); } else { regExp.setPattern("^" + QRegExp::escape(name.toLower()) + "\\w*"); query.bindValue(":regexp", regExp.pattern()); } query.exec(); while (query.next()) { const QString fullname = query.value(0).toString(); const int id = query.value(1).toInt(); QListWidgetItem* item = new QListWidgetItem(fullname); item->setData(mIDRole, id); names.append(item); } return names; }
Btw. should I use QRegularExpression instead of QRegExp?
-
For the proxy setup, please show were you create and set it.
If you want to be future proof, then yes move to QRegularExpression.
There's something strange here.
We were talking about QSortFilterProxyModel and now there's database thrown in the mix without trace of the proxy but use of QListWidget.
-
@SGaist said in QSortFilterProxyModel strange behavior with QRegExp:
We were talking about QSortFilterProxyModel and now there's database thrown in the mix without trace of the proxy but use of QListWidget.
No, that was old code. I just wanted to show you that my regular expression should work, because it worked in this code section.
In my mainwindow.h I do this:
private: Ui::MainWindow *ui; QStringList *mSelection; SettingsManager *mSettingsManager; DatabaseManager *mDatabaseManager; QDataWidgetMapper *mFungusMapper; QDataWidgetMapper *mSynonymMapper; QDataWidgetMapper *mGermanMapper; QSqlTableModel *mFungusModel; QSqlTableModel *mSynonymModel; QSqlRelationalTableModel *mGermanModel; QSortFilterProxyModel *mFungusProxyModel; QSortFilterProxyModel *mSynonymProxyModel;
And my constructor of mainwindow.cpp I do this:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); mSettingsManager = new SettingsManager(); mDatabaseManager = new DatabaseManager(); mSelection = new QStringList {"Vollnamen", "Synonyme", "Deutsche Namen"}; const bool isStandardDatabasePathSet = mSettingsManager->isStandardDatabasePathSet(); ui->selection->addItems(*mSelection); if(isStandardDatabasePathSet) { const QString standardDatabase = mSettingsManager->loadStandardDatabase(); mDatabaseManager->openDatabase(standardDatabase); ui->selection->setCurrentText("Vollnamen"); mFungusMapper = new QDataWidgetMapper(); mSynonymMapper = new QDataWidgetMapper(); mGermanMapper = new QDataWidgetMapper(); mFungusProxyModel = new QSortFilterProxyModel(); mFungusModel = mDatabaseManager->fungusTable(); mSynonymModel = mDatabaseManager->synonymNameTable(); mGermanModel = mDatabaseManager->germanNameTable(); mFungusProxyModel->setSourceModel(mFungusModel); ui->listView->setModel(mFungusProxyModel); ui->listView->setModelColumn(1); mFungusMapper->setModel(mFungusProxyModel); mFungusMapper->addMapping(ui->number, 0, "text"); mFungusMapper->addMapping(ui->fullname, 1); mFungusMapper->addMapping(ui->genus, 2); mFungusMapper->addMapping(ui->kind, 3); mFungusMapper->addMapping(ui->family, 4); mFungusMapper->addMapping(ui->order, 5); mFungusMapper->addMapping(ui->noteTextfield, 6); } }
-
So to make things clear: at which point is it not working ?
On the filter proxy or when populating the models ?
-
I say (guess) at the filter proxy.
Let me try to explain it again. I have a QLineEdit that has the name "searchbar". When I type something in it, it should search the data from mFungusModel. For this I thought I use the QSortFilterProxyModel() (in my case it is called mFungusProxyModel)
I created a slot "textChanged(QString)" which in my case is called "on_searchbar_textChanged(const QString &name)". The code for this slot looks like this:
void MainWindow::on_searchbar_textChanged(const QString &name) { QRegularExpression regExp; regExp.setPatternOptions(QRegularExpression::CaseInsensitiveOption); ui->selection->setCurrentIndex(0); if(ui->selection->currentIndex() == 0) { mFungusProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); mFungusProxyModel->setFilterKeyColumn(1); if(name.contains(QRegExp("\\s"))) { QStringList word = name.split(QRegExp("\\s")); regExp.setPattern("^" + QRegExp::escape(word[0]) + "\\w*\\s" + QRegExp::escape(word[1]) + "\\w*"); } else { regExp.setPattern("^" + QRegExp::escape(name) + "\\w*");; } mFungusProxyModel->setFilterRegExp(regExp.pattern()); ui->listView->setModel(mFungusProxyModel); ui->listView->setModelColumn(1); } }
Now when I type "bol" into my search bar then my QListView (in my case it's just called listview) doesn't show me. But if I type "bol" with a space, i.e. "bol " then it shows me the desired results.
-
Please show the exact pattern you have.
Note that you should really use the proper overloads. The configuration you set on QRegExp or QRegularExpression like case sensitivity is not something that is translatable through the pattern itself.
-
My pattern looks like this:
[...] if(name.contains(QRegExp("\\s"))) { QStringList word = name.split(QRegExp("\\s")); regExp.setPattern("^" + QRegExp::escape(word[0]) + "\\w*\\s" + QRegExp::escape(word[1]) + "\\w*"); } else { regExp.setPattern("^" + QRegExp::escape(name) + "\\w*");; } [...]
The debugger shows following, if I type:
bol -> ^bol\w*
bol whit space like "bol " -> ^bol\w*\s\w*
bol edu -> ^bol\w*\sedu\w*@SGaist said in QSortFilterProxyModel strange behavior with QRegExp:
Note that you should really use the proper overloads.
Sorry I don't know what you mean. Please make an example for my code.
-
My pattern looks like this:
[...] if(name.contains(QRegExp("\\s"))) { QStringList word = name.split(QRegExp("\\s")); regExp.setPattern("^" + QRegExp::escape(word[0]) + "\\w*\\s" + QRegExp::escape(word[1]) + "\\w*"); } else { regExp.setPattern("^" + QRegExp::escape(name) + "\\w*");; } [...]
The debugger shows following, if I type:
bol -> ^bol\w*
bol whit space like "bol " -> ^bol\w*\s\w*
bol edu -> ^bol\w*\sedu\w*@SGaist said in QSortFilterProxyModel strange behavior with QRegExp:
Note that you should really use the proper overloads.
Sorry I don't know what you mean. Please make an example for my code.
@Gabber
I don't claim to know what is going on. And if you sayIf I then delete " edu" again, so that I only have "bol" again, everything that starts with "bol" also appears. Only if I enter "bol" to start the program, the search does not work.
that sounds odd, because you are claiming you end up with the same reg exp as you started with but different behaviour. Which sounds like a
QSortFilterProxyModel
rather than a reg exp thing.Just a couple of suggestions:
-
Try using
\S
in place of\w
. Any difference? -
Try using
.
, or even(.|\n)
, in place of\w
. Any difference? -
You are going to move over from
QRegExp
toQRegularExpression
, aren't you? Do so now. If by any chance it's aQRegExp
issue withQSortFilterProxyModel
which works withQRegularExpression
, it's not worth the effort to deal with.
-
-
@JonB said in QSortFilterProxyModel strange behavior with QRegExp:
Try using \S in place of \w. Any difference?
No difference.
@JonB said in QSortFilterProxyModel strange behavior with QRegExp:
Try using ., or even (.|\n), in place of \w. Any difference?
No difference.
@JonB said in QSortFilterProxyModel strange behavior with QRegExp:
You are going to move over from QRegExp to QRegularExpression, aren't you?
I switched to QRegularExrpession. Now my code looks like this:
void MainWindow::on_searchbar_textChanged(const QString &name) { QRegularExpression regularExpression; regularExpression.setPatternOptions(QRegularExpression::CaseInsensitiveOption); ui->selection->setCurrentIndex(0); if(ui->selection->currentIndex() == 0) { mFungusProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); mFungusProxyModel->setFilterKeyColumn(1); if(name.contains(QRegularExpression("\\s"))) { QStringList word = name.split(QRegularExpression("\\s")); regularExpression.setPattern("^" + QRegularExpression::escape(word[0]) + "\\w*\\s" + QRegularExpression::escape(word[1]) + "\\w*"); } else { regularExpression.setPattern("^" + QRegularExpression::escape(name) + "\\w*"); } mFungusProxyModel->setFilterRegularExpression(regularExpression); ui->listView->setModelColumn(1); ui->listView->setModel(mFungusProxyModel); } }
Here's what else I found out: If I enter "bole" instead of "bol" then the search starts and lists all names that start with "bole".
I suspect it is the QSortFilterProxyModel. Can I somehow say there I want to search for 3 letters? Unfortunately I didn't find anything in the documentation.
Do you have any other idea how I can implement this differently?
-
@JonB said in QSortFilterProxyModel strange behavior with QRegExp:
Try using \S in place of \w. Any difference?
No difference.
@JonB said in QSortFilterProxyModel strange behavior with QRegExp:
Try using ., or even (.|\n), in place of \w. Any difference?
No difference.
@JonB said in QSortFilterProxyModel strange behavior with QRegExp:
You are going to move over from QRegExp to QRegularExpression, aren't you?
I switched to QRegularExrpession. Now my code looks like this:
void MainWindow::on_searchbar_textChanged(const QString &name) { QRegularExpression regularExpression; regularExpression.setPatternOptions(QRegularExpression::CaseInsensitiveOption); ui->selection->setCurrentIndex(0); if(ui->selection->currentIndex() == 0) { mFungusProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); mFungusProxyModel->setFilterKeyColumn(1); if(name.contains(QRegularExpression("\\s"))) { QStringList word = name.split(QRegularExpression("\\s")); regularExpression.setPattern("^" + QRegularExpression::escape(word[0]) + "\\w*\\s" + QRegularExpression::escape(word[1]) + "\\w*"); } else { regularExpression.setPattern("^" + QRegularExpression::escape(name) + "\\w*"); } mFungusProxyModel->setFilterRegularExpression(regularExpression); ui->listView->setModelColumn(1); ui->listView->setModel(mFungusProxyModel); } }
Here's what else I found out: If I enter "bole" instead of "bol" then the search starts and lists all names that start with "bole".
I suspect it is the QSortFilterProxyModel. Can I somehow say there I want to search for 3 letters? Unfortunately I didn't find anything in the documentation.
Do you have any other idea how I can implement this differently?
@Gabber
Using the regular expression filter inQSortFilterProxyModel
is only a "convenience" for you, you don't have to use it. You can subclass and and implement your own code for virtual protected bool QSortFilterProxyModel::filterAcceptsRow() if you cannot get the regular expression to work right for some reason. -
The length of the expression should have no importance as long it's properly written.
Can you write a minimal compilable example that uses a QStandardItemModel with some suitable entries and your proxy setup so that we can test it ?
Note that you can also to some testing with the QRegularExpression tool from Qt's examples.
-
The length of the expression should have no importance as long it's properly written.
Can you write a minimal compilable example that uses a QStandardItemModel with some suitable entries and your proxy setup so that we can test it ?
Note that you can also to some testing with the QRegularExpression tool from Qt's examples.
@SGaist
I have made a very simple example that corresponds to what I want to search. If I take the QStandardItemModel my QRegularExpression also works as expected with QSortFilterProxyModel. Here is my code.main.cpp
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
mainwindow.cpp
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); ui->comboBox->addItem("Vollnamen"); ui->comboBox->setCurrentText("Vollnamen"); QStringList fungusList{ "Boletus aereus Bulliard ex Fr.", "Boletus betulicola (Vassilkov) Pilat & Dermek", "Boletus carpinaceus Velen.", "Boletus edulis Bulliard", "Boletus edulis var.arenarius Engel, Krglst. & Dermek", "Boletus edulis var.clavipes Peck", "Abortiporus biennis (Bull.) Singer", "Adustomyces spec.", "Euryachora spec", "Diaporthe strumella (Fries) Fuckel", "Cudonia spec." }; mFungusListModel = new QStandardItemModel(fungusList.count(),0); for(int row=0; row<mFungusListModel->rowCount(); row++){ QStandardItem *item = new QStandardItem(QString(fungusList.at(row))); mFungusListModel->setItem(row,0,item); } mFungusProxyModel = new QSortFilterProxyModel(); mFungusListModel->sort(0); mFungusProxyModel->setSourceModel(mFungusListModel); ui->listView->setModel(mFungusProxyModel); ui->listView->setModelColumn(0); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_lineEdit_textChanged(const QString &name) { QRegularExpression regularExpression; regularExpression.setPatternOptions(QRegularExpression::CaseInsensitiveOption); ui->comboBox->setCurrentIndex(0); if(ui->comboBox->currentIndex() == 0) { mFungusProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); mFungusProxyModel->setFilterKeyColumn(0); if(name.contains(QRegularExpression("\\s"))) { QStringList word = name.split(QRegularExpression("\\s")); regularExpression.setPattern("^" + QRegularExpression::escape(word[0]) + "\\w*\\s" + QRegularExpression::escape(word[1]) + "\\w*"); } else { regularExpression.setPattern("^" + QRegularExpression::escape(name) + "\\w*"); } mFungusProxyModel->setFilterRegularExpression(regularExpression); ui->listView->setModelColumn(0); ui->listView->setModel(mFungusProxyModel); } }
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QSortFilterProxyModel> #include <QStandardItemModel> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void on_lineEdit_textChanged(const QString &name); private: Ui::MainWindow *ui; QSortFilterProxyModel *mFungusProxyModel; QStandardItemModel *mFungusListModel; }; #endif // MAINWINDOW_H
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>424</width> <height>590</height> </rect> </property> <property name="windowTitle"> <string>MainWindow</string> </property> <widget class="QWidget" name="centralwidget"> <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QLineEdit" name="lineEdit"/> </item> <item> <widget class="QComboBox" name="comboBox"/> </item> <item> <widget class="QListView" name="listView"/> </item> </layout> </widget> <widget class="QMenuBar" name="menubar"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>424</width> <height>23</height> </rect> </property> </widget> <widget class="QStatusBar" name="statusbar"/> </widget> <resources/> <connections/> </ui>
What could be the problem now? Do you have another idea? Do you need any code to analyze the problem? I hope we can work this out somehow. The search function is the most important thing in the program.
Thanks already :)
-
@Gabber said in QSortFilterProxyModel strange behavior with QRegExp:
If I take the QStandardItemModel my QRegularExpression also works as expected with QSortFilterProxyModel.
Do you mean that with the QStandardItemModel the erroneous behaviour does not trigger ?
-
@Gabber said in QSortFilterProxyModel strange behavior with QRegExp:
If I take the QStandardItemModel my QRegularExpression also works as expected with QSortFilterProxyModel.
Do you mean that with the QStandardItemModel the erroneous behaviour does not trigger ?
@SGaist said in QSortFilterProxyModel strange behavior with QRegExp:
Do you mean that with the QStandardItemModel the erroneous behaviour does not trigger ?
Yes, that's what I mean. My search works exactly as I imagine it.
-
In that case, the problem might be with mFungusModel.
-
@SGaist
I have created a file DatabaseManager.cpp. The following function exists in it:QSqlTableModel *DatabaseManager::fungusTable() const { QSqlTableModel *model = new QSqlTableModel(); model->setTable("Pilze"); model->setSort(1, Qt::AscendingOrder); model->select(); return model; }
In my mainwindow.h I do this:
private: Ui::MainWindow *ui; DatabaseManager *mDatabaseManager; SettingsManager *mSettingsManager; QDataWidgetMapper *mFungusMapper; QSqlTableModel *mFungusModel;
And in my constructor in my mainwindow.cpp I assign the data from the DatabaseManager to mFungusModel.
[...] if(isStandardDatabasePathSet) { const QString standardDatabase = mSettingsManager->loadStandardDatabase(); mDatabaseManager->openDatabase(standardDatabase); ui->selection->setCurrentText("Vollnamen"); mFungusMapper = new QDataWidgetMapper(); mFungusProxyModel = new QSortFilterProxyModel(); mFungusModel = mDatabaseManager->fungusTable(); mFungusProxyModel->setSourceModel(mFungusModel); ui->listView->setModel(mFungusProxyModel); ui->listView->setModelColumn(1); } [...]
That's all what I'm doing in my constructor of mainwindow.cpp