Bug in wxPython? MessageDialog.ShowModal() returning always zero on OS X

Hi,

I noticed recently that when a MessageDialog is created in a thread and its ShowModal() is called, it always returns 0 on OS X. This is an error, because it should return the wx.ID of the button pressed. On Windows this does not happen.

When the MessageDialog is created in the main thread, the ShowModal() returns correct value on OS X and PC.

The Mac I used to reproduce this problem is Macbook Air with OS X version 10.9.5. wxPython version is 3.0.2.0. My PC is running Windowsx 8.1 and also has wxPython 3.0.2.0 installed.

Below is code to reproduce the problem. See the comments.

Code also available in pastebin: http://pastebin.com/QbNkw5gz

So is this a bug in wxPython?

  1. import os

  2. import wx

  3. import threading

  4. class MyClass(wx.App):

  5. def init(self, redirect=False, filename=None):

  6. wx.App.init(self, redirect, filename)

  7. self.frame = wx.Frame(None, title=‘Blah blah’)

  8. self.panel = wx.Panel(self.frame)

  9. self.frame.Show()

  10. self.startBtn = wx.Button(self.panel, size=(140,40), label=‘Start’)

  11. self.startBtn.Bind(wx.EVT_BUTTON, self.onStart)

  12. self.panel.Layout()

  13. dlg = wx.MessageDialog(None, “foo”, “bar”, wx.YES_NO)

  14. result = dlg.ShowModal()

  15. print "dialog result: ",result #prints 5103 (wx.ID_YES) or 5104 (wx.ID_NO) both on Windows and OS X

  16. dlg.Destroy()

  17. def onStart(self, event):

  18. test = “test1”

  19. self.t = threading.Thread(target=self.testThread, args = (self, test))

  20. self.t.daemon = False

  21. print “Starting thread:”, test

  22. self.t.start()

  23. def testThread(self, a, test):

  24. dlg = wx.MessageDialog(None, test, test, wx.YES_NO)

  25. result = dlg.ShowModal()

  26. print "dialog result: ",result #prints 5103 (wx.ID_YES) or 5104 (wx.ID_NO) on Windows, but always 0 on OS X

  27. dlg.Destroy()

  28. if name == ‘main’:

  29. app = MyClass()

  30. app.MainLoop()

Jari Nevala wrote:

Hi,

I noticed recently that when a MessageDialog is created in a thread and
its ShowModal() is called, it always returns 0 on OS X. This is an
error, because it should return the wx.ID of the button pressed. On
Windows this does not happen.

When the MessageDialog is created in the main thread, the ShowModal()
returns correct value on OS X and PC.

The Mac I used to reproduce this problem is Macbook Air with OS X
version 10.9.5. wxPython version is 3.0.2.0. My PC is running Windowsx
8.1 and also has wxPython 3.0.2.0 installed.

Below is code to reproduce the problem. See the comments.
Code also available in pastebin: import osimport wximport threadingclass MyClass(wx.App): def __init - Pastebin.com

So is this a bug in wxPython?

It's a known issue, but it only happens with stock dialogs shown before the MainLoop starts. So the easy fix is to just move that code to a method and use wx.CallAfter or wx.CallLater in OnInit to invoke it right after the MainLoop starts processing events.

···

--
Robin Dunn
Software Craftsman

Jari Nevala wrote:

Hi,

I noticed recently that when a MessageDialog is created in a thread and
its ShowModal() is called, it always returns 0 on OS X. This is an
error, because it should return the wx.ID of the button pressed. On
Windows this does not happen.

When the MessageDialog is created in the main thread, the ShowModal()
returns correct value on OS X and PC.

The Mac I used to reproduce this problem is Macbook Air with OS X
version 10.9.5. wxPython version is 3.0.2.0. My PC is running Windowsx
8.1 and also has wxPython 3.0.2.0 installed.

Below is code to reproduce the problem. See the comments.
Code also available in pastebin: import osimport wximport threadingclass MyClass(wx.App): def __init - Pastebin.com

So is this a bug in wxPython?

