Exiting from a thread causes unresponsive GUI - python

This is a follow-up to my previous question, wxPython popup from calling imported function.
I was able to figure out a way to create a wxPython dialog window to determine if a thread my GUI called should continue its execution. What I did was simply create the dialog window in the thread itself. However, once I made the thread exit by clicking "no" in this popup, the close button in my GUI became unresponsive, and I couldn't close the GUI itself. Once again, your help is much appreciated!
GUI code:
import sys
import os
import re
import subprocess
import threading
import wx
import errno, os, stat, shutil
import extern_func
#this object redirects the external function output to the text box
class RedirectText(object):
def __init__(self,aWxTextCtrl):
self.out=aWxTextCtrl
def write(self,string):
self.out.WriteText(string)
#GUI code here
class progFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="functionGUI", size=(800, 600), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
panel = wx.Panel(self)
#more things....
self.closeButton = wx.Button(panel, wx.ID_OK, "Run", pos=(250, 300))
self.runButton = wx.Button(panel, wx.ID_OK, "Run", pos=(200, 300))
self.out=wx.TextCtrl(panel, style=wx.TE_MULTILINE|wx.VSCROLL|wx.TE_READONLY, pos = (300, 50), size=(500, 200))
#Run button event
self.Bind(wx.EVT_BUTTON, self.OnRun, self.runButton)
#close button event
self.Bind(wx.EVT_BUTTON, self.OnClose, self.closeButton)
#command prompt output to frame
redir=RedirectText(self.out)
sys.stdout=redir
self.Show()
def OnRun(self, event):
t=threading.Thread(target=self.__run)
t.start()
def OnClose(self, event):
self.Destroy()
#external function call
def __run(self):
externFunc()
if __name__ == '__main__':
app = wx.App(False)
progFrame(None)
app.MainLoop()
External function code:
import sys
def externFunc():
print "Starting execution..."
#a bunch of code...
#this is the code for the Yes/No prompt and what introduced the buggy behavior
if(os.path.isdir(mirror_source_path) or os.path.isdir(mirror_dest_path)):
app = wx.App(False)
dlg = wx.MessageDialog(None, "Something bad happened. Continue?","Warning",wx.YES_NO | wx.ICON_QUESTION)
retCode = dlg.ShowModal()
if (retCode == wx.ID_YES):
print "Continuing."
else:
print "Aborted."
return None
sys.exit(0)
dlg.Destroy()
#more function code...
print "Success!"

You cannot have 2 wxPython main loops running at the same time. That will cause some pretty screwy behavior. Personally I think I would split this code into 2 threads. When the first one finishes, it sends a message using wx.CallAfter and pubsub or wx.PostEvent and at that time you can do your if statement in a wxPython handler.
If you continue, then you spin up a second thread with the rest of the function code.

Related

Python wx session timeoute

i am trying to write a simple code for timeout a session. my idea is that if user dont interact with the application for 5min, then a function will fireup and kill the application. But if user is active and interact with the application the kill function won't be able to run. Anyone help
my simple wx here
import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Timeout",
size=(500, 500))
panel = wx.Panel(self, wx.ID_ANY)
self.toggleBtn = wx.Button(panel, wx.ID_ANY, "Enter")
self.toggleBtn.Bind(wx.EVT_BUTTON, self.onToggle)
def onToggle(self, event):
print("you have a action")
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()
wx.Timer is what you need.
Start a timer with 60000 ms interval (5 minutes). Bind the timer event to some handler. When the timer shots, close your app in that handler.
If the user makes some action (e.g. moving the mouse, or hitting the keyboard) the timer must be stopped and restarted.
For this you need to bind mouse and keyboard events (and let them to be processed as usual by calling Skip). In these handlers is where you restart the timer.
See, for example, this wiki

Is there some sort of printing buffer that overflows, when WriteText (AppendText, SetLabel, etc.) continuously in wxPython?

