wxPython: Stuck in .MainLoop() - python

I am not an experienced programmer. This is probably a simple problem to solve.
I have a function that is supposed to run every two minutes. This function is inside a simple wxPython system tray program. The problem is that I do not know how to run the function because wxPython never leave .MainLoop(). Where should I put the function?
Here is the code: (I have left out the function and import because it is not relevant.)
TRAY_TOOLTIP = 'System Tray Demo'
TRAY_ICON = 'star.png'
def create_menu_item(menu, label, func):
item = wx.MenuItem(menu, -1, label)
menu.Bind(wx.EVT_MENU, func, id=item.GetId())
menu.AppendItem(item)
return item
class TaskBarIcon(wx.TaskBarIcon):
def __init__(self):
super(TaskBarIcon, self).__init__()
self.set_icon(TRAY_ICON)
self.Bind(wx.EVT_TASKBAR_LEFT_DOWN, self.on_left_down)
def CreatePopupMenu(self):
menu = wx.Menu()
create_menu_item(menu, 'Say Hello', self.on_hello)
menu.AppendSeparator()
create_menu_item(menu, 'Exit', self.on_exit)
return menu
def set_icon(self, path):
icon = wx.IconFromBitmap(wx.Bitmap(path))
self.SetIcon(icon, TRAY_TOOLTIP)
def on_left_down(self, event):
print 'Tray icon was left-clicked.'
MailCheck()
def on_hello(self, event):
print 'Hello, world!'
def on_exit(self, event):
wx.CallAfter(self.Destroy)
def main():
app = wx.PySimpleApp()
TaskBarIcon()
app.MainLoop()
#This is my function I want to run
#But MainLoop() never ends. Where should I put MainCheck() ?
MailCheck()
if __name__=='__main__':
main()

wxPython, as with most GUI frameworks, uses an event-driven programming model. That means that bits of your program are run in response to actions that may have originated from the user, (such as a key press, a menu selection, etc.) the system or perhaps from some other program. The rest of the time it sits in MainLoop waiting for one of those things to happen.
For situations like yours, there is the wx.Timer class which can trigger an event once or perhaps periodically after N milliseconds. If you bind an event handler for the timer event, then that handler will be called when the timer expires.

I've never used wxPython but you could use the threading-module of Python's standard library.
A minimal example:
import threading
def work():
threading.Timer(0.25, work).start()
print "stackoverflow"
work()
Look at this thread (example is from there): Periodically execute function in thread in real time, every N seconds

Related

How to create simple PyQt popup in Python function and close at the end of it?

I'm running a function in which I would like to open a PyQt popup that says something like "Waiting..." which lasts as long as it takes for the function to finish. Afterwards I would like to change the text to "Complete", sleep for a second or two, and close the popup. I've tried several many ways to do this but have been so far unsuccessful. It looks like any time I call app.exec_() the function halts the function until I close the popup. I want to do this outside of the context of the main event loop of PyQt (I tried running this event loop asynchronously to no avail). If I don't call exec_() I never see the popup at all. Basically I want something like this:
# this is the function that is being run
def run():
create_popup() # show popup
wait_for_some_messages() # do some stuff
close_popup() # close popup
def create_popup():
app = QApplication([])
popup = Popup()
popup.show()
class Popup(QDialog):
super().__init__()
self.label = QLabel(self)
Any help is much appreciated. Thanks!
I think the task you want to execute is heavy so you can block the GUI loop so it is recommended to implement a QRunnable and launch it with QThreadPool, in the run method the heavy function will be called, in the end this task will update the data through QMetaObject::invokeMethod, then we will use a QEventLoop to wait 2 seconds and call the method close.
class Runnable(QRunnable):
def __init__(self, popup, func):
QRunnable.__init__(self)
self.popUp = popup
self.func = func
def run(self):
# execute hard function
self.func()
# Update the text shown in the popup
QMetaObject.invokeMethod(self.popUp, "setText",
Qt.QueuedConnection,
Q_ARG(str, "Complete"))
# wait 2 seconds
loop = QEventLoop()
QTimer.singleShot(2000, loop.quit)
loop.exec_()
# call the close method of the popup
QMetaObject.invokeMethod(self.popUp, "close",
Qt.QueuedConnection)
class PopUp(QDialog):
def __init__(self, *args, **kwargs):
QDialog.__init__(self, *args, **kwargs)
self.setLayout(QVBoxLayout())
self.label = QLabel(self)
self.layout().addWidget(self.label)
#pyqtSlot(str)
def setText(self, text):
self.label.setText(text)
# emulate the heavy task
def func():
for i in range(10000):
print(i)
QThread.msleep(1)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = PopUp()
w.show()
w.setText("Waiting...")
runnable = Runnable(w, func)
QThreadPool.globalInstance().start(runnable)
w.exec_()
plus:
If you want to call another function just change:
runnable = Runnable(w, func)
to:
runnable = Runnable(w, name_of_your_function)

