Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

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 a QLineEdit, 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 do

    combobox->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 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.

    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.



  • @l3u_

    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 from Qt::MatchStartsWith, Qt::MatchContains and Qt::MatchEndsWith. As you would expect, one could generate the matches if one took the input and iterated over all items and did a QString::startsWith(), QString::contains() or a QString::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 …


  • Lifetime Qt Champion

    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's QLineEdit 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.


Log in to reply