I have some text that goes somewhat continuously on the output (somewhat = every two seconds approx in the real application).
When just print into the terminal, everything is ok, no matter how long I keep the running loop.
However, when directing the print to a wx frame, the print action itself goes well, no problem here, but if I keep the running loop more than 10-20 cycles, I am no longer able to close the window normally (mouse or Ctrl+F4 in this example, but the same if I construct an explicit Exit event menu). The action in thread stops, but the application hangs -- it can only be closed by the OS (Windows in my case) with a "force close / wait for the program to respond" dialog. Though, it can be closed immediately with Ctrl+C when the terminal window is on focus. It looks like something fills up with too much text and locks the closing process.
Happens with either WriteText or AppendText on a multiline wxTextCtrl window, or with the SetLabel as in the test code below.
In this test, if I close the wx window almost immediately after launch, it closes well. If I keep running the loop longer (or by comment out the two time.sleep() lines), then the hang will succeed whenever trying to close the window.
What am I missing here ? (Is there something I have to flush ?)
edit Hmm, the issue could be related to "threadsafe method", as described briefly here. Will do some tests in that direction, have to find out the proper usage of each of the wx.CallAfter, wx.CallLater or wx.PostEvent.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import wx
import time
import threading
class TestFrame(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.sometext = wx.TextCtrl(self, wx.ID_ANY, "", style=wx.TE_READONLY)
self.thread = None
self.alive = threading.Event()
self.__set_properties()
self.__do_layout()
self.__attach_events()
self.StartThread()
def __set_properties(self):
self.SetTitle("test")
def __do_layout(self):
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.sometext, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL, 0)
self.SetSizer(sizer)
sizer.Fit(self)
self.Layout()
def __attach_events(self):
self.Bind(wx.EVT_CLOSE, self.OnClose)
def OnClose(self, event):
""" stop & close on system window close """
self.StopThread()
self.Destroy()
def StartThread(self):
""" start the thread """
self.thread = threading.Thread(target=self.TestThread)
self.thread.setDaemon(1)
self.alive.set()
self.thread.start()
def StopThread(self):
""" stop the thread, wait util it is finished """
if self.thread is not None:
self.alive.clear()
self.thread.join()
self.thread = None
def TestThread(self):
""" main thread """
while self.alive.isSet():
self.sometext.SetLabel("Hello")
time.sleep(0.5)
self.sometext.SetLabel("Python")
time.sleep(0.5)
class MyApp(wx.App):
def OnInit(self):
wx.InitAllImageHandlers()
frame = TestFrame(None, -1, "")
self.SetTopWindow(frame)
frame.Show(1)
return 1
if __name__ == "__main__":
app = MyApp(0)
app.MainLoop()
I've changed every SetLabal with AppendtText. I'm don't know why, but SetLabel function don't work correctly for me.
After a few seconds the application will stop working. This is my error output:
(python:14099): Pango-CRITICAL **: pango_layout_get_iter: assertion 'PANGO_IS_LAYOUT (layout)' failed
Segmentation fault
After that I used CallAfter function http://wiki.wxpython.org/CallAfter the application started correctly working for me. My version of TestThread:
def TestThread(self):
""" main thread """
while self.alive.isSet():
wx.CallAfter(self.sometext.AppendText, "Hello")
time.sleep(0.5)
wx.CallAfter(self.sometext.AppendText, "Python")
time.sleep(0.5)
wx.CallAfter(self.sometext.Clear)
I hope this help you.

wxPython event starts up at startup

So I am a complete beginner at python and usually code in C/C++ or Java. In the process of developing a GUI my events keep getting called at the start of the program running instead of when I click the button. (i know my indents are wrong because I needed to put it into a code block). How do I make my events only get called when I left click the button?
def __init__(self, parent, title):
super(QuadDash, self).__init__(parent, title=title, size=(1024, 780))
self.SetBackgroundColour("white")
pnl = wx.Panel(self)
cbtn = wx.Button(pnl, label='Start Quad', pos=(20,30))
self.Bind(wx.EVT_LEFT_DOWN, self.start_quad_event(), cbtn)
self.Show(True)
def start_quad_event(self):
dlg = wx.MessageDialog(self, "Test", "ABC", wx.YES_NO | wx.ICON_QUESTION)
dlg.ShowModal()
if __name__ == '__main__':
app = wx.App()
qd = QuadDash(None, title='QuadCopter Dashboard')
app.MainLoop()
The offending line is:
self.Bind(wx.EVT_LEFT_DOWN, self.start_quad_event(), cbtn)
You actually call start_quad_event() and pass the result to bind().
The correct line is:
self.Bind(wx.EVT_LEFT_DOWN, self.start_quad_event, cbtn)
Note: No parentheses. Here you actually pass the function to bind().

How do I safely destroy a dialog window of a wxPython application?

