Dealing with Qt out-of-order focusoutevent bug

Some call it as feature, others treat it as bug - be what it may, you still have to address this if your application depends on the order of events in Qt.

Consider the case where you have a QLineEdit object with editingFinished() handler, along with a QPushButton with its relevant clicked() handler (or a toolbar /menu action with its triggered() handler). Now try typing something in the lineEdit followed immediately by a click on the button or menu/toolbar (while the cursor is still in the text box). You will see the button clicked() (or toolbar action triggered()) handler getting invoked before editingFinished() handler.

This happens because of the incorrect order in which focus out and button clicked events are raised by Qt. For some reasons, when focus shifts from textbox to button, the clicked() event for button gets triggered before the focusoutevent() for the lineEdit.

Now, if you do not see this behavior, then you do not need to worry. On the otherhand, if you do see this behavior, but argue that its a feature - good luck to you - but for my CarMusTy, this out of sequence event order means a problem, since there are custom property managers user has to deal with and receiving editingFinished() after the button event handler means having inconsistent state in the generated pdf. (In case you are wondering what CarMusTy is, its a Typesetting environment for editing and publishing high quality Carnatic Music Books. Compromising on quality is certainly not an option for this application.)

Now, how do we deal with such situation - is there a way you can force the event order in the Qt. Something that can help you tell Qt to raise the editingFinished() of lineEdit before the clicked() handler of button?

Luckily, there is one. Here is the way CarMusTy addressed this problem. Try this - it might help you too.

The event subscription mechanism of Qt allows you to specify how the handler should be invoked, through Qt::DirectConnection and Qt::QueuedConnection options. The Qt::DirectConnection lets the handler be called right-on-the-spot when the signal is emitted vs. the Qt::QueuedConnection lets the handler be pushed into the event queue and called next time when the event loop is executed. Combine this with the QCoreApplication::processEvents() method and you can execute all pending queued events before they are naturally invoked

As a first step, ensure that your button clicked() event handler is connected explicitly through QObject::connect() method rather than through named connection. This way you can specify Qt::QueuedConnection to the button clicked() event handler while connecting it to your slot. (If you let the named connections automatically connect your button clicked event handler, then you will not be able to specify Qt::QueuedConnection. Named connections by default use the Qt::AutoConnection, which resorts to direct connection if on the same thread.) This Qt::QueuedConnection specification helps delay the button event handler till the event loop execution and might resolve some order issue.

Note that when the button clicked handler is called, the editingFinished() will be waiting in the queue to be process once your button clicked handler returns. To ensure that the editingFinished() handler is completed before, you can insert a QCoreApplication::processEvents() call in your button clicked event handler. This forces the event queue to be flushed and thus the editingFinished() be called at that point.

For example, OnBuildPreview() is the button click handler and SongRagaChanged() is the lineEdit valuechanged handler. We want SongRagaChanged() be called before OnBuildPreview() be called. But we cannot enforce Qt to this, so we make QCoreApplication::processEvents() inside the buttong handler (OnBuildPreview), to force the line edit handler SongRagaChanged() get complete first.

 void PDFDocument::OnBuildPreview() // Handler for the button clicked
{
    // We need this to flush the events (so that any changes to song data get comitted first through SongRagaChanged())
    QCoreApplication::processEvents();

    if(m_pCMInputArea->OnCompile())            
        reloadPreview(m_pCMInputArea->GeneratedPDFPath());
}

void CMInputArea::SongRagaChanged(const QString &val) // Handler for text value changed
{
    CallSongItemMethod(SetRaga, val);
    m_pVerseRagaProp->AddEditListEntry(val);
}

- Gopalakrishna Palem, Creator of CFugue and CarMusTy