Gtk widget shows up with delay

Using python3 and gi.repository, I want to have a Gtk.HeaderBar with a Gtk.Button that is replaced by a Gtk.Spinner as soon as you click on it. After a calculation the button should appear again.
Here is an example of how I think it should work but the Gtk.Spinner only shows up after the calculation (in this example sleep) for a very short time. How can I achieve that the spinner shows up for the whole calculation (or sleep)?
from gi.repository import Gtk
import time
class window:
def __init__(self):
self.w = Gtk.Window()
self.button = Gtk.Button('x')
self.button.connect('clicked', self.on_button_clicked)
self.spinner = Gtk.Spinner()
self.hb = Gtk.HeaderBar()
self.hb.props.show_close_button = True
self.hb.pack_start(self.button)
self.w.set_titlebar(self.hb)
self.w.connect('delete-event', Gtk.main_quit)
self.w.show_all()
def on_button_clicked(self, widget):
self.button.hide()
self.hb.pack_start(self.spinner)
self.spinner.show()
self.spinner.start()
time.sleep(5)
self.spinner.stop()
self.hb.remove(self.spinner)
self.button.show()
if __name__ == '__main__':
w = window()
Gtk.main()
GTK+ is an event driven system where the mainloop should be left free to update the UI and everything that takes time (like reading from a file, making a network connection, long calculations) should happen asynchronously.
In your case this would look something like this:
def on_button_clicked(self, widget):
self.button.hide()
self.spinner.show()
self.spinner.start()
GLib.timeout_add_seconds (5, self.processing_finished)
def processing_finished(self):
self.spinner.stop()
self.spinner.hide()
self.button.show()
Note that I removed the pack and remove calls: do those in __init__(). You'll want from gi.repository import GLib in there as well.
This way the main loop is free to update the UI as often as it wants. If you really want to use a blocking call like sleep(), then you'll need to do that in another thread, but my suggestion is to use libraries that are asychronous like that timeout_add_seconds() call.
The problem is time.sleep(): it is a blocking function.
def on_button_clicked(self, widget):
self.button.hide()
self.hb.pack_start(self.spinner)
self.spinner.show()
self.spinner.start()
t = time.time()
while time.time() - t < 5:
Gtk.main_iteration()
self.spinner.stop()
self.hb.remove(self.spinner)
self.button.show()
I think that's what you expect.
Edit: You may put a time.sleep(.1) inside while loop, for cpu saving, but don't forget Gtk.main_iteration(): that is the function that exit from while loop to main loop (show spinner, progress bar and so on).

Pause a python tkinter script indefinetly