I created a wxPython application which shows some messages on a dialog window. The dialog window is needed to be force-destroyed by the application before I click the dialog OK button. I used wx.lib.delayedresult to make the destroy call.
My code is:
import wx
dlg=wx.MessageDialog(somewindow,'somemessage')
from wx.lib.delayedresult import startWorker
def _c(d):
dlg.EndModal(0)
dlg.Destroy()
def _w():
import time
time.sleep(1.0)
startWorker(_c,_w)
dlg.ShowModal()
This can do what I desire to do while I got a error message below:
(python:15150): Gtk-CRITICAL **: gtk_widget_destroy: assertion `GTK_IS_WIDGET (widget)' failed
How do I "safely" destroy a dialog without clicking the dialog button?
It has been a while since I have used wxWidgets but I think your dlg.Destroy() may be in the wrong place. Try moving it into the main thread.
import wx
dlg=wx.MessageDialog(somewindow,'somemessage')
from wx.lib.delayedresult import startWorker
def _c(d):
dlg.EndModal(0)
def _w():
import time
time.sleep(1.0)
startWorker(_c,_w)
dlg.ShowModal()
dlg.Destroy()
I would use a wx.Timer()
import wx
########################################################################
class MyDialog(wx.Dialog):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Dialog.__init__(self, None, title="Test")
timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.onTimer, timer)
timer.Start(5000)
self.ShowModal()
#----------------------------------------------------------------------
def onTimer(self, event):
""""""
print "in onTimer"
self.Destroy()
if __name__ == "__main__":
app = wx.App(False)
dlg = MyDialog()
app.MainLoop()
See also http://www.blog.pythonlibrary.org/2009/08/25/wxpython-using-wx-timers/
My problem with dlg.Destroy() is that it is not exiting the prompt.
I have done following to exit the prompt:
def OnCloseWindow(self, e):
dial = wx.MessageDialog(None, 'Are you sure to quit?', 'Question',
wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
ret = dial.ShowModal()
if ret == wx.ID_YES:
self.Destroy()
sys.exit(0)
sys.exit(0) will exit the prompt and move to next line.

How to make something like a log box in wxPython

I'm assuming this is possible with a multiline text box, but not sure how to do it. What I'm looking to do is make a log box in my wxPython program, where I can write messages to it when certain actions happen. Also, i need to write the messages not only when an event happens, but certain times in the code. How would i get it to redraw the window so the messages appear at that instant?
I wrote an article on this sort of thing a couple years ago:
http://www.blog.pythonlibrary.org/2009/01/01/wxpython-redirecting-stdout-stderr/
If you would like just a log dialog in wxpython, use wx.LogWindow:
import wx
class MainWindow(wx.Frame):
def __init__(self, parent=None):
wx.Frame.__init__(self, parent, wx.NewId(), 'Logging')
self.log_window = wx.LogWindow(self, 'Log Window', bShow=True)
box_sizer = wx.BoxSizer(orient=wx.VERTICAL)
show_log_button = wx.Button(self, wx.NewId(), 'Show Log')
show_log_button.Bind(wx.EVT_BUTTON, self._show_log)
log_message_button = wx.Button(self, wx.NewId(), 'Log Message')
log_message_button.Bind(wx.EVT_BUTTON, self._log_message)
box_sizer.AddMany((show_log_button, log_message_button))
self.SetSizer(box_sizer)
self.Fit()
self.Bind(wx.EVT_CLOSE, self._on_close)
def _show_log(self, event):
self.log_window.Show()
def _log_message(self, event):
wx.LogError('New error message')
def _on_close(self, event):
self.log_window.this.disown()
wx.Log.SetActiveTarget(None)
event.Skip()
if __name__ == '__main__':
app = wx.PySimpleApp()
dlg = MainWindow()
dlg.Show()
app.MainLoop()
Where bShow in wx.LogWindow is if it's initially shown or not. This will log nicely all your wx.LogX messages that you can trigger, and it still passes it on to any other handlers.
Another method you could use would be to log with python and then, upon opening a frame/window with a text control in it, use LoadFile to open the log file:
import logging
LOG_FILENAME = 'example.log'
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG)
logging.debug('This message should go to the log file')
Then, when creating a wx.TextCtrl somewhere:
log_control = wx.TextCtrl(self, wx.NewId(), style=wx.TE_MULTILINE|wx.TE_READONLY)
log_control.LoadFile('example.log')
EDIT:
This now works with the _on_close event! Thanks Fenikso

Categories

Resources