It's a known issue, but it only happens with stock dialogs shown before the MainLoop starts. So the easy fix is to just move that code to a method and use wx.CallAfter or wx.CallLater in OnInit to invoke it right after the MainLoop starts processing events.

Also, it's important to note that Cocoa, and GUI libraries in general, are not thread-safe. On Mac there is a further restriction that the GUI must be started from the app's main thread. So the best practice is that you start the main loop in the main thread, and any time you want to call GUI code from a thread, as you do in the testThread method, you need to use wx.CallAfter or wx.CallLater to run the GUI code to ensure it runs in the main thread.

Regards,

Kevin

···

On May 13, 2015, at 8:21 AM, Robin Dunn <robin@alldunn.com> wrote:

--
Robin Dunn
Software Craftsman
http://wxPython.org

--
You received this message because you are subscribed to the Google Groups "wxPython-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to wxPython-dev+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Thanks for the answers. As I am new to wxPython. I looked at the CallAfter(). It seemst work as expected. But I need to switch code in my thread based on the dialog result. How can I do that?

Here is some code (I modified the CallAfter() example found at CallAfter - wxPyWiki a bit):

import threading,wx

ID_RUN=101
ID_RUN2=102
class MyFrame(wx.Frame):
    def __init__(self, parent, ID, title):
        wx.Frame.__init__(self, parent, ID, title)
        panel = wx.Panel(self, -1)
        mainSizer=wx.BoxSizer(wx.HORIZONTAL)
        mainSizer.Add(wx.Button(panel, ID_RUN, "Button 1"))
        mainSizer.Add(wx.Button(panel, ID_RUN2, "Button 2"))
        panel.SetSizer(mainSizer)
        mainSizer.Fit(self)
        wx.EVT_BUTTON(self, ID_RUN, self.onClick1)
        wx.EVT_BUTTON(self, ID_RUN2, self
.onClick2)
    def onClick1(self,event):
        print "Clicked button 1"
        wx.CallAfter(self.ShowDialog, "I don't appear until after onClick1 exits")        
        s=raw_input("Enter something:")
        print
 s
    def onClick2(self,event):
        print "Clicked button 2"
        t=threading.Thread(target=self.__run)
        t.start()
        #how to use message dialog result here?

    def __run(self):
        wx.CallAfter(self.ShowDialog, "I appear immediately (event handler\nexited when onClick2 finished)")        
        s=raw_input("Enter something in this thread:")
        print
 s        
    def ShowDialog(self,msg):
        dlg=wx.MessageDialog(self, msg, "Called after", wx.YES_NO|wx.ICON_INFORMATION)
        result = dlg.ShowModal()
        #how to pass result to calling funtion?
        dlg.Destroy()        
class MyApp(wx.App):
    def OnInit(self):
        frame = MyFrame(None, -1, "CallAfter demo")
        frame.Show(True)
        frame.Centre()
        return True

app = MyApp(0)
app.MainLoop()

keskiviikko 13. toukokuuta 2015 18.49.58 UTC+3 kevino kirjoitti:

···

On May 13, 2015, at 8:21 AM, Robin Dunn ro...@alldunn.com wrote:

Jari Nevala wrote:

Hi,

I noticed recently that when a MessageDialog is created in a thread and

its ShowModal() is called, it always returns 0 on OS X. This is an

error, because it should return the wx.ID of the button pressed. On

Windows this does not happen.

When the MessageDialog is created in the main thread, the ShowModal()

returns correct value on OS X and PC.

The Mac I used to reproduce this problem is Macbook Air with OS X

version 10.9.5. wxPython version is 3.0.2.0. My PC is running Windowsx

8.1 and also has wxPython 3.0.2.0 installed.

Below is code to reproduce the problem. See the comments.

Code also available in pastebin: http://pastebin.com/QbNkw5gz

So is this a bug in wxPython?

It’s a known issue, but it only happens with stock dialogs shown before the MainLoop starts. So the easy fix is to just move that code to a method and use wx.CallAfter or wx.CallLater in OnInit to invoke it right after the MainLoop starts processing events.

