time.sleep() crashes program when using while-loop in PyQt - python

When I use the function time.sleep() in the terminal, it is no problem.
When I use PyQt and want to use a label, it crashes and/or only shows the last number.
By the way: I want a function, that counts e.g. a year from 2020 to 2030 in various speed, that the user can change and the year should be shown in a label.
Thanks a lot for your help.
# timer that counts in the future with various speed // still crashing
def timer(self):
x=datetime.datetime.now()
z=x.ctime()
self.ui.labelDateTime.setText(z)
var=x.year
while True:
if var==2030:
break
else:
var+=1
y=x.replace(year=var)
z=y.ctime()
self.ui.labelDateTime.setText(z)
time.sleep(0.5)
print("You are dead")

You should call QtCore.QCoreApplication.processEvents() within your for loop to make the Qt's event loop proceed the incoming event (from keyboard or mouse or sleep).
Although calling QtCore.QCoreApplication.processEvents() works , I have read in many places on the web that it should be a last resort. Unfortunately none of the sources clearly explain why -- but see for example
How to make Qt work when main thread is busy?
Should I use QCoreApplication::processEvents() or QApplication::processEvents()?
How to implement a QThread that runs forever{} with a QWaitCondition but still needs to catch another Slot while doing that
http://qt-project.org/forums/viewthread/22967
So it does seem allowed, but in general, it seems to be better design to use QTimer or QThread.

Related

Tkinter: mainloop() looping over an init function? Why and how?

What code exactly does mainloop() loop over? I've seen a couple examples where the loop appears to be run over the init function of a window object, but also some where it seems to run over the whole program/no particular object. For what reasons would you set it up one way or the other? And how can you tell what mainloop() is going to execute?
I've read a bunch of Tkinter documentation and StackOverflow questions and (admittedly only skimmed) a couple tutorials, and they all tell me that mainloop() creates and executes an eternal event loop... but there's no detail on what code constitutes the event loop.
The event loop is internal to Tk. It's the code that waits for the user to do something and dispatches those events to the handlers you registered. When it has dispatched one event, it loops back to wait for the next user action. You don't need to do anything with it, but it's what makes the whole system work.
The event loop is found in most (probably virtually all) GUI programming models; it's not just a Tk thing. They don't explain it because it is assumed that you're already familiar with it.
https://en.wikipedia.org/wiki/Event_loop

Detecting if a key is HELD down - python

