[wxPython] ShowModal implementation under wxMSW Buggy!

Problem:

(See sourcecode pieces of wxMSW)

wxApp::OnIdle()

    Can only be entered once a time because of:

        if ( s_inOnIdle )
                return;

        s_inOnIdle = TRUE;
        ProcessPendingEvents();

        ...

        s_inOnIdle = FALSE;

wxApp::MainLoop()

while ( m_keepGoing )
    while ( !Pending() && ProcessIdle() )
        ;

    DoMessage();

wxDialog::DoShowModal()

    while ( IsModalShowing() )
        while ( !wxTheApp->Pending() && wxTheApp->ProcessIdle() ) <<<<XXXX
        wxTheApp->DoMessage();

Scenario:

  • Application calls wxPostEvent(Handler1,Event1)

  • Event1 is appended to the pending-events-list on Handler1

  • Handler 1 is appended to the wxPendingEvents list

  • wxApp.OnIdle() is called by the MainLoop() via ProcessIdle()

  • s_inOnIdle is set to True

  • EventHandler-Function of Event1 is called

  • Function does a ShowModal()

  • While modal-showing of Dialog, the ProcessIdle() function which invokes

    wxApp.OnIdle has no effect because s_inOnIlde of wxTheApp is already set to true

    and no Pending Events can be processed during a modal dialog shown

    from a pending event!!!

Problem:

  • Implementation of wxDialog.ShowModal() under wxMSW does not work correctly,

    when ShowModal called from an EventHandler invoked from a Pending Event

Questions:

  • How could this bug be fixed, does anybody have an idea?

  • How is wxGTKs ShowModal implemented?

  • Is there a workaround for my problem?

Please Help me somebody,

I would patch the ShowModal Implementation by myself, If somebody would give me a hint,

why the s_inOnIdle has to be done. if ( s_inOnIdle ) return;

Greetings, Andre’

See here a testscript:

···

from wxPython.wx import *

MYEVENT1=wxNewEventType()
MYEVENT2=wxNewEventType()

class Dialog2(wxDialog):
def init(self,parent):
wxDialog.init(self,parent,-1,“Dialog2”)
b=wxButton(self,-1,“Send Event2 to Dialog2”)
EVT_BUTTON(self,b.GetId(),self.onButton)
self.Connect(-1,-1,MYEVENT2,self.onEvent2)
self.Fit()
def onButton(self,event):
wxPostEvent(self,wxPyCommandEvent(MYEVENT2,self.GetId()))
print “Posted Event2 to Dialog2”
def onEvent2(self,event):
print “Received Event2 in Dialog2”

class Dialog1(wxDialog):
def init(self,parent):
wxDialog.init(self,parent,-1,“Dialog1”)
b1=wxButton(self,-1,“Dialog2.ShowModal() directly” ,pos=(0, 0))
b2=wxButton(self,-1,“Send Event to Dialog1 which Does Dialog2.ShowModal()”,pos=(0,30))
EVT_BUTTON(self,b1.GetId(),self.onOpenDirectly )
EVT_BUTTON(self,b2.GetId(),self.onPostEventToMySelf)
self.Connect(-1,-1,MYEVENT1,self.onEvent1)
self.SetSize((500,200))
def onOpenDirectly(self,event):
print “onOpenDirectly”
dialog2=Dialog2(self)
dialog2.ShowModal()
dialog2.Destroy()
def onPostEventToMySelf(self,event):
wxPostEvent(self,wxPyCommandEvent(MYEVENT1,self.GetId()))
print “Posted Event1 to Dialog1”
def onEvent1(self,event):
print “Received Event1 in Dialog1”
dialog2=Dialog2(self)
dialog2.ShowModal()
dialog2.Destroy()

class Application(wxApp):
def OnInit(self):
dialog1=Dialog1(None)
dialog1.ShowModal()
dialog1.Destroy()
return 1

app=Application()
app.MainLoop()

I just discovered this problem last week, but not the cause, so it is great to see that someone else has located the source of the problem.

(I posted to wx-users, under the title "wxPostEvent and modal dialogs", because I thought it was more likely a generic wxWindows problem than a specifically wxPython one.)

Andre Reitz wrote:

Problem:

(See sourcecode pieces of wxMSW)

wxApp::OnIdle()

        Can only be entered once a time because of:

            if ( s_inOnIdle )
                    return;

The comment in the source code (2.3.3) says that this is necessary to:

// Avoid recursion (via ProcessEvent default case)

Looking at wxEvtHandler::ProcessEvent (in common/event.cpp), I would guess that "default case" refers to the call to wxTheApp->ProcessEvent(event) under the comment "// Last try - application object". However, that call is made from inside an

if ( event.GetEventType() != wxEVT_IDLE )

statement. Perhaps the if statement was added after the s_inOnIdle flag, but no one noticed that s_inOnIdle was now redundant?

There is also a call to parent->GetEventHandler->ProcessEvent earlier in wxEvtHandler::ProcessEvent, but that is inside an if (m_isWindow && event.IsCommandEvent()), and I would assume that m_isWindow is false when the event handler is a wxApp.

So, it looks to me like the s_inOnIdle test is unnecessary. However, I'm not such an expert on the wxWindows source code, so you shouldn't necessarily assume my guesses are right.

David

···

            s_inOnIdle = TRUE;
            ProcessPendingEvents();

            ...

            s_inOnIdle = FALSE;

wxApp::MainLoop()
    ...

    while ( m_keepGoing )
        while ( !Pending() && ProcessIdle() )
            ;

        DoMessage();

wxDialog::DoShowModal()

        while ( IsModalShowing() )
            while ( !wxTheApp->Pending() && wxTheApp->ProcessIdle() ) <<<<XXXX
            wxTheApp->DoMessage();

Scenario:

- Application calls wxPostEvent(Handler1,Event1)

- Event1 is appended to the pending-events-list on Handler1

- Handler 1 is appended to the wxPendingEvents list

- wxApp.OnIdle() is called by the MainLoop() via ProcessIdle()

- s_inOnIdle is set to True

- EventHandler-Function of Event1 is called

- Function does a ShowModal()

- While modal-showing of Dialog, the ProcessIdle() function which invokes

  wxApp.OnIdle has no effect because s_inOnIlde of wxTheApp is already set to true

  and no Pending Events can be processed during a modal dialog shown

  from a pending event!!!!

Problem:

- Implementation of wxDialog.ShowModal() under wxMSW does not work correctly,

  when ShowModal called from an EventHandler invoked from a Pending Event

Questions:

- How could this bug be fixed, does anybody have an idea?

- How is wxGTKs ShowModal implemented?

- Is there a workaround for my problem?

Please Help me somebody,

I would patch the ShowModal Implementation by myself, If somebody would give me a hint,

why the s_inOnIdle has to be done. if ( s_inOnIdle ) return;

Greetings, Andre'