handling things outside of main loop in PyQt4 - python

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.

Related

How does the MainDialog() cycle work

I'm trying to understand how the cycle of my "main.py" works. It's based on examples found on the net, about the PySide and Qt Designer, to implement a Python GUI.
The code is:
#***********************************#
# Python Libraries #
#***********************************#
from PySide.QtCore import *
from PySide.QtGui import *
import sys
import time
#***********************************#
# Python files #
#***********************************#
import Gui
from server import *
class MainDialog(QDialog, Gui.Ui_TCPServer):
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
self.setupUi(self)
self.connect(self.ConnectBt, SIGNAL("clicked()"), self.ConnectBt_clicked)
self.connect(self.QuitBt, SIGNAL("clicked()"), self.QuitBt_clicked)
self.connect(self.DisconnectBt, SIGNAL("clicked()"), self.DisconnectBt_clicked)
print "NOW HERE\r\n"
def ConnectBt_clicked(self):
self.ConnectBt.setText("Connecting...")
self.server_connect()
print "THEN HERE\r\n"
def QuitBt_clicked(self):
self.close()
def DisconnectBt_clicked(self):
self.ConnectBt.setText("Connect")
self.server_off = ChronoRequestHandler()
self.server_off.finish()
def server_connect(self):
self.server_on = ServerStart()
self.server_on.try_connect()
if __name__ == '__main__':
app = QApplication(sys.argv)
form = MainDialog()
print "HERE\r\n"
form.show()
app.exec_()
print "END\r\n"
When I call the "main.py" I get a print of "NOW HERE" and "THEN HERE". When I press the 'ConnectBt', I get the print of "THEN HERE".
But and after this, where the cycle remains? Does it returns to init, if so, shouldn't I get again the print of "NOW HERE"? Does it returns to main, if so, shouldn't I get the print of "HERE"? Please explain me...
When I press the 'QuitBt' I get the print of "END"... I'm confused!
Thanks.
I think you should get more clear on how object programming and events work.
In the last if-statement (the code on the bottom that runs when you call your script from e.g. terminal) you create an app object instance of QApplication.
After that you create form, instance of MainDialog which is the class you define above (inheriting methods, properties, etc from two classes, QDialog, Gui.Ui_TCPServer).
By doing
form = MainDialog()
you run __init__, print "NOW HERE" and go out of that method. Please check what __init__ does in Python. why-do-we-use-init-in-python-classes
Before the end you call the exec() method of the app instance. This contains a loop so that your interface gathers and processes events. See the documentation on QApplication.exec() below.
When you press the 'ConnectBt' button you call the ConnectBt_clicked() method, which does stuff (connects with the server) and prints "THEN HERE".
In the same way, when you press QuitBt you call QuitBt_clicked(), which closes the connection and lets the code print "END".
I also suggest you read more documentation about the classes you are using. They will explain how come that the different buttons are "linked"/have as callbacks the methods ConnectBt_clicked(), def QuitBt_clicked(), and DisconnectBt_clicked(). The mechanisms by which the buttons trigger these callbacks is kind of implicit in the code implemented in those classes.
QApplication Class Reference: exec_
int QApplication.exec_ ()
Enters the main event loop and waits until exit() is called, then
returns the value that was set to exit() (which is 0 if exit() is
called via quit()).
It is necessary to call this function to start event handling. The
main event loop receives events from the window system and dispatches
these to the application widgets.
Generally, no user interaction can take place before calling exec().
As a special case, modal widgets like QMessageBox can be used before
calling exec(), because modal widgets call exec() to start a local
event loop.
To make your application perform idle processing, i.e., executing a
special function whenever there are no pending events, use a QTimer
with 0 timeout. More advanced idle processing schemes can be achieved
using processEvents().
We recommend that you connect clean-up code to the aboutToQuit()
signal, instead of putting it in your application's main() function.
This is because, on some platforms the QApplication.exec() call may
not return. For example, on the Windows platform, when the user logs
off, the system terminates the process after Qt closes all top-level
windows. Hence, there is no guarantee that the application will have
time to exit its event loop and execute code at the end of the main()
function, after the QApplication.exec() call.
See also quitOnLastWindowClosed, quit(), exit(), processEvents(), and
QCoreApplication.exec().

PySide / Python GUI freezes

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.

pySDL2 Display without End Loop

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.

Exit from infinite loop after gtk window closes

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.

wxPython won't close Frame with a parent who is a window handle

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.

Categories