Also, it’s important to note that Cocoa, and GUI libraries in general, are not thread-safe. On Mac there is a further restriction that the GUI must be started from the app’s main thread. So the best practice is that you start the main loop in the main thread, and any time you want to call GUI code from a thread, as you do in the testThread method, you need to use wx.CallAfter or wx.CallLater to run the GUI code to ensure it runs in the main thread.

Regards,

Kevin


Robin Dunn

Software Craftsman

http://wxPython.org


You received this message because you are subscribed to the Google Groups “wxPython-dev” group.

To unsubscribe from this group and stop receiving emails from it, send an email to wxPython-dev...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Thanks for the answers. As I am new to wxPython. I looked at the CallAfter(). It seemst work as expected. But I need to switch code in my thread based on the dialog result. How can I do that?

For this particular code, since the very first thing you do is show the dialog, I would say to just show the dialog, get the result, and start the thread, passing in the result.

For the more general case where you need to show a prompt or otherwise get user input for a thread while it’s in progress, though, a good way to do things is to use pubsub. Some basic docs are here:

http://wiki.wxpython.org/WxLibPubSub

For this case, to wait on the message to arrive inside the thread, you would do something like this (pseudocode, untested…):

import time

from wx.lib.pubsub import pub

Your MyFrame class code…

def ShowDialog(self, msg):

# code from your example goes here

pub.sendMessage('dialog.response', response=result)

def __run(self):

# do stuff....

answer = None

def _onDialogResponse(response):

	answer = response

pub.subscribe(onDialogResponse, 'dialog.response')

wx.CallAfter(self.ShowDialog, "Do you like SPAM?")

while answer is None:

	time.sleep(0.1)

pub.unsubscribe(onDialogResponse, 'dialog.response')  # so we don't get a response when something else calls the dialog

print("Answer is %r" % answer)

print("Do stuff with answer here...")

Regards,

Kevin

···

On May 16, 2015, at 6:49 AM, Jari Nevala jaritheman@gmail.com wrote:

