Always select the first item in a QListView during a search
-
Hi,
I need your help. I have created a subclass of QSortFilterProxyModel with a custom filter which works very well. This proxymodel is set as model to my QListView. The code of the proxymodel looks like this:
fungussortfilterproxymodel.h
#ifndef FUNGUSSORTFILTERPROXYMODEL_H #define FUNGUSSORTFILTERPROXYMODEL_H #include <QSortFilterProxyModel> class FungusSortFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: explicit FungusSortFilterProxyModel(QObject *parent = nullptr); void setIdFilter(const int &id); void setNameFilter(const QString &name); bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; private: const int mIdColumn = 0; const int mNameColumn = 1; int mIdFilterValue = -1; QRegularExpression mRegularExpression; }; #endif // FUNGUSSORTFILTERPROXYMODEL_H
fungussortfilterproxymodel.cpp
#include "fungussortfilterproxymodel.h" FungusSortFilterProxyModel::FungusSortFilterProxyModel(QObject *parent): QSortFilterProxyModel{parent} {} void FungusSortFilterProxyModel::setIdFilter(const int &id) { if(mIdFilterValue != id) { mIdFilterValue = id; emit invalidateFilter(); } } void FungusSortFilterProxyModel::setNameFilter(const QString &name) { mRegularExpression.setPatternOptions(QRegularExpression::CaseInsensitiveOption); if(name.isEmpty()){ mRegularExpression.setPattern(QString()); } else if (name.contains(QRegularExpression("\\s"))) { QStringList word = name.split(QRegularExpression("\\s")); mRegularExpression.setPattern("^" + QRegularExpression::escape(word[0]) + "\\w*\\s" + QRegularExpression::escape(word[1]) + "\\w*"); } else { mRegularExpression.setPattern("^" + QRegularExpression::escape(name) + "\\w*"); } emit invalidateRowsFilter(); } bool FungusSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { if(!mRegularExpression.pattern().isEmpty()){ const QModelIndex index = sourceModel()->index(source_row, mNameColumn, source_parent); const QString name = sourceModel()->data(index).toString(); return (name.contains(mRegularExpression)); } if(mIdFilterValue != -1){ const int id = sourceModel()->index(source_row, mIdColumn).data().toInt(); return (id == mIdFilterValue); } else { return true; } }
In the next step, I want to highlight the first Item of QListView at programm start and during search via a QLineEdit. So I make a slot and connect the signal textChanged to my custom slot in the constructor of mainwindow.cpp.
connect(ui->searchbar, &QLineEdit::textChanged, this, &MainWindow::handleItemSelection);
My custom slot, called handleItemSelection looks like this:
void MainWindow::handleItemSelection(const QString &name) { Q_UNUSED(name); QModelIndex index; if(ui->modelSelection->currentIndex() == Model::Fullnames){ index = mFungusList->model()->index(0,1); mFungusList->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); } }
This works, too. So I connect the signal QItemSelectionModel::selectionChanged to my custom slot.
connect(mFungusList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::fungusSelectionChanged);
fungusSelectionChanged slot:
void MainWindow::fungusSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QModelIndex index = selected.indexes().first(); const int id = mFungusProxyModel->data(index.siblingAtColumn(0)).toInt(); const QString redlist = mFungusProxyModel->data(index.siblingAtColumn(7)).toString(); if(redlist.isEmpty()) ui->redlist->setCurrentIndex(-1); mSynonymFilterModel->setIdFilter(id); index = mFungusProxyModel->mapToSource(index); mMapper->setCurrentModelIndex(index); }
And this works too if I click the item in QListView with my mouse. But when I use the QLineEdit (search function for QListView) my programm crash at FungusSortFilterProxyModel at the position "emit invalidateRowsFilter()" and says:
In my qDebug() output qt creator says:
ASSERT: "!isEmpty()" in file /usr/include/qt6/QtCore/qlist.h, line 591I have also figured out that it must have something to do with this line. If I remove this line, the search works and my program does not crash, but I do not see the error.
QModelIndex index = selected.indexes().first();
However, if I omit this line then my QDataWidgetMapper (mMapper) no longer works....
Can someone please help me and tell me what I'm doing wrong?
-
Hi,
From the looks of it, you do not take into account the empty selection case.
-
@Gabber said in Always select the first item in a QListView during a search:
selected.indexes().first();
if(selected.indexes().isEmpy()) { // no selection at all } else { QModelIndex index = selected.indexes().first(); }
-
Something is still strange, maybe someone can give me a hint. In my MainWindow I have a QStackWidget with 3 QListViews. The three QListViews are selected via a QComboBox. I connect it in my mainwindow.cpp via:
connect(ui->modelSelection, QOverload<int>::of(&QComboBox::currentIndexChanged), ui->stackedListViews, &QStackedWidget::setCurrentIndex); connect(ui->searchbar, &QLineEdit::textChanged, this, &MainWindow::handleItemSelection); connect(ui->modelSelection, &QComboBox::currentTextChanged, this, &MainWindow::handleItemSelection); connect(mFungusList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::fungusSelectionChanged); connect(mSynonymList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::synonymSelectionChanged);
Yesterday I wrote that it works fine with my first QListView (index 0 of the QComboBox). But as soon as it selects the second QListView via the QComboBox, the search works, but it doesn't map me the data via the QDataWidgetMapper (mMapper) anymore. I even created a separate slot.
void MainWindow::synonymSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QModelIndex selectedIndex; QModelIndex proxyIndex; QModelIndex sourceIndex; if(selected.indexes().isEmpty()){ return; } else { selectedIndex = selected.indexes().first(); } const int id = mSynonymProxyModel->data(selectedIndex.siblingAtColumn(2)).toInt(); qDebug() << id; mFungusProxyModel->setIdFilter(id); mSynonymFilterModel->setIdFilter(id); proxyIndex = mFungusProxyModel->index(0,0); sourceIndex = mFungusProxyModel->mapToSource(proxyIndex); qDebug() << sourceIndex; const QString redlist = mFungusProxyModel->data(proxyIndex.siblingAtColumn(7)).toString(); qDebug() << redlist; if(redlist.isEmpty()) ui->redlist->setCurrentIndex(-1); mMapper->setCurrentModelIndex(sourceIndex); mFungusProxyModel->setIdFilter(-1); }
My complete handleItemSelection method looks like this:
void MainWindow::handleItemSelection(const QString &name) { Q_UNUSED(name); QModelIndex index; if(ui->modelSelection->currentIndex() == Model::Fullnames){ index = mFungusList->model()->index(0,1); mFungusList->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); } if(ui->modelSelection->currentIndex() == Model::Synonyms){ index = mSynonymList->model()->index(0,1); mSynonymList->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); } if(ui->modelSelection->currentIndex() == Model::GermanNames){ index = mGermanNameList->model()->index(0,0); mGermanNameList->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); } }
When I switch to synonyms in my QComboBox and select an entry via mouse click from the QListView it works as expected, when I try to select an entry via the search it does not recognize the data for me. My proxyIndex is then always QModelIndex(-1,-1,0x0,QObject(0x0)).
I have no idea what could be wrong. Does anyone have a tip for me again?
-
@Gabber said in Always select the first item in a QListView during a search:
when I try to select an entry via the search it does not recognize the data for me. My proxyIndex is then always QModelIndex(-1,-1,0x0,QObject(0x0)).
I'm not sure to understand what " to select an entry via the search" really means.
QModelIndex(-1,-1,0x0,QObject(0x0)).
Obviously, there's no selection, so you have to select one item by code I guess. -
@mpergand said in Always select the first item in a QListView during a search:
I'm not sure to understand what " to select an entry via the search" really means.
I have a QLineEdit that searches my data in my subclass QSortFilterProxyModel (in my case FungusSortFilterProxyModel) via the setNameFilter(QString) function using a Regular Expression. And after each search I want to select the first element in my second QListView (mSynonymList) and map the data via QDataWidgetmapper to the corresponding QLineEdits.
If I click it with the mouse everything works as expected, but if I tell it to select it (see handleItemSelection) unfortunately it doesn't work. But it is not clear to me why not....
I hope I could explain it a little bit...
-
@Gabber
Put a breakpoint at the beginning of handleItemSelection(),
run the debugger and step by step look what happens here. -
I think I have found my error.
//3 QListViews with 3 subclass QSortFilterProxyModel mFungusList->setModel(mFungusProxyModel); mSynonymList->setModel(mSynonymProxyModel); mGermanNameList->setModel(mGermanNameProxyModel);
I already said that I have my 3 QListViews in a QStack widget. If I now search the first ProxyModel (in my case mFungusProxyModel) and have no hit then select the second page of my QStackWidget and search the stored model (in my case mSynonynProxyModel). Since I have the following signal
connect(mSynonymList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::synonymSelectionChanged);
it accesses the slot SynonymSelectionChanged and exactly here it uses mFungusProxyModel which is "empty" since no hit was found and therefore I always get an empty QModelIndex.
I simply created a second model in my Constructor of MainWindow.cpp and added the source model twice.
mFungusProxyModel = new FungusSortFilterProxyModel; mFungusFilterModel = new FungusSortFilterProxyModel; // add source Model mFungusProxyModel->setSourceModel(mFungusDataModel); mFungusFilterModel->setSourceModel(mFungusDataModel);
@SGaist @mpergand
Now I just have a question for the experts, can you do something like that or can it cause complications?It is currently working.
-
@Gabber said in Always select the first item in a QListView during a search:
// add source Model
mFungusProxyModel->setSourceModel(mFungusDataModel);
mFungusFilterModel->setSourceModel(mFungusDataModel);Seems strange to me, but maybe i'm wrong - Model-View pattern gives me headache, too complicated for my little brain :)
In one of my app, i'm using a sorting model and i do like this:
historyModel=new HistoryModel(); historySortFilter= new HistorySortFilter(); historySortFilter->setSourceModel(historyModel); tableView->setModel(historySortFilter);
As you can see, i first set the proxy model to the sorting model,
then set the sorting model to the view.Hope this helps.