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
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.