2

I customized a dialog that contains a QComboBox control m_box and a QListWidget control m_lst. The m_box supports search matching. If an item from the dropdown list of m_box is selected by directly clicking with the mouse, the cursor will not be displayed in m_box, as shown in the example below.

enter image description here

If an item from m_box is selected using the search matching method with the up and down keys and the Enter key, the cursor will also not be displayed in m_box, as shown in the example below.

enter image description here

However, if matching items are displayed through search matching and an item from the dropdown list is selected with the mouse, the cursor will then be displayed in m_box, as shown in the example below.

enter image description here

I hope for consistent behavior across all three methods: as long as the selected item exists in the candidate items of m_box, the cursor should not be displayed in m_box, unless the mouse is clicked on m_box, at which point the cursor should appear. Here’s a minimal reproducible example:

#include <QApplication>
#include <QDialog>
#include <QComboBox>
#include <QListWidget>
#include <QCompleter>
#include <QStringList>
#include <QVBoxLayout>
#include <QDebug>

class CustomDialog : public QDialog
{
    Q_OBJECT
public:
    CustomDialog(QWidget* parent = nullptr) : QDialog(parent)
    {
        QStringList items;
        items << "item1" << "item2";
        m_box = new QComboBox(this);
        m_box->setEditable(true);
        m_box->addItems(items);
        QCompleter* completer = new QCompleter(items, m_box);
        completer->setFilterMode(Qt::MatchContains);
        m_box->setCompleter(completer);

        m_lst = new QListWidget(this);
        m_lst->addItems(items);

        QVBoxLayout* layout = new QVBoxLayout(this);
        layout->addWidget(m_box);
        layout->addWidget(m_lst);

        connect(m_box, SIGNAL(activated(int)), this, SLOT(onSelChangedBox(int)));
    }

public slots:
    void onSelChangedBox(int iSel);

private:
    QComboBox* m_box;
    QListWidget* m_lst;
};

void CustomDialog::onSelChangedBox(int iSel)
{
    qDebug() << "clear focus of m_box";
    m_lst->setFocus();
}

int main(int argc, char* argv[])
{
    QApplication a(argc, argv);

    CustomDialog dlg;
    dlg.setFixedSize(300, 200);
    dlg.show();

    return a.exec();
}

#include "main.moc"

Note: If a second QComboBox is added and the item is selected using the third method, then clicking on the second QComboBox will result in two cursors appearing. In fact, at this point, the focus is no longer on the first QComboBox, but the cursor is still displayed. The code and example are as follows:

enter image description here

#include <QApplication>
#include <QDialog>
#include <QComboBox>
#include <QListWidget>
#include <QCompleter>
#include <QStringList>
#include <QVBoxLayout>
#include <QDebug>

class CustomDialog : public QDialog
{
    Q_OBJECT
public:
    CustomDialog(QWidget* parent = nullptr) : QDialog(parent)
    {
        QStringList items;
        items << "item1" << "item2";
        m_box1 = new QComboBox(this);
        m_box1->setEditable(true);
        m_box1->addItems(items);
        QCompleter* completer1 = new QCompleter(items, m_box1);
        completer1->setFilterMode(Qt::MatchContains);
        m_box1->setCompleter(completer1);

        m_box2 = new QComboBox(this);
        m_box2->setEditable(true);
        m_box2->addItems(items);
        QCompleter* completer2 = new QCompleter(items, m_box2);
        completer2->setFilterMode(Qt::MatchContains);
        m_box2->setCompleter(completer2);

        m_lst = new QListWidget(this);
        m_lst->addItems(items);

        QVBoxLayout* layout = new QVBoxLayout(this);
        layout->addWidget(m_box1);
        layout->addWidget(m_box2);
        layout->addWidget(m_lst);

        connect(m_box1, SIGNAL(activated(int)), this, SLOT(onSelChangedBox(int)));
        connect(m_box2, SIGNAL(activated(int)), this, SLOT(onSelChangedBox(int)));
    }

public slots:
    void onSelChangedBox(int iSel);

private:
    QComboBox* m_box1;
    QComboBox* m_box2;
    QListWidget* m_lst;
};

void CustomDialog::onSelChangedBox(int iSel)
{
    qDebug() << "clear focus of m_box";
    m_lst->setFocus();
}

int main(int argc, char* argv[])
{
    QApplication a(argc, argv);

    CustomDialog dlg;
    dlg.setFixedSize(500, 500);
    dlg.show();

    return a.exec();
}

