Udostępnij za pośrednictwem


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

Comments

  • Anonymous
    March 15, 2012
    I am having a similar issue with Qt. My main app has a number of QLineEdit fields, two of which I need to validate by a rather complex routine which is not ammenable to the QValidate process. So I connected the editingFinished signal to a handler which validates and then copies the value to my document object. I also have a QButton which starts the data capture using the information from my document object. Interestingly the QButton and QLineEdit operate OK together, the editingFinished gets signalled before the clicked signal. However I also have an action on a toolbar which also calls the start routine connected to the button, this is signalled bya triggered signal, but it is this one which gets out of step. The result is that my capture starts with the wrong value and exceptions later on when the write edit field data is used. I tried the solution you present but cannot get it to work. I am also confused as you indicate the use of both processEvents() and sendPostedEvents(). I tried both but neither seemed to work. Have you got any suggestions?

  • Anonymous
    March 16, 2012
    Corrected the type about sendPostedEvents(). It should be processEvents(). As for the problem presented in your post,

  1. First try connecting the toolbar action handler through QueuedConnection (if you are using namedconnection, you might have to remove it first) App_Constructor() {  connect(toolbar_action, SIGNAL(triggered()), this, SLOT(myToolbarHandler()), Qt::QueuedConnection); }
  2. In the tool bar action handler, use the QCoreApplication::processEvents() myToolbarHandler() {    // We need this to flush the events (so that any changes from text fields get comitted)    QCoreApplication::processEvents();      // Now call the button handler to process the events    myButtonHandler(); } myButtonHandler() {    // We need this to flush the events (so that any changes from text fields get comitted)    QCoreApplication::processEvents();      // Get the text fields data and process them } This should usually solve the problem. If not, then there is something else we can do about the button handler. We can also queue the button clicked() event handler as shown below and emit the button clicked() event (rather than calling the myButtonHandler() directly.) (If the button clicked handler is using named connection, remove it) First, queue the button handler connection: App_Constructor() {  connect(button1, SIGNAL(clicked()), this, SLOT(myButtonHandler()), Qt::QueuedConnection);  connect(toolbar_action, SIGNAL(triggered()), this, SLOT(myToolbarHandler()), Qt::QueuedConnection); } Now, in the toolbar action handler, emulate the button click (rather than calling button handler directly). myToolbarHandler() {    // We need this to flush the events (so that any changes from text fields get comitted)    QCoreApplication::processEvents();      button1.click();// emit the button clicked handler (which will be queued) } Inside the button handler we already have the QCoreApplication::processEvents() which should take care of processing all events emitted till now. Hope this solves the problem.
  • Anonymous
    November 05, 2013
    I am having the same issue on Qt 4.8.5 on OSX. The solution you proposed did not work for me, but it turned out simply setting the focus policy on the button causes the events to be emitted in the right order: button.setFocusPolicy(Qt::ClickFocus)

  • Anonymous
    November 05, 2013
    I am having the same issue on Qt 4.8.5 on OSX. The solution you proposed did not work for me, but it turned out simply setting the focus policy on the button causes the events to be emitted in the right order: button.setFocusPolicy(Qt::ClickFocus)