My use case
I need to know when a (specific) key is pressed and held down. The use case after detection is fairly easy. When the key is released, send a signal to stop the callback (which I know already).
Desired behavior
Here is a rough scheme of how the algo looks like:
def the_callback():
if key_held == the_hotkey:
someObj.start() # this class Obj works totally well so no issues here on
elif key_released == the_hotkey:
someObj.stop()
else:
# we don't care. continue looking for Keyboard events
# here any kinda listener or just a loop which passes events to the callback
I should mention that any kinda listener which blocks the execution is okay as it will run in its own thread (already running pynput.keyboard.Listener in a thread so not a problem)
What I've tried
I used pynput and its pynput.keyboard.Listener to detect key-presses and invoke callbacks accordingly but I couldn't make that work to detect when a key is HELD down.
the current solution looks roughly like:
# not real code. just rough scheme
def on_pressed(key):
if key == my_hotkey:
if running_already: # this part works well already
obj.stop()
else:
obj.start()
else:
# we don't care
with pynput.keyboard.Listener(on_press=on_pressed) as listener:
listener.join() # blocking call until SystemExit, `return False` from callback or `listener.stop()`
I have a very strong feeling that I can make this work by adding on_release=another_callback_that_handles_releases (available within pynput.keyboard.listener).
Perhaps by storing the last known pressed keystroke, and checking if the key released was the same as the hotkey which was pressed earlier but I'm not sure how would I go about it and can that even work?
Then I decided to give keyboard (different lib) a go.
I wrote the below code for the same which can detect keys being held down. This below code achieves almost nearly what I want:
import keyboard as kb, time
while 1:
while kb.is_pressed('q'):
print('Key is held')
time.sleep(0.5) # sleep added just to stop it from spamming the stdout
else:
print('No it\'s Not')
time.sleep(0.5)
The issue with this solution is, it's not very well suited for OSX and Ubuntu. And it has some issues working with special keys. Moreover, I have the hotkey stored as pynput.keyboard.Key.f7 (for eg) or pynput.keyboard.KeyCode(char='s') # for character keys and these enums have different values than what keyboard uses to scan key IDs (using keyboard.hook()).
The final question
How should I go about detecting a key being HELD down. I'd prefer to achieve this using pynput as the rest of the code base uses it but 'keyboard is fine too.
Again I have a feeling that using on_press=a_callback and on_release=another_callback this might be achieved but I'm not entirely sure about it. Lastly, the solution is preferred to be cross platform (I'm fine with using three different functions depending on value of platform.system()).
How would you go about achieving it?
EDIT-1
HERE is what I wrote as an attempt (and MCVE) after suggestion by Isak. This works almost perfectly with just 1 flaw. And that is that it doesn't listen to keypresses right from the program start.
It takes some time for some unknown reason before it starts to actually detect any keypress. Good thing is that once it detects the keypress for the first time, it works flawlessly.
What am I missing there?
Try to check for the key_pressed event on the specific key until the event becomes key_released. So when you detect a click on the key you execute your code and when it detects the release of that key the code stops
I figured out why My Approach was taking a lot of time to initialize before starting the Listener. It was because of the while loop which didn't have any time.sleep() calls and it was probably messing with the system (although I wouldn't expect that to happen as it runs in its own thread but probably the while loop doesn't release the GIL as it's just in the loop doing literally nothing without any sort of delay).
I just added time.sleep(0.2) inside the while loop (the outer one). Any delay would do as that would release the GIL for some time and the Listener thread would be processed and made active.
Edit: Accepting the answer by Isak as accepted as that is the correct approach.

Beginner at Python (Timer help)

I am making a questionnaire using python and want to have a visible timer on the questionnaire. The code I used for the timer is below but when it is counting down, it creates a new line and would not let the rest of my questionnaire run. Please help!
import os
import time
s=59
m=5
while s<=59:
os.system('cls')
print ( m, "Minutes", s, "Seconds")
time.sleep(1)
s-=1
if s==0:
m-=1
s=59
Seems there are two problems:
Your timer loop blocks the rest of your application
Printing the current time gives a newline and makes the screen scroll
To solve 1 use threads or processes. Timer and questionaire are in a different thread.
To solve 2 you could use escape sequences to control the screen. But since your questionaire will have to scroll without scrolling your timer, this really begs for a GUI. Tcl/Tk is the GUI library that comes with Python. It will be the easiest to use that, I expect.

Running Python graphics and code

I have a program that I'm just adding graphics to, but I'm having trouble running my main code along with the graphics. Basically I have something like this:
def mainFunction():
while True:
run code in here
root = Tk()
board = Canvas(root, height=710, width=1000)
board_image = PhotoImage(file="/path/example.jpg")
photo = board.create_image(0,0, anchor=NW, image=board_image)
board.pack()
mainFunction
root.mainloop()
I can only run either the mainFunction or the graphics because whichever one I make run first in the code is the only that runs. It doesn't stop to allow the next code to run. There has to be a simple way to get graphics and code to run together side by side. Thanks!
Use Tk.after_idle() to register a function that will do a piece of the work required. Keep doing piece after piece until all the work is done.
Generally speaking, you cannot put an infinite loop inside a Tkinter application. Why? It's already running an infinite loop: the event loop. Your code is the equivalent of this:
while <there are more events to service>:
while True:
<run code in here>
<get the next event>
<service the event>
See the problem? You're preventing the code from ever servicing events, and events are the life blood of a GUI.
Instead, you need to take advantage of the already-running infinite loop by adding code to be run inside the loop. You do this with after (and after_idle). This will put one even on the queue. If, during the processing of that event you again call after_idle, you've effectively set up an infinite loop that works within the event loop.
For example:
def do_one_iteration(self):
<run code in here>
self.after(100, self.do_one_iteration)
Then, somewhere in your main logic, or in response to a button, you call do_one_iteration. It will do one iteration of your previously-infinite-loop. When it is done it instructs Tkinter to call itself again 100 milliseconds later. When that time period elapses your code is run, it schedules another iteration in 100 milliseconds, etc. etc. You can change the interval to whatever you want; the smaller the interval the faster your code runs, but the greater the chance that you starve the GUI for events.
Note that this only works if <run code in here> runs relatively fast. While it is running your GUI will freeze. If it can complete one iteration in a couple hundred milliseconds then the user will never know. If it takes a second or more it will be noticeable.
Note: this example assumes your main application is an object that inherits from a Tkinter widget. If that's not the case it will still work, you just have to remove the self parameter. An even better solution is to refactor your GUI to use objects -- it's a much more flexible way of implementing GUIs.

Progress bar not updating during operation

in my python program to upload a file to the internet, im using a GTK progress bar to show the upload progress. But the problems that im facing is that the progress bar does not show any activity until the upload is complete, and then it abruptly indicates upload complete. im using pycurl to make the http requests...my question is -
do i need to have a multi-threaded application to upload the file and simultaneously update the gui? or is there some other mistake that im making?
Thanks in advance!
I'm going to quote the PyGTK FAQ:
You have created a progress bar inside a window, then you start running a loop that does some work:
while work_left:
...do something...
progressbar.set_fraction(...)
You will notice that the window doesn't even show up, or if it does the progress bar stays frozen until the end of the task. The explanation is simple: gtk is event driven, and you are stealing control away from the gtk main loop, thus preventing it from processing normal GUI update events.
The simplest solution consists on temporarily giving control back to gtk every time the progress is changed:
while work_left:
...do something...
progressbar.set_fraction(...)
while gtk.events_pending():
gtk.main_iteration()
Notice that with this solution, the user cannot quit the application (gtk.main_quit would not work because of new loop [gtk.main_iteration()]) until your heavy_work is done.
Another solution consists on using gtk idle functions, which are called by the gtk main loop whenever it has nothing to do. Therefore, gtk is in control, and the idle function has to do a bit of work. It should return True if there's more work to be done, otherwise False.
The best solution (it has no drawbacks) was pointed out by James Henstridge. It is taking advantage of python's generators as idle functions, to make python automatically preserve the state for us. It goes like this:
def my_task(data):
...some work...
while heavy_work_needed:
...do heavy work here...
progress_label.set_text(data) # here we update parts of UI
# there's more work, return True
yield True
# no more work, return False
yield False
def on_start_my_task_button_click(data):
task = my_task(data)
gobject.idle_add(task.next)
The 'while' above is just an example. The only rules are that it should yield True after doing a bit of work and there's more work to do, and it must yield False when the task is done.
More than likely the issue is that in your progress callback, which is where I presume you're updating the progress bar, you're not making a call to manually update the display i.e. run through the GUI's event loop. This is just speculation though, if you can provide more code, it might be easier to narrow it down further.
The reason you need to manually update the display is because your main thread is also performing the upload, which is where it's blocking.
In python 2.x integer operands result in integer division. Try this:
#Callback function invoked when download/upload has progress
def progress(download_t, download_d, upload_t, upload_d):
print 'in fileupload progress'
mainwin.mainw.prog_bar.set_fraction(float(upload_d) / upload_t)
Yes, you probably need concurrency, and yes threads are one approach, but if you do use threads, please use an method like this one: http://unpythonic.blogspot.com/2007/08/using-threads-in-pygtk.html which will abstract away the pain, and allow you to focus on the important aspects.
(I have not repeated everything in that blog post through laziness, hence community wiki).
One option, if you are not married to pycurl, is to use GObject's IO watchers.
http://pygtk.org/pygtk2reference/gobject-functions.html#function-gobject--io-add-watch
Using this you can interleave the file upload with the normal PyGTK event loop, and even do the set_progress call in your IO watch callback. If you are offloading all the work for uploading onto pycurl this is not really feasible, but if you're just uploading a file over HTTP, io_add_watch will make using a socket for this much less painful as well.

Categories

Resources