I have a game in Tkinter in which I want to implement a pause option. I want to bind the key p to stop the script. I tried using time.sleep, but I want to pause the game until the user presses u. I have tried:
def pause(self, event):
self.paused = True
while self.paused:
time.sleep(0.000001)
def unpause(self, event):
self.paused = False
However, this crashes the program and doesn't un-pause.
What is going wrong and how can I fix it?
while creates a loop which makes the GUI loop unresponsive to anything--including KeyPress bindings. Calling just time.sleep(9999999) in the pause method would do the same thing. I'm not sure how the rest of your program is structured, but you should look into the after() method for an easy way to add start and stop features. Here's a simple example:
class App(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.text = Text(self)
self.text.pack()
self._unpause(None) # start the method
self.bind_all('<p>', self._pause)
self.bind_all('<u>', self._unpause)
def _unpause(self, event):
'''this method is the running portion. after its
called, it continues to call itself with the after
method until it is cancelled'''
self.text.insert(END, 'hello ')
self.loop = self.after(100, self._unpause, None)
def _pause(self, event):
'''if the loop is running, use the after_cancel
method to end it'''
if self.loop is not None:
self.after_cancel(self.loop)
root = Tk()
App(root).pack()
mainloop()

python GTK: Dialog with information that app is closing

I have a problem
My application on close has to logout from web application. It's take some time. I want to inform user about it with " logging out" information
class Belt(gtk.Window):
def __init__(self):
super(Belt, self).__init__()
self.connect("destroy", self.destroy)
def destroy(self, widget, data=None):
if self.isLogged:
md = gtk.MessageDialog(None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, ico, gtk.BUTTONS_NONE, txt)
md.showall()
self.send('users/logout.json', {}, False, False)
gtk.main_quit()
def main(self):
if self.iniError is False:
gtk.gdk.threads_init()
gtk.gdk.threads_enter()
gtk.main()
gtk.gdk.threads_leave()
if __name__ == "__main__":
app = Belt()
app.main()
When I try to show dialog in destroy method only window does appear, without icon and text.
I want to, that this dialog have no confirm button, just the information, and dialog have to be destroy with all app.
Any ideas?
Sorry for my poor English
Basically, GTK has to have the chance to work through the event queue all the time. If some other processing takes a long time and the event queue is not processed in the meantime, your application will become unresponsive. This is usually not what you want, because it may result in your windows not being updated, remaining grey, having strange artefacts, or other kinds of visible glitches. It may even cause your window system to grey the window out and offer to kill the presumably frozen application.
The solutution is to make sure the event queue is being processed. There are two primary ways to do this. If the part that takes long consists of many incremental steps, you can periodically process the queue yourself:
def this_takes_really_long():
for _ in range(10000):
do_some_more_work()
while gtk.events_pending():
gtk.main_iteration()
In the general case, you'll have to resort to some kind of asynchronous processing. The typical way is to put the blocking part into its own thread, and then signal back to the main thread (which sits in the main loop) via idle callbacks. In your code, it might look something like this:
from threading import Thread
import gtk, gobject
class Belt(gtk.Window):
def __init__(self):
super(Belt, self).__init__()
self.connect("destroy", self.destroy)
self.show_all()
self.isLogged = True
self.iniError = False
def destroy(self, widget, data=None):
if self.isLogged:
md = gtk.MessageDialog(None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 0, gtk.BUTTONS_NONE, "Text")
md.show_all()
Thread(target=self._this_takes_very_long).start()
def main(self):
if self.iniError is False:
gtk.gdk.threads_init()
gtk.gdk.threads_enter()
gtk.main()
gtk.gdk.threads_leave()
def _this_takes_very_long(self):
self.send('users/logout.json', {}, False, False)
gobject.idle_add(gtk.main_quit)
if __name__ == "__main__":
app = Belt()
app.main()

Python Tkinter: App hangs when changing window title via callback event

I'm creating a simple Python UI via Tkinter, and I'd like use self.title to have the window title change when a callback event is generated.
If I bind the event to a button, or call the event handler directly within the Tk thread, the window title changes as expected. However, I intend this event to be invoked by a separate thread, and I've found that using title in the callback event handler causes the app to hang.
Other tasks that I have in the event handler (such as updating a label) work just fine, so I have to assume that the event is being invoked properly. I've tried wm_title instead of title, but didn't see a difference. I've dug around and found nothing odd about title's usage, just call it with a string to set the title.
Here's a stripped-down sample that replicates the problem (I'm running v2.7.1 on WinXP FYI); the app runs fine for 10 seconds (can move the window, resize, etc.), after which Timer generates the event and the app then freezes.
import Tkinter
import threading
class Gui(Tkinter.Tk):
def __init__(self, parent=None):
Tkinter.Tk.__init__(self, parent)
self.title('Original Title')
self.label = Tkinter.Label(self, text='Just a Label.',
width=30, anchor='center')
self.label.grid()
self.bind('<<change_title>>', self.change_title)
timer = threading.Timer(10, self.event_generate, ['<<change_title>>'])
timer.start()
def change_title(self, event=None):
self.title('New Title')
G = Gui(None)
G.mainloop()
I encountered the same problem, where the UI hangs when calling self.title() from a thread other than the main thread. Tkinter expects all UI stuff to be done in the same thread (the main thread).
My solution was to have the separate thread put functions in a queue. The queue is serviced periodically by the main thread, making use of the after(ms) function provided by Tkinter. Here's an example with your code:
import Tkinter
import threading
from Queue import Queue, Empty
class Gui(Tkinter.Tk):
def __init__(self, parent=None):
Tkinter.Tk.__init__(self, parent)
self.ui_queue = Queue()
self._handle_ui_request()
self.title('Original Title')
self.label = Tkinter.Label(self, text='Just a Label.',
width=30, anchor='center')
self.label.grid()
self.bind('<<change_title>>', self.change_title)
timer = threading.Timer(1, self.event_generate, ['<<change_title>>'])
timer.start()
def change_title(self, event=None):
# Separate the function name, it's args and keyword args,
# and put it in the queue as a tuple.
ui_function = (self.title, ('New Title',), {})
self.ui_queue.put(ui_function)
def _handle_ui_request(self):
'''
Periodically services the UI queue to handles UI requests in the main thread.
'''
try:
while True:
f, a, k = self.ui_queue.get_nowait()
f(*a, **k)
except Empty:
pass
self.after(200, self._handle_ui_request)
G = Gui(None)
G.mainloop()
Well, your code actually runs fine to me.
Except that, when interrupted before the ten secs, it says "RuntimeError: main thread is not in main loop"
I'm using python 2.6.6 under ubuntu 10.10
Hope this was of some help.
I tried it as well with 2.6.2 (Windows) and the caption/title didn't change. No runtime error though.

Categories

Resources