#include "main.moc"
8
  • Your description is a bit confusing and your examples misguiding. While the focus issue may be relevant, you should first ensure that the activated signal is actually emitted in all the cases listed above (a simple qDebug message in the connected function would suffice). Then: if the signal is emitted in all cases, it may be a problem related to event handling (possibly due to the mouse release received after clicking on an item). But I'm not sure that's the case, and the style may not emit activated in some cases (some platforms don't do it on single click). Commented Oct 17, 2024 at 1:44
  • Note that, in theory, calling clearFocus() on one widget followed by setFocus() on another makes the former quite pointless, as the latter will automatically clear the focus on the first widget, since only one widget can only have focus. Commented Oct 17, 2024 at 1:45
  • I'm sure the slot function can be triggered in all three situations above. In addition, m_box->clearFocus() has been removed from the slot function and qDebug has been added.
    – lijiang99
    Commented Oct 17, 2024 at 9:20
  • 1
    Well, if that's the case, you're most probably having some sort of "racing issue" with event management (which can also depend on the platform). My suggestion is to take your time and dig into the Qt sources and study the complex relations between all classes involved with your QComboBox, including: the inner line edit and popup (considering that it's embedded in a QFrame), the completer and its own popup. Be aware that some of them work with their own event filters, and mouse press/release events may have different results. You may prefer using codebrowser.dev for all that. Commented Oct 22, 2024 at 2:42
  • 1
    @lijiang99 I know it may not be that "elegant", but did you try to call setFocus() with a simple QTimer::singleShot()? Eventually connected to a function that also calls QCoreApplication::processEvents() before setting the focus? What about using setFocus() with an appropriate FocusReason? Commented Nov 3, 2024 at 20:08

2 Answers 2

1
+50

This seems to be a Qt bug. It may be a good idea to report this issue using the official Qt bug reporting system.

The problem is that the focus out event is eaten by the popup QCompleter.

Easiest work around is to allow Qt to close the popup normally by delaying the focus change (as already suggested by @musicamante in his comments):

void CustomDialog::onSelChangedBox(int iSel)
{
    qDebug() << "clear focus of m_box";
    QTimer::singleShot(0, this, [this](){m_lst->setFocus();});
}

Another work around is to use QCompleter::InlineCompletion instead of the popup completion.

1
  • 1
    It should be worth noticing that, due to possible event queuing caused by the system, the key/mouse release may still trigger the signal (QCompleter related objects make event handling quite complex). It may be a good idea to consider further testing using a specific and persistent QTimer or QBasicTimer, that could be restarted in the above slot, in order to (theoretically?) ensure that the related setFocus() call is eventually done at the right moment. I agree that this looks like a bug and worth reporting; yet, due to the above complexity, it may not be possible to reliably fix it. Commented Nov 10, 2024 at 3:02
1

I've changed my answer to show an additional observation after playing around with your example. With the following connections, you get the desired behaviour for both mouse clicks and key presses:

class CustomDialog : public QDialog
{
    Q_OBJECT
public:
    CustomDialog(QWidget* parent = nullptr) : QDialog(parent)
    {
        QStringList items;
        items << "item1" << "item2";
        m_box = new QComboBox( this);
        m_box->setEditable(true);
        m_box->addItems(items);
        QCompleter* completer = new QCompleter(items, m_box);
        completer->setFilterMode(Qt::MatchContains);
        m_box->setCompleter(completer);

        m_lst = new QListWidget(this);
        m_lst->addItems(items);

        QVBoxLayout* layout = new QVBoxLayout(this);
        layout->addWidget(m_box);
        layout->addWidget(m_lst);

        connect(completer->popup(), &QAbstractItemView::activated, this, &CustomDialog::onSelChangedBox);
        connect(completer, QOverload<QModelIndex const&>::of(&QCompleter::activated), this, &CustomDialog::onSelChangedBox);
    }

public slots:
    void onSelChangedBox(QModelIndex const&);

private:
    QComboBox* m_box;
    QListWidget* m_lst;
};

void CustomDialog::onSelChangedBox(QModelIndex const& index)
{
    qDebug() << "clear focus of m_box";
    m_lst->setFocus();
    m_lst->setCurrentRow(index.row());
}

Note that the order of the connections matters in this case and the slot function is called twice for mouse clicks. Also, adding a connection to QComboBox::activated unfortunately reverts back to the behaviour you described.

Since the full list of items is already shown in the box layout, you could consider replacing the combobox with a simple line edit.

4
  • I tried your method but unfortunately, it didn't work.
    – lijiang99
    Commented Oct 17, 2024 at 11:53
  • @lijiang99 Oh no! I can have another go on a proper Qt environment soon
    – sigma
    Commented Oct 23, 2024 at 15:47
  • Unfortunately, this does not solve my problem. In my actual project, I must rely on the QComboBox::activated signal to complete certain tasks. Additionally, if a second QComboBox is added to the initial example code and the item is selected using the third method, then clicking on the second QComboBox will result in two cursors appearing. In fact, at this point, the focus is no longer on the first QComboBox, but the cursor is still displayed. I have tried various methods, and the only way to hide the extra cursor on the first QComboBox is within the paintEvent.
    – lijiang99
    Commented Oct 29, 2024 at 14:11
  • @lijiang99 Considering the form of your original post, the fact that adding more combos further complicates things, and that you specifically need the activated signal, I'd suggest to take your time to review/rewrite the post (or create a new one) ensuring that you succinctly yet carefully describe what you want as opposed to what you get (instead of just describing the observation of different results, which doesn't make it clear what specific behavior you need and/or want to avoid). Imagine being the one that reads your question, knowing nothing about the whole context of your problem. Commented Oct 29, 2024 at 21:37

Not the answer you're looking for? Browse other questions tagged or ask your own question.