I'm creating a program that keeps checking for change in a MySQL database, and according updates a GTK display. The part that keeps checking is in an infinite loop.
What I want is that once the GTK window has been closed, I can break out of the infinite loop.
But I don't know what condition to use for that. I've tried
if !window:
and
if window == None:
but in either case, it doesn't work.
The structure of my code is like this:
while True:
# my code
while gtk.events_pending():
gtk.main_iteration()
# something to exit here
window.connect("destroy", gtk.main_quit())
I don't know if placing "window.connect" there can cause a problem, because the window seems to close just fine. Also, if I placed it within the loop, or before the loop, I'd get a Runtime Error: called outside of mainloop.
So to re-iterate, how do I exit the infinite loop using the closure of the window as a condition? I don't want the user to have to use Ctrl + C.
Thanks in advance!
This is a classical background thread problem.
You need to have a loop like this:
closing = False
while not closing:
// do the MySQL stuff
And then connect a signal handler to window destroy event that sets closing to True
The basic structure of a pygtk app is usually something like this:
win = gtk.MyWindow()
win.connect("destroy", gtk.main_quit) # Note no paretheses after main_quit.
gobject.timeout_add(1000, win.check_DB)
win.show_all()
gtk.main()
The gobject.timeout_add command will call the win.check_DB method every 1000 milliseconds.
In win.connect("destroy", gtk.main_quit) it is important not to put parentheses after main_quit. You are passing the function object gtk.main_quit to the win.connect method, not the return value of having called gtk.main_quit(), which is what would happen if you add the parentheses.
Since gtk.main_quit() quits the app, using parentheses here halts the program too early.
Related
The code below is a stripped down version (for clarity reasons) of a small application I am working on; an application for spelling words for children.
The problem
The problem I am having is in the function flash_correct(); its purpose is to show a word for 5 seconds, then hide again.
I must have a silly blind spot, but no matter where I put the time.sleep(5), the function starts with the break of 5 seconds, while the entry: self.entry2 never shows up:
Without the time.sleep(5) however, it shows up correctly:
Where is my blind spot?
The (stripped down) code:
#!/usr/bin/env python3
from gi.repository import Gtk, Pango, Gdk
import subprocess
import time
class InterFace(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Woorden raden")
maingrid = Gtk.Grid()
self.add(maingrid)
maingrid.set_border_width(10)
self.entry2 = Gtk.Entry()
self.entry2.set_size_request(500,60)
self.entry2.set_child_visible(False)
self.entry2.modify_font(Pango.FontDescription('Ubuntu 30'))
maingrid.attach(self.entry2, 0, 4, 4, 1)
quitbutton = Gtk.Button("Stop", use_underline=True)
quitbutton.modify_font(Pango.FontDescription('Ubuntu 20'))
quitbutton.connect("clicked", self.on_close_clicked)
maingrid.attach(quitbutton, 3, 7, 1, 1)
showword_button = Gtk.Button("↺", use_underline=True)
showword_button.modify_font(Pango.FontDescription('Ubuntu 25'))
showword_button.connect("clicked", self.flash_correct)
showword_button.set_size_request(60,20)
maingrid.attach(showword_button, 0, 6, 1, 1)
def flash_correct(self, button):
# the time.sleep(5) seems to take place at the beginning
# no matter in which order I set the commands
self.entry2.set_text("Monkey")
self.entry2.set_child_visible(True)
time.sleep(5)
self.entry2.set_child_visible(False)
def on_close_clicked(self, button):
Gtk.main_quit()
window = InterFace()
window.connect("delete-event", Gtk.main_quit)
window.set_default_size(330, 330)
window.set_resizable(False)
window.show_all()
Gtk.main()
You can use time.time to hide for roughly 5 seconds calling Gtk.main_iteration() in the loop to avoid your app becoming unresponsive.
def hide(self, time_lapse):
start = time.time()
end = start + time_lapse
while end > time.time():
Gtk.main_iteration()
def flash_correct(self, button):
# the time.sleep(5) seems to take place at the beginning
# no matter in which order I set the commands
self.entry2.set_text("Monkey")
self.entry2.set_child_visible(True)
self.hide(5)
self.entry2.set_child_visible(False)
There is a good explanation in the pygtk faq 7. How can I force updates to the application windows during a long callback or other internal operation?
If you have a long-running callback or internal operation that tries to modify the application windows incrementally during its execution, you will notice that this doesn't happen; the windows of your app freeze for the duration.
This is by design: all gtk events (including window refreshing and updates) are handled in the mainloop, and while your application or callback code is running the mainloop can't handle window update events. Therefore nothing will happen in the application windows.
The trick here is to realize where your operation can take a while to return, or where it is dynamically changing the window contents, and add a code fragment like this wherever you want an update forced out:
while gtk.events_pending():
gtk.main_iteration(False)
This tells gtk to process any window events that have been left pending. If your handler has a long loop, for instance, inserting this snippet as part of the loop will avoid it hanging the window till the callback has finished.
More eloquently, in the words of the great Malcolm Tredinnick, 'this requires using what should be called "Secret Technique #1 For Making Your Application Look Responsive"(tm):
Adding while gtk.events_pending(): may be no harm also.
It would be better to use a timer that integrates with the main loop, rather than busy-waiting until the time has elapsed. Luckily there is just such a facility in GLib:
def flash_correct(self, button):
self.entry2.set_text("Monkey")
self.entry2.set_child_visible(True)
GLib.timeout_add_seconds(5, self.flash_end)
def flash_end(self):
self.entry2.set_child_visible(False)
return GLib.SOURCE_REMOVE
I'm currently writing a GUI for rvplayer that shall enable artists to automatically render dailies with slate and burn-in information. The GUI is written with PySide and scripted in Python 2.7. My problem is that upon calling my process and updating my QProgressBar with the stdout the GUI freezes. I know that this is a common problem and that it can probably be solved with processEvents() somehow, but I know far too little about threading and process loops to get my head around this issue. Since my code is a little lengthy already, here's the part that causes the issue:
def rv(self, args):
p = subprocess.Popen(["C:/Program Files/Tweak/RV-4.0.10-64/bin/rvio_hw.exe"]+[x for x in args], stdout=subprocess.PIPE)
while True:
line = p.stdout.readline()
if line != "":
progressStr=re.search(r"([0-9]+.[0-9]+%)", line.rstrip())
if progressStr == None:
print line.rstrip()
else:
progressInt=int(float(re.sub("[^0123456789\.]", "", progressStr.group())))
self.prog_QProgressBar.setValue(progressInt)
print progressStr.group()
else:
break
and here is the part that starts my QApplication:
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
finalForm = MainWindow()
finalForm.show()
sys.exit(app.exec_())
I'm calling the function rv upon pressing a button and although the progress bar keeps updating normally, the window starts to get nonresponsive after some time. I do not understand at which point I could use app.processEvents() to tell my QApplication to run the process in a separate thread or in the background.
Since it looks like you're not using threads, it may be that all that's required is to call processEvents after updating the progress bar, like this:
self.prog_QProgressBar.setValue(progressInt)
QtGui.qApp.processEvents()
However, the effectiveness of this may depend on how long it takes the process to produce the output. All that the processEvents call does is to immediately handle any pending events (e.g. widget painting, mouse clicks, etc) that are currently in the application's event queue. In between those calls, the GUI will continue to freeze (i.e. the executing code is not run in a separate thread or in the background, as you suggested). So the extent to which this technique can keep the GUI responsive depends on how frequently processEvents can be called within the rv() method.
The issue is that it's not as if your app is frozen, but Windows thinks that the app is frozen as it's ignoring events (mouse over, click etc etc), so Windows, in its wisdom, gives you that dialogue.
You need to start the thread off after the show() and then run the processEvents function, and obviously only call sys.exit once your thread has finished.
In every pySLD2 example I've found, I've seen a loop at the end of the code to keep the window open until closure. For example:
#!/usr/bin/env python
"""
The code is placed into public domain
by anatoly techtonik <techtonik#gmail.com>
"""
import sdl2
import sdl2.ext as lib
lib.init()
window = lib.Window('', size=(300, 100))
window.show()
renderer = lib.Renderer(window)
renderer.draw_point([10,10], lib.Color(255,255,255))
renderer.present()
####Specifically this loop####
running = True
while running:
for e in lib.get_events():
if e.type == sdl2.SDL_QUIT:
running = False
break
if e.type == sdl2.SDL_KEYDOWN:
if e.key.keysym.sym == sdl2.SDLK_ESCAPE:
running = False
break
All event handlers I've seen have been blocking. Is there a way, like I have seen done in standard SDL, to simply call initialization and updating functions on a window that stays open. I am writing to write this as an external library that I can call independently from any project.
Any ideas? Thanks!
EDIT: As per request for a way to do this in standard SDL, this works. Just call the init function to set up the screen and it will stay until you close it.
You are misinterpreting the example - this is an additional hook (screen.c) to be set up in the main loop.
SDL uses the main loop approach to deal with events, update the window display, etc. Yes, there is a way to work around that. Create your very own window and use whatever approach to keep it open, get the window handle via the SDL_SysWM* functions and update the window's display buffer.
This however involves some glue code and also requires you to do the window handling on your own.
We're building a hardware thing based on RaspberryPi and using PyQt4 to handle output on a small 3" screen.
Apart from screen we have a simple hardware keypad that needs handling in a loop to check for keys pressed and act accordingly.
To simplify things code right now looks something like this:
while True:
gui_initialized = False
current_screen = None
if gui_initialized is False:
app = QtGui.QApplication(sys.argv)
main_win = gui.GUI()
gui_initialized = True
sys.exit(app.exec_())
key_code = kp.getKey()
This code is wrong because of the PyQt4 main loop. Once execution gets to the line sys.exit(app.exec_()) - it stops in there forever and never gets to key_code = kp.getKey(). But I need key_code = kp.getKey() to execute somewhere within the loop to handle keypad and also will need to do other background work.
In fact usual Qt event handling is not really applicable in our case because we'll have no standard input tools, only a custom keypad and NFC antenna, both handled within main python program.
So the question is - how do I handle additional work I need to do along with PyQt4 main loop?
The idiomatic way of doing polling in Qt is to put your polling code in a slot, and connect to it a timeout signal from a QTimer with a zero timeout.
I have a program in Python that gets a window handle via COM from another program (think of the Python program as an addin) I set this window to be the main Python frame's parent so that if the other program minimizes, the python frame will too. The problem is when I go to exit, and try to close or destroy the main frame, the frame.close never completes it's execution (although it does disappear) and the other program refuses to close unless killed with TaskManager.
Here are roughly the steps we take:
if we are started directly, launch other program
if not, we are called from the other program, do nothing
enter main function:
create new wx.App
set other program as frame parent:
Get handle via COM
create a parent using wx.Window_FromHWND
create new frame with handle as parent
show frame
enter main loop
App.onexit:
close frame
frame = None
handle as parent = None
handle = None
Anybody have any thoughts on this or experience with this sort of thing?
I appreciate any help with this!
[Edit]
This is only the case when I use the handle as a parent, if I just get the handle and close the python program, the other program closes fine
I wonder if your Close call may be hanging in the close-handler. Have you tried calling Destroy instead? If that doesn't help, then the only solution would seem to be "reparenting" or "detaching" your frame -- I don't see a way to do that in wx, but maybe you could drop down to win32 API for that one task...?
If reparenting is all you need, you can try frame.Reparent(None) before frame.Close()
My resolution to this is a little bit hacked, and admittedly not the most elegant solution that I've ever come up with - but it works rather effectively...
Basically my steps are to start a thread that polls to see whether the window handle is existent or not. While it's still existent, do nothing. If it no longer exists, kill the python application, allowing the handle (and main application) to be released.
class CheckingThread(threading.Thread):
'''
This class runs a check on Parent Window to see if it still is running
If Parent Window closes, this class kills the Python Window application in memory
'''
def run(self):
'''
Checks Parent Window in 5 seconds intervals to make sure it is still alive.
If not alive, exit application
'''
self.needKill = False
while not self.needKill:
if self.handle is not None:
if not win32gui.IsWindow(self.handle):
os._exit(0)
break
time.sleep(5)
def Kill(self):
'''
Call from Python Window main application that causes application to exit
'''
self.needKill = True
def SetHandle(self, handle):
'''
Sets Handle so thread can check if handle exists.
This must be called before thread is started.
'''
self.handle = handle
Again, it feels a little hackish, but I don't really see another way around it. If anybody else has better resolutions, please post.