Here is some code (I modified the CallAfter() example found at http://wiki.wxpython.org/CallAfter a bit):

import threading,wx

ID_RUN=101
ID_RUN2=102
class MyFrame(wx.Frame):
    def __init__(self, parent, ID, title):
        wx.Frame.__init__(self, parent, ID, title)
        panel = wx.Panel(self, -1)
        mainSizer=wx.BoxSizer(wx.HORIZONTAL)
        mainSizer.Add(wx.Button(panel, ID_RUN, "Button 1"))
        mainSizer.Add(wx.Button(panel, ID_RUN2, "Button 2"))
        panel.SetSizer(mainSizer)
        mainSizer.Fit(self)
        wx.EVT_BUTTON(self, ID_RUN, self.onClick1)
        wx.EVT_BUTTON(self, ID_RUN2, self
.onClick2)
    def onClick1(self,event):
        print "Clicked button 1"
        wx.CallAfter(self.ShowDialog, "I don't appear until after onClick1 exits")        
        s=raw_input("Enter something:")
        print
 s
    def onClick2(self,event):
        print "Clicked button 2"
        t=threading.Thread(target=self.__run)
        t.start()
        #how to use message dialog result here?

    def __run(self):
        wx.CallAfter(self.ShowDialog, "I appear immediately (event handler\nexited when onClick2 finished)")        
        s=raw_input("Enter something in this thread:")
        print
 s        
    def ShowDialog(self,msg):
        dlg=wx.MessageDialog(self, msg, "Called after", wx.YES_NO|wx.ICON_INFORMATION)
        result = dlg.ShowModal()
        #how to pass result to calling funtion?
        dlg.Destroy()        
class MyApp(wx.App):
    def OnInit(self):
        frame = MyFrame(None, -1, "CallAfter demo")
        frame.Show(True)
        frame.Centre()
        return True

app = MyApp(0)
app.MainLoop()

keskiviikko 13. toukokuuta 2015 18.49.58 UTC+3 kevino kirjoitti:

On May 13, 2015, at 8:21 AM, Robin Dunn ro...@alldunn.com wrote:

Jari Nevala wrote:

Hi,

I noticed recently that when a MessageDialog is created in a thread and

its ShowModal() is called, it always returns 0 on OS X. This is an

error, because it should return the wx.ID of the button pressed. On

Windows this does not happen.

When the MessageDialog is created in the main thread, the ShowModal()

returns correct value on OS X and PC.

The Mac I used to reproduce this problem is Macbook Air with OS X

version 10.9.5. wxPython version is 3.0.2.0. My PC is running Windowsx

8.1 and also has wxPython 3.0.2.0 installed.

Below is code to reproduce the problem. See the comments.

Code also available in pastebin: http://pastebin.com/QbNkw5gz

So is this a bug in wxPython?

It’s a known issue, but it only happens with stock dialogs shown before the MainLoop starts. So the easy fix is to just move that code to a method and use wx.CallAfter or wx.CallLater in OnInit to invoke it right after the MainLoop starts processing events.

Also, it’s important to note that Cocoa, and GUI libraries in general, are not thread-safe. On Mac there is a further restriction that the GUI must be started from the app’s main thread. So the best practice is that you start the main loop in the main thread, and any time you want to call GUI code from a thread, as you do in the testThread method, you need to use wx.CallAfter or wx.CallLater to run the GUI code to ensure it runs in the main thread.

Regards,

Kevin


Robin Dunn

Software Craftsman

http://wxPython.org


You received this message because you are subscribed to the Google Groups “wxPython-dev” group.

To unsubscribe from this group and stop receiving emails from it, send an email to wxPython-dev...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

You received this message because you are subscribed to the Google Groups “wxPython-dev” group.

To unsubscribe from this group and stop receiving emails from it, send an email to wxPython-dev+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Thanks for the answer again.

Im a bit surprised how much code this workaround requires. After all, were talking about a simple prompt dialog which should return the result correctly, even when calling it from a separate thread (just like it works on Windows).

Is this known issue going to be fixed in the upcoming wxPython release?

Anyway, I tried to address this with pubsub. It doesnt seem to work, the answer is always None so the while loop doesnt exit.

The code:

import threading,wx
import time
from wx.lib.pubsub import pub

ID_RUN=101
ID_RUN2=102
class MyFrame(wx.Frame):
    def __init__(self, parent, ID, title):
        wx.Frame.__init__(self, parent, ID, title)
        panel = wx.Panel(self, -1)
        mainSizer=wx.BoxSizer(wx.HORIZONTAL)
        mainSizer.Add(wx.Button(panel, ID_RUN, "Button 1"))
        mainSizer.Add(wx.Button(panel, ID_RUN2, "Button 2"))
        panel.SetSizer(mainSizer)
        mainSizer.Fit(self)
        wx.EVT_BUTTON(self, ID_RUN, self.onClick1)
        wx.EVT_BUTTON(self, ID_RUN2, self
.onClick2)
    def onClick1(self,event):
        print "Clicked button 1"
        wx.CallAfter(self.ShowDialog, "I don't appear until after onClick1 exits")        
        s=raw_input("Enter something:")
        print
 s
    def onClick2(self,event):
        print "Clicked button 2"
        t=threading.Thread(target=self.__run)
        t.start()
        #how to use message dialog result here?

    def __run(self):
        #wx.CallAfter(self.ShowDialog, "I appear immediately (event handler\nexited when onClick2 finished)")        
        #s=raw_input("Enter something in this thread:")
        #print s     
        
        answer = None
        def _onDialogResponse(response):
            answer = response

        pub.subscribe(_onDialogResponse, 'dialog.response')
        wx.CallAfter(self.ShowDialog, "Do you like SPAM?")
        while answer is None:
            time.sleep(0.1)
        pub.unsubscribe(_onDialogResponse, 'dialog.response')  # so we don't get a response when something else calls the dialog
        print("Answer is %r" % answer)
        print("Do stuff with answer here..."
)
    def ShowDialog(self,msg):
        dlg=wx.MessageDialog(self, msg, "Called after", wx.YES_NO|wx.ICON_INFORMATION)
        result = dlg.ShowModal()
        #how to pass result to calling funtion?
        dlg.Destroy()        
        pub.sendMessage('dialog.response'

, response=result) 
class MyApp(wx.App):
    def OnInit(self):
        frame = MyFrame(None, -1, "CallAfter demo")
        frame.Show(True)
        frame.Centre()
        return True

app = MyApp(0)
app.MainLoop()

lauantai 16. toukokuuta 2015 17.44.00 UTC+3 kevino kirjoitti:

···

On May 16, 2015, at 6:49 AM, Jari Nevala jarit...@gmail.com wrote:

Thanks for the answers. As I am new to wxPython. I looked at the CallAfter(). It seemst work as expected. But I need to switch code in my thread based on the dialog result. How can I do that?

For this particular code, since the very first thing you do is show the dialog, I would say to just show the dialog, get the result, and start the thread, passing in the result.

For the more general case where you need to show a prompt or otherwise get user input for a thread while it’s in progress, though, a good way to do things is to use pubsub. Some basic docs are here:

http://wiki.wxpython.org/WxLibPubSub

For this case, to wait on the message to arrive inside the thread, you would do something like this (pseudocode, untested…):

import time

from wx.lib.pubsub import pub

Your MyFrame class code…

def ShowDialog(self, msg):

code from your example goes here

pub.sendMessage(‘dialog.response’, response=result)

def __run(self):

do stuff…

answer = None

def _onDialogResponse(response):

  answer = response

pub.subscribe(onDialogResponse, ‘dialog.response’)

wx.CallAfter(self.ShowDialog, “Do you like SPAM?”)

while answer is None:

  time.sleep(0.1)

pub.unsubscribe(onDialogResponse, ‘dialog.response’) # so we don’t get a response when something else calls the dialog

print(“Answer is %r” % answer)

print(“Do stuff with answer here…”)

Regards,

Kevin

Here is some code (I modified the CallAfter() example found at http://wiki.wxpython.org/CallAfter a bit):

import threading,wx

ID_RUN=101
ID_RUN2=102
class MyFrame(wx.Frame):
    def __init__(self, parent, ID, title):
        wx.Frame.__init__(self, parent, ID, title)
        panel = wx.Panel(self, -1)
        mainSizer=wx.BoxSizer(wx.HORIZONTAL)
        mainSizer.Add(wx.Button(panel, ID_RUN, "Button 1"))
        mainSizer.Add(wx.Button(panel, ID_RUN2, "Button 2"))
        panel.SetSizer(mainSizer)
        mainSizer.Fit(self)
        wx.EVT_BUTTON(self, ID_RUN, self.onClick1)
        wx.EVT_BUTTON(self, ID_RUN2, self
.onClick2)
    def onClick1(self,event):
        print "Clicked button 1"
        wx.CallAfter(self.ShowDialog, "I don't appear until after onClick1 exits")        
        s=raw_input("Enter something:")
        print
 s
    def onClick2(self,event):
        print "Clicked button 2"
        t=threading.Thread(target=self.__run)
        t.start()
        #how to use message dialog result here?

    def __run(self):
        wx.CallAfter(self.ShowDialog, "I appear immediately (event handler\nexited when onClick2 finished)")        
        s=raw_input("Enter something in this thread:")
        print
 s        
    def ShowDialog(self,msg):
        dlg=wx.MessageDialog(self, msg, "Called after", wx.YES_NO|wx.ICON_INFORMATION)
        result = dlg.ShowModal()
        #how to pass result to calling funtion?
        dlg.Destroy()        
class MyApp(wx.App):
    def OnInit(self):
        frame = MyFrame(None, -1, "CallAfter demo")
        frame.Show(True)
        frame.Centre()
        return True

app = MyApp(0)
app.MainLoop() 

keskiviikko 13. toukokuuta 2015 18.49.58 UTC+3 kevino kirjoitti:

On May 13, 2015, at 8:21 AM, Robin Dunn ro...@alldunn.com wrote:

Jari Nevala wrote:

Hi,

I noticed recently that when a MessageDialog is created in a thread and

its ShowModal() is called, it always returns 0 on OS X. This is an

error, because it should return the wx.ID of the button pressed. On

Windows this does not happen.

When the MessageDialog is created in the main thread, the ShowModal()

returns correct value on OS X and PC.

The Mac I used to reproduce this problem is Macbook Air with OS X

version 10.9.5. wxPython version is 3.0.2.0. My PC is running Windowsx

8.1 and also has wxPython 3.0.2.0 installed.

Below is code to reproduce the problem. See the comments.

Code also available in pastebin: http://pastebin.com/QbNkw5gz

So is this a bug in wxPython?

It’s a known issue, but it only happens with stock dialogs shown before the MainLoop starts. So the easy fix is to just move that code to a method and use wx.CallAfter or wx.CallLater in OnInit to invoke it right after the MainLoop starts processing events.

Also, it’s important to note that Cocoa, and GUI libraries in general, are not thread-safe. On Mac there is a further restriction that the GUI must be started from the app’s main thread. So the best practice is that you start the main loop in the main thread, and any time you want to call GUI code from a thread, as you do in the testThread method, you need to use wx.CallAfter or wx.CallLater to run the GUI code to ensure it runs in the main thread.

Regards,

Kevin


Robin Dunn

Software Craftsman

http://wxPython.org


You received this message because you are subscribed to the Google Groups “wxPython-dev” group.

To unsubscribe from this group and stop receiving emails from it, send an email to wxPython-dev...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

You received this message because you are subscribed to the Google Groups “wxPython-dev” group.

To unsubscribe from this group and stop receiving emails from it, send an email to wxPython-dev...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Thanks for the answer again.

Im a bit surprised how much code this workaround requires. After all, were talking about a simple prompt dialog which should return the result correctly, even when calling it from a separate thread (just like it works on Windows).

Thread safety is an issue in pretty much all programming languages. In Python, due to the GIL, often when performing simple operations with simple data types you can avoid issues, leading to an appearance of thread-safety, but more complex Python code, just like C/C++ code, etc. must be made thread-safe. A good Python-centric resource discussing thread-safety is here:

http://effbot.org/zone/thread-synchronization.htm

Also, it may have seemed like it was working on Windows, but you have a very simple app you’re testing with. Since the modal dialog was in another thread, I bet you would have been able to actually bring up your main window and interact with it when the modal dialog was still showing, continually being able to show more and more dialogs by clicking Button 2 over and over, which in most real apps would present a very real problem. (That is indeed what happens when I remove the wx.CallAfter on Mac.) Threading bugs don’t always show up as crashes or errors, and sometimes, depending on the situation, you may not see a problem at all, but some person running your code on another machine will.

Is this known issue going to be fixed in the upcoming wxPython release?

It’s important to remember that wxPython simply wraps the native OS libraries, so wxPython has the same limitations they do. I’m not aware of any GUI library that advertises itself as thread-safe, which is understandable, as it would be a huge undertaking and would probably slow down the library as well.

Anyway, I tried to address this with pubsub. It doesnt seem to work, the answer is always None so the while loop doesnt exit.

Sorry, change answer to self.answer and it will work, at least it did for me. It’s most likely that despite the way I scoped things, _onDialogResponse is being called from another thread so answer is not actually being set in the __run method’s thread. Possibly using a threading.Lock would also have fixed it, but I didn’t try that. Threads are fun. :wink:

Regards,

Kevin

···

On May 18, 2015, at 6:31 AM, Jari Nevala jaritheman@gmail.com wrote:

The code:

import threading,wx
import time
from wx.lib.pubsub import pub

ID_RUN=101
ID_RUN2=102
class MyFrame(wx.Frame):
    def __init__(self, parent, ID, title):
        wx.Frame.__init__(self, parent, ID, title)
        panel = wx.Panel(self, -1)
        mainSizer=wx.BoxSizer(wx.HORIZONTAL)
        mainSizer.Add(wx.Button(panel, ID_RUN, "Button 1"))
        mainSizer.Add(wx.Button(panel, ID_RUN2, "Button 2"))
        panel.SetSizer(mainSizer)
        mainSizer.Fit(self)
        wx.EVT_BUTTON(self, ID_RUN, self.onClick1)
        wx.EVT_BUTTON(self, ID_RUN2, self
.onClick2)
    def onClick1(self,event):
        print "Clicked button 1"
        wx.CallAfter(self.ShowDialog, "I don't appear until after onClick1 exits")        
        s=raw_input("Enter something:")
        print
 s
    def onClick2(self,event):
        print "Clicked button 2"
        t=threading.Thread(target=self.__run)
        t.start()
        #how to use message dialog result here?

    def __run(self):
        #wx.CallAfter(self.ShowDialog, "I appear immediately (event handler\nexited when onClick2 finished)")        
        #s=raw_input("Enter something in this thread:")
        #print s     
        
        answer = None
        def _onDialogResponse(response):
            answer = response

        pub.subscribe(_onDialogResponse, 'dialog.response')
        wx.CallAfter(self.ShowDialog, "Do you like SPAM?")
        while answer is None:
            time.sleep(0.1)
        pub.unsubscribe(_onDialogResponse, 'dialog.response')  # so we don't get a response when something else calls the dialog
        print("Answer is %r" % answer)
        print("Do stuff with answer here..."
)
    def ShowDialog(self,msg):
        dlg=wx.MessageDialog(self, msg, "Called after", wx.YES_NO|wx.ICON_INFORMATION)
        result = dlg.ShowModal()
        #how to pass result to calling funtion?
        dlg.Destroy()        
        pub.sendMessage('dialog.response'

, response=result) 
class MyApp(wx.App):
    def OnInit(self):
        frame = MyFrame(None, -1, "CallAfter demo")
        frame.Show(True)
        frame.Centre()
        return True

app = MyApp(0)
app.MainLoop()

lauantai 16. toukokuuta 2015 17.44.00 UTC+3 kevino kirjoitti:

On May 16, 2015, at 6:49 AM, Jari Nevala jarit...@gmail.com wrote:

Thanks for the answers. As I am new to wxPython. I looked at the CallAfter(). It seemst work as expected. But I need to switch code in my thread based on the dialog result. How can I do that?

For this particular code, since the very first thing you do is show the dialog, I would say to just show the dialog, get the result, and start the thread, passing in the result.

For the more general case where you need to show a prompt or otherwise get user input for a thread while it’s in progress, though, a good way to do things is to use pubsub. Some basic docs are here:

http://wiki.wxpython.org/WxLibPubSub

For this case, to wait on the message to arrive inside the thread, you would do something like this (pseudocode, untested…):

import time

from wx.lib.pubsub import pub

Your MyFrame class code…

def ShowDialog(self, msg):

# code from your example goes here
pub.sendMessage('dialog.response', response=result) 

def __run(self):

# do stuff....
answer = None
def _onDialogResponse(response):
  answer = response
pub.subscribe(onDialogResponse, 'dialog.response')
wx.CallAfter(self.ShowDialog, "Do you like SPAM?")
while answer is None:
  time.sleep(0.1)
pub.unsubscribe(onDialogResponse, 'dialog.response')  # so we don't get a response when something else calls the dialog
print("Answer is %r" % answer)
print("Do stuff with answer here...")

Regards,

Kevin

Here is some code (I modified the CallAfter() example found at http://wiki.wxpython.org/CallAfter a bit):

import threading,wx

ID_RUN=101
ID_RUN2=102
class MyFrame(wx.Frame):
    def __init__(self, parent, ID, title):
        wx.Frame.__init__(self, parent, ID, title)
        panel = wx.Panel(self, -1)
        mainSizer=wx.BoxSizer(wx.HORIZONTAL)
        mainSizer.Add(wx.Button(panel, ID_RUN, "Button 1"))
        mainSizer.Add(wx.Button(panel, ID_RUN2, "Button 2"))
        panel.SetSizer(mainSizer)
        mainSizer.Fit(self)
        wx.EVT_BUTTON(self, ID_RUN, self.onClick1)
        wx.EVT_BUTTON(self, ID_RUN2, self
.onClick2)
    def onClick1(self,event):
        print "Clicked button 1"
        wx.CallAfter(self.ShowDialog, "I don't appear until after onClick1 exits")        
        s=raw_input("Enter something:")
        print
 s
    def onClick2(self,event):
        print "Clicked button 2"
        t=threading.Thread(target=self.__run)
        t.start()
        #how to use message dialog result here?

    def __run(self):
        wx.CallAfter(self.ShowDialog, "I appear immediately (event handler\nexited when onClick2 finished)")        
        s=raw_input("Enter something in this thread:")
        print
 s        
    def ShowDialog(self,msg):
        dlg=wx.MessageDialog(self, msg, "Called after", wx.YES_NO|wx.ICON_INFORMATION)
        result = dlg.ShowModal()
        #how to pass result to calling funtion?
        dlg.Destroy()        
class MyApp(wx.App):
    def OnInit(self):
        frame = MyFrame(None, -1, "CallAfter demo")
        frame.Show(True)
        frame.Centre()
        return True

app = MyApp(0)
app.MainLoop() 

keskiviikko 13. toukokuuta 2015 18.49.58 UTC+3 kevino kirjoitti:

On May 13, 2015, at 8:21 AM, Robin Dunn ro...@alldunn.com wrote:

Jari Nevala wrote:

Hi,

I noticed recently that when a MessageDialog is created in a thread and

its ShowModal() is called, it always returns 0 on OS X. This is an

error, because it should return the wx.ID of the button pressed. On

Windows this does not happen.

When the MessageDialog is created in the main thread, the ShowModal()

returns correct value on OS X and PC.

The Mac I used to reproduce this problem is Macbook Air with OS X

version 10.9.5. wxPython version is 3.0.2.0. My PC is running Windowsx

8.1 and also has wxPython 3.0.2.0 installed.

Below is code to reproduce the problem. See the comments.

Code also available in pastebin: http://pastebin.com/QbNkw5gz

So is this a bug in wxPython?

It’s a known issue, but it only happens with stock dialogs shown before the MainLoop starts. So the easy fix is to just move that code to a method and use wx.CallAfter or wx.CallLater in OnInit to invoke it right after the MainLoop starts processing events.

Also, it’s important to note that Cocoa, and GUI libraries in general, are not thread-safe. On Mac there is a further restriction that the GUI must be started from the app’s main thread. So the best practice is that you start the main loop in the main thread, and any time you want to call GUI code from a thread, as you do in the testThread method, you need to use wx.CallAfter or wx.CallLater to run the GUI code to ensure it runs in the main thread.

Regards,

Kevin


Robin Dunn

Software Craftsman

http://wxPython.org


You received this message because you are subscribed to the Google Groups “wxPython-dev” group.

To unsubscribe from this group and stop receiving emails from it, send an email to wxPython-dev...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

You received this message because you are subscribed to the Google Groups “wxPython-dev” group.

To unsubscribe from this group and stop receiving emails from it, send an email to wxPython-dev...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

You received this message because you are subscribed to the Google Groups “wxPython-dev” group.

To unsubscribe from this group and stop receiving emails from it, send an email to wxPython-dev+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

I just ran into this myself. I have a cross platform app written in python/wx.python and it worked fine on Windows so I couldn’t figure out why it wasn’t working on OSX. Debugging in pyCharm showed that the dialog always returned 0 but I had no idea why. I’m glad you guys mentioned the threading issue because I never would have suspected that as the problem. I was doing this in a thread because it was for an update checker and I didn’t want to block the main UI while I waited for the http request to return. I just moved the dialog part out to a function in the main UI thread and used CallAfter like you guys suggested and it worked like a charm.