Solved How to implement a custom matching function for a QComboBox's QCompleter
-
Hi :-)
I have a search function that is intended to be used when searching for names. It's a bit more advanced than
QString::contains()
, e. g. if you have one name in a list like "François René Müller", it will still find this as a match if you search for "mueller rene".Till now, I only used it to search in a
QListWidget
. I use aQLineEdit
, and when it's changed, I iterate through all entries of the listwidget and hide the ones that don't match the entered string.Now, I tried to implement this search function for a
QComboBox
. And this seems to be quite hard … essentially, I want what can be done if you docombobox->setEditable(true); combobox->setInsertPolicy(QComboBox::NoInsert); combobox->completer()->setCompletionMode(QCompleter::PopupCompletion); combobox->completer()->setFilterMode(Qt::MatchContains);
but with the difference that I the possible values shown are not chosen by
QString::contains()
(or whatever is used there), but by my function.I tried to work on the
QComboBox
's model directly and hide the non-matching items when something is entered and the list is opened then. This works for a few entries, but with many (>=100), the style doesn't work anymore: the list is only 5px in height or so and can't be used anymore. Most probably, this isn't intended to be done.I then tried to somehow implement a
QSortProxyModel
that is wrapped around theQComboBox
's model which selects the possible rows using my function, and let the completer show it, but I couldn't get it to work.Perhaps, I search on the wrong side. It seems to be quite hard to implement a custom completion function, that doesn't use the pre-defined matching functions. How do I start here? How can this be done? I would greatly appreciate some help! Thanks in advance for it.
-
I then tried to somehow implement a QSortProxyModel that is wrapped around the QComboBox's model which selects the possible rows using my function, and let the completer show it, but I couldn't get it to work.
I believe that was the correct route. A couple of the answers to https://stackoverflow.com/questions/5129211/qcompleter-custom-completion-rules show
QSortFilterProxyModel
in use. Also, in this forum https://forum.qt.io/topic/41123/solved-search-filter-inside-combobox-qcombobox/8 seems to use it to do what you're looking for? -
@JonB Thanks for the link! But as far as I can grasp it, it's about doing filtering at all in this case …
My problem is that I want to change how the completer searches for possible completions: By default, you can set it's behavior via
QCompleter::setFilterMode()
, where you can choose fromQt::MatchStartsWith
,Qt::MatchContains
andQt::MatchEndsWith
. As you would expect, one could generate the matches if one took the input and iterated over all items and did aQString::startsWith()
,QString::contains()
or aQString::endsWith()
on the item's text.What I'm trying to do is a custom matching function … I also saw the SO questions, but none of them really helped me …
-
Hi,
From a look at the QCompleter source, I don't see any easy way to do what you want. You would need a custom QCompletionEngine that does what you are describing.
WARNING: untested and requires using privates from Qt so it might break in any other release of Qt since the private parts offer no guarantees:
What you can try is:- subclass the private class QCompletionEngine and implement your filtering
- get the completion model from your QCompleter object
- cast it as a QCompletionModel (again, private class and use qobject_cast and check that the pointer is not null)
- set your custom engine on it.
Hope it helps
-
@SGaist Well, okay … at least it's not the case that it's simple and I just didn't get it ;-)
I don't want to mess with Qt's private parts for sure ;-) So … what about this:
Would it be possible to create a "dumb" completer, which does no filtering at all and simply shows everything it has? And doesn't share the model of the combobox? This way, I could create a QStringList or such with all matches for the currently entered string and pass it to the completer?
-
I found a solution :-)
I didn't even had to subclass anything for getting it to work. May not be the most performant implementation, but it works:
Edit: Setting a completer used this way on the
QComboBox
itself messes up timing with some signals (e. g.QComboBox::currentIndexChanged
is emitted twice, first with a bogus index and then with the correct one). Most probably, it will cause other problems too.But I managed to use it anyway by setting up the completer for the
QComboBox
'sQLineEdit
instead as follows:Here's the setup of the completer (m_playersSelect is a
QComboBox
):m_matchingNames = new QStringListModel(this); m_nameCompleter = new QCompleter(m_matchingNames, this); m_nameCompleter->setCompletionMode(QCompleter::UnfilteredPopupCompletion); m_playersSelect->setEditable(true); m_playersSelect->setInsertPolicy(QComboBox::NoInsert); m_playersSelect->setCompleter(0); m_playersSelect->lineEdit()->setCompleter(m_nameCompleter); connect(m_playersSelect->lineEdit(), &QLineEdit::textEdited, this, &ScorePage::nameSearchChanged);
And here's
ScorePage::nameSearchChanged
:void ScorePage::nameSearchChanged(const QString &text) { QStringList possibleNames; for (const QString &name : m_availableNames) { if (checkMatch(name, text)) { possibleNames << name; } } m_matchingNames->setStringList(possibleNames); }
Works like a charm ;-) Thanks for your help!
-
@l3u_ if your issue is solved, please don't forget to mark your post as such. Thanks.