I'm new to Python as well as event-driven/GUI programming in general. As far as I can tell, all the event choices are things like mouse clicks and key presses.
I've written a set of functions in a separate library that read from an I2C device (on Raspberry Pi). The functions return -1 if nothing is read. So basically, I want to loop, calling the read function each time, until something besides -1 is returned.
My first instinct was to write something like:
readResult = -1
while (readResult == -1):
readResult = IO.read()
changeGUI()
This doesn't seem to work though in the tkinter structure. I get how to make a function get called on a button press, but I don't know how to do a custom event.
There are a few ways to go with this -- you could give up using Tkinter's mainloop(), and build your own event loop that polled for both types of events. Or, you could spawn a separate thread to monitor IO. Or, you could use the after() method from Tkinter.
For the first two cases, if IO.read() returns immediately, whether or not there's a result, then you probably want to throw a time.sleep() call in the loop, to avoid hogging the CPU.
If your call to IO.read() doesn't block, and doesn't take very long, it's very easy to set up a loop to poll the device every few milliseconds. All you need to do is something like this:
def read_one_result():
readResult = IO.read()
if readResult != -1:
changeGUI()
root.after(100, read_one_result)
This will read one result, update the GUI if anything was read, and the schedule itself to run again in 100ms.
Related
Is there Python equivalent of VB6 DoEvents statement where the handler will temporarily pass to the OS? I found similar code in http://code.activestate.com/recipes/496767-set-process-priority-in-windows/
def setpriority(pid=None,priority=1):
""" Set The Priority of a Windows Process. Priority is a value between 0-5 where
2 is normal priority. Default sets the priority of the current
python process but can take any valid process ID. """
import win32api,win32process,win32con
priorityclasses = [win32process.IDLE_PRIORITY_CLASS,
win32process.BELOW_NORMAL_PRIORITY_CLASS,
win32process.NORMAL_PRIORITY_CLASS,
win32process.ABOVE_NORMAL_PRIORITY_CLASS,
win32process.HIGH_PRIORITY_CLASS,
win32process.REALTIME_PRIORITY_CLASS]
if pid == None:
pid = win32api.GetCurrentProcessId()
handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, True, pid)
win32process.SetPriorityClass(handle, priorityclasses[priority])
Is there better and more pythonic way?
Your code doesn't do what you say it does. It sets priority for a program.
DoEvents is an internal VB thing that allows VB to suspend its code and run other code in your program. Thus it's dangerous because events can become reentrant.
The part about switching to the OS is done at the end of DoEvents and it calls the Windows API to call Sleep(0) which is sleep 0 seconds but surrender the rest of your 20 ms of time to other processes.
See documentation:
https://learn.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-sleep
More on DoEvents.
In VBA/VB6 all functions/subs/properties/methods run from Sub to End Sub. No other sub/function can run while another is running. DoEvents changes this.
In the distance past of WordBasic etc one used sendkeys to send keystrokes to the application you were running in (Word for Wordbasic). But because your macro was running Word couldn't so didn't update it's state. One used DoEvents with SendKeys to automate things in word that weren't programmable and have it update it's UI. WordBasic didn't have events.
You can't change global variables, can't use global variables that may change, you can't change input parameters, and you have to make sure the sub/function with DoEvents isn't re-entrant (or only a limited number of times). Or bad things happen and it may not be predictable. EG calling doevents in a selectionchange event that changes the selection will eventually crash with out of stack space.
VB6 DoEvents has nothing to do with priority of threads. It was a method to let the system handle the message-loop so it could update the UI. VB6 programs were single-treaded, so if you had a time-consuming task, let us say, some stuff running in a do-while loop then, in order to keep your UI responsive, you had to call DoEvents in that loop. So DoEvents is a UI-thing, and has nothing to do with a programming language per sé.
But...
If you use Python+Tkinter, and for some reason you don't want to use multithreading(preferred). Let us say, you want a fast solution (for testing purpose), then you can use the real equivalent of VB6 DoEvents:
root=Tk()
root.update()
#or
root.update_idletasks()
I am trying to implement a simple spinner (using code adapted from this answer) below a progress bar for a long-running function.
[######## ] x%
/ Compressing filename
I have the compression and progress bar running in the main thread of my script and the spinner running in another thread, so it can actually spin while compression takes place. However, I am using curses for both the progress bar and the spinner, and both use curses.refresh()
Sometimes the terminal will randomly output gibberish, and I'm not sure why. I think it is due to the multi-threaded nature of the spinner, as when I disable the spinner the problem goes away.
Here is the pseudocode of the spinner:
def start(self):
self.busy = True
global stdscr
stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
threading.Thread(target=self.spinner_task).start()
def spinner_task(self):
while self.busy:
stdscr.addstr(1, 0, next(self.spinner_generator))
time.sleep(self.delay)
stdscr.refresh()
And here is the pseudocode for the progress bar:
progress_bar = "\r[{}] {:.0f}%".format("#" * block + " " * (bar_length - block), round(progress * 100, 0))
progress_file = " {} {}".format(s, filename)
stdscr.clrtoeol()
stdscr.addstr(1, 1, " ")
stdscr.clrtoeol()
stdscr.addstr(0, 0, progress_bar)
stdscr.addstr(1, 1, progress_file)
stdscr.refresh()
And called from main() like:
spinner.start()
for each file:
update_progress_bar
compress(file)
spinner.stop()
Why would the output sometimes become corrupted? Is it because of the separate threads? If so, any suggestions on a better way to design this?
The curses libraries that Python's curses module relies on are not threadsafe.
ncurses has a curs_threads feature, which has apparently been there since 5.7 about a decade ago. But it requires changing the way you do a few API calls, and linking against -lncursest, and it's still not trivial, and… almost nobody ever uses it.
As far as I know, no standard installer or distro package ever builds Python curses to link ncursest—even if the distro includes ncursest in the first place, which they often won't. And even if they did, there are no bindings for the threadsafe functions, so you still wouldn't be able to safely access things like setting the tabsize.
In my (possibly out-of-date, and possibly platform-limited) experience, you can nevertheless get away with things, but you need to:
Obviously only one thread can ever call stuff like getch and getmouse.
Add a global Lock, then make sure every batch of updates ends with a refresh, and the whole batch is inside the Lock.
Avoid the Python wrappers around the functionality mentioned in curs_threads—e.g., don't change the escdelay or the tabsize.
Init (and close) the screen from the main thread, before starting (after exiting) the other threads.
If at all possible, make sure you also create all of the windows you need in the main thread. (Hopefully you didn't want any dynamic popup subwindows or anything…)
But the safe way to do this is to do the same kind of thing you do with tkinter or other GUI libraries that don't understand threads. It's not identical, but the idea is similar. The simplest version is:
Move your main thread's work to another background thread.
Add a queue.Queue so that your background threads can ask for curses commands to be run. (You don't need anything complicated to represent a "command", it's just a (func, *args) tuple, because Python.)
Make the main thread loop around popping commands off that queue and calling them.
If your background threads need to call functions that return a value, obviously you need to make this slightly more complicated. You can look at how multiprocessing.dummy.AsyncResult and concurrent.futures.Future work. Or you can even steal Future for your own purposes. But you probably don't need anything as complicated as either.
If you're looping around input, you'll probably also want your main thread to do that (this means picking a "frame rate" and alternating between waiting on the queue and the input, with a timeout) and dispatch it, even if you're always dispatching to the same thread.
You could even write an mtTkinter-style wrapper that reproduces the curses interface (or even monkeypatches the curses module) but replaces each function with a call to put the function and args on a queue. But I'm not sure this would be worth the effort.
If this is the only place where you're using the curses module, the best solution will be to stop using it.
The only functionality of curses that you're really using here is its ability to clear the screen and move the cursor. This can easily be replicated by outputting the appropriate control sequences directly, e.g:
sys.stdout.write("\x1b[f\x1b[J" + progress_bar + "\n" + progress_file)
The \x1b[f sequence moves the cursor to 1,1, and \x1b[J clears all content from the cursor position to the end of the screen.
No additional calls are needed to refresh the screen, or to reset it when you're done. You can output "\x1b[f\x1b[J" again to clear the screen if you want.
This approach does, admittedly, assume that the user is using a VT100-compatible terminal. However, terminals which do not implement this standard are effectively extinct, so this is probably a safe assumption.
I want my QListWidget to update with the new item as it is added, but it only updates with all of the items once the function has ended. I have tried using update() and repaint(), but neither work. I actually had to use repaint() on the Widget itself just to get it to show up before the end, but none of the items do. Here is a brief view of the first item to add:
def runPW10(self):
self.PWList.setVisible(True)
self.PWList.setEnabled(True)
# This repaint() has to be here for even the List to show up
self.PWList.repaint()
....
self.PWList.addItem('Executing Change Password Tool')
# This does not help
self.PWList.repaint()
....
There is more to the function, but it is long and this should include what it needed. Please let me know if more is required.
What am I doing wrong that makes this List not update as the item is added?
Add QApplication.processEvents().
QCoreApplication.processEvents (QEventLoop.ProcessEventsFlags flags = QEventLoop.AllEvents)
Processes all pending events for the calling thread according to the specified flags until there are no more events to process.
You can call this function occasionally when your program is busy performing a long operation (e.g. copying a file).
Your widget originally will be shown but unresponsive. To make the application responsive, add processEvents() calls to some whenever you add an item.
Do keep in mind that this can affect performance a lot. This lets the whole application loop execute including any queued events. Don't add this to performance sensitive loops.
Also consider that this allows your user to interact with the application, so make sure that any interactions that can happen either are not allowed, such as somebutton.enabled(False), or are handled gracefully, like a Cancel button to stop a long task.
See the original C++ docs for further information, since pyqt is a direct port.
To complete Drise's answer on this point:
Also consider that this allows your user to interact with the application, so make sure that any interactions that can happen either are not allowed, such as somebutton.enabled(False), or are handled gracefully, like a Cancel button to stop a long task.
You may want to use the QEventLoop.ExcludeUserInputEvents flag this way: QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents) to refresh the GUI while preventing the user to activate any widgets.
QEventLoop.ExcludeUserInputEvents
0x01
Do not process user input events, such as ButtonPress and KeyPress. Note that the events are not discarded; they will be delivered the next time processEvents() is called without the ExcludeUserInputEvents flag.
So I'm pretty deep into a program I am doing and I realized that with my program, I think I need to implement threading in some manner in order to stop it from locking up.
My program uses Tkinter for it's GUI, and when the program starts, I have a process that is running every second uses Tkinter's after() function. The method in question reads data and appends it to a fixed length deque(). In other parts of my program I have methods which read the last appended value, and process it.
My issue however is a simple loop that goes like this:
value = valDeque.getLastAppendedValue()
while value != "this specific value":
value = valDeque.getLastAppendedValue()
When the inital variable call before the while loop sets value as something other than the specific value I am looking for the program goes into an endless loop and it seems everything else stops functioning.
I am assuming this is because the while loop keeps executing while my after() function that would append the value I am looking for is sitting in limbo waiting for it's turn to execute. This is why I believe I need to use threading, since I can set up my update/append function to run separate of the rest of the processes so I don't have this error.
With that said, I am not super experienced with Python, and have no idea how to integrate threading with the Tkinter after() function since I know Tkinter's mainloop() doesn't interact with with something like time.sleep().
I've tried looking online for some examples to get some headway, but I can't really make heads or tails or what I am finding.
I'm building a Python GUI app using tkinter.
Basically I'm starting and integrating with a different thread, while communication goes using input and output queues.
In the GUI side (the main thread where tkinter's mainloop() goes) I want to add a function which will be called on every iteration of the mainloop (I'm processing and displaying information on real-time).
So my function does something like that:
def loop(self):
try:
output_type, data = wlbt.output_q.get_nowait()
pass # if got something out of the queue, display it!
except Queue.Empty:
pass
self.loop_id = self.after(1, self.loop)
While when starting the program I just call self.loop_id = self.after(1, self.loop).
So two things that bother me:
The loop function raise the CPU usage by 30%-50%. If I disable it then it's good.
I want to be able to use after_idle() to maximize the refresh-rate, but I wasn't able to just replace it - got and error.
I'm sensing there's something I don't fully understand. What can be done to address these issues?
When you call self.after(1, self.loop) you are asking for a function to be run roughly once per millisecond. It's not at all surprising that the CPU usage goes up since you are making 1000 function calls per second.
Given that humans cannot perceive that many changes, if all you're doing is updating the display then there's no reason to do that more than 20-30 times per second.