Label in PyQt4 GUI not updating with every loop of FOR loop - python

I'm having a problem, where I wish to run several command line functions from a python program using a GUI. I don't know if my problem is specific to PyQt4 or if it has to do with my bad use of python code.
What I wish to do is have a label on my GUI change its text value to inform the user which command is being executed. My problem however, arises when I run several commands using a for loop. I would like the label to update itself with every loop, however, the program is not updating the GUI label with every loop, instead, it only updates itself once the entire for loop is completed, and displays only the last command that was executed.
I am using PyQt4 for my GUI environment. And I have established that the text variable for the label is indeed being updated with every loop, but, it is not actually showing up visually in the GUI.
Is there a way for me to force the label to update itself? I have tried the update() and repaint() methods within the loop, but they don't make any difference.
I would really appreciate any help.
Thank you.
Ronny.
Here is the code I am using:
# -*- coding: utf-8 -*-
import sys, os
from PyQt4 import QtGui, QtCore
Gui = QtGui
Core = QtCore
# ================================================== CREATE WINDOW OBJECT CLASS
class Win(Gui.QWidget):
def __init__(self, parent = None):
Gui.QWidget.__init__(self, parent)
# --------------------------------------------------- SETUP PLAY BUTTON
self.but1 = Gui.QPushButton("Run Commands",self)
self.but1.setGeometry(10,10, 200, 100)
# -------------------------------------------------------- SETUP LABELS
self.label1 = Gui.QLabel("No Commands running", self)
self.label1.move(10, 120)
# ------------------------------------------------------- SETUP ACTIONS
self.connect(self.but1, Core.SIGNAL("clicked()"), runCommands)
# ======================================================= RUN COMMAND FUNCTION
def runCommands():
for i in commands:
win.label1.setText(i) # Make label display the command being run
print win.label1.text() # This shows that the value is actually
# changing with every loop, but its just not
# being reflected in the GUI label
os.system(i)
# ======================================================================== MAIN
# ------------------------------------------------------ THE TERMINAL COMMANDS
com1 = "espeak 'senntence 1'"
com2 = "espeak 'senntence 2'"
com3 = "espeak 'senntence 3'"
com4 = "espeak 'senntence 4'"
com5 = "espeak 'senntence 5'"
commands = (com1, com2, com3, com4, com5)
# --------------------------------------------------- SETUP THE GUI ENVIRONMENT
app = Gui.QApplication(sys.argv)
win = Win()
win.show()
sys.exit(app.exec_())

The label gets updated all right, but the GUI isn't redrawn before the end of your loop.
Here's what you can do about it:
Move your long-running loop to a secondary thread, drawing the GUI is happening in the main thread.
Call app.processEvents() in your loop. This gives Qt the chance to process events and redraw the GUI.
Break up your loop and let it run using a QTimer with a timeout of 0.
Using a thread is the best option, but involves quite a bit more work than just calling processEvents. Doing it with a timer is the old fashioned way and is not recommanded anymore. (see the documentation)

You have a basic misunderstanding of how such a GUI works. A Qt GUI has to run in an event loop of its own. Your loop runs instead, and the GUI can't do its work between the executions of your loop. That is, while your for loop is running the GUI code doesn't get CPU time and won't update.
You can set up a timer with an event, and execute your code in handlers of this event a set amount of time - this will solve your problem.

Or you can just call repaint() it update the GUI instantly.

Related

window freezes in Python 3

I am using python3 on a mac and run scripts with the IDLE which comes automatically with the python3 installation.
I am trying to make an alert to the user and found the command
tkinter.messagebox.showinfo("title","some text")
So I i tried a minimal script to check if I can get along with that command
import tkinter
tkinter.messagebox.showinfo("test" , "blabla")
The window is displayed correctly but it doesn't respond when I click on the "OK" button.
Addtionally there is a second empty window which appears when I start the script.
What is the explanation for this or at least how can I fix that?
tkinter isn't designed to work this way. Every tkinter requires a root window. If you don't explicitly create one (and you didn't), one will be created for you. That's what the blank window is.
Also, a tkinter GUI can't function properly unless it has a running event loop. This is necessary because some functions, such as responding to buttons and redrawing the window, only happens in response to events. If the event loop isn't running, events can't be processed.
Bottom line: the dialogs aren't designed to be used outside of the context of a proper tkinter app.
Wrapper for standalone use
The following code can be used to display one of the dialogs in standalone mode. It works by creating and hiding a root window, displaying the dialog, and then destroying the root window.
import tkinter as tk
from tkinter import messagebox
def show_dialog(func, *args, **kwargs):
# create root window, then hide it
root = tk.Tk()
root.withdraw()
# create a mutable variable for storing the result
result = []
# local function to call the dialog after the
# event loop starts
def show_dialog():
# show the dialog; this will block until the
# dialog is dismissed by the user
result.append(func(*args, **kwargs))
# destroy the root window when the dialog is dismissed
# note: this will cause the event loop (mainloop) to end
root.destroy()
# run the function after the event loop is initialized
root.after_idle(show_dialog)
# start the event loop, then kill the tcl interpreter
# once the root window has been destroyed
root.mainloop()
root.quit()
# pop the result and return
return result.pop()
To use it, pass the dialog you want as the first option, followed by dialog-specific options.
For example:
result = show_dialog(messagebox.askokcancel, "title", "Are you sure?")
if result:
print("you answered OK")
else:
print("you cancelled")

How to open a popup window with a spinner in python + Gtk

I have a python Gtk application, with the GUI designed in Glade. It has a "scan" feature which will scan the network for a few seconds and then report its results to the user. During the scanning I want a popup window to appear stealing the focus from the parent until scanning is done.
I use a threading.Lock to synchronize the GUI and the scan thread, which makes the popup to last exactly the right time I want (see scanLock.acquire() ). It seems straightforward to me to implement something like a show() and hide() call before and after the scanLock.acquire(). I did use waitPopupShow and waitPopupHide instead of just calling the window.show() and window.hide() because I also may want to set the Label in the popup or start/stop the GtkSpinner. Here is some code from the GUI class:
def scan(self):
sT = scannerThread(self,self.STagList)
self.dataShare.threadsList.append(sT)
sT.start() # start scanning
self.waitPopupShow('Scanning... Please Wait')
self.scanLock.acquire() # blocks here until scan is finished
self.waitPopupHide()
def waitPopupShow(self, msg): # shows a GtkSpinner until the semaphore is cleared
self.waitDialogLabel.set_text(msg)
self.waitDialogBox.show_all()
self.waitDialog.show()
self.waitDialogSpinner.start()
def waitPopupHide(self):
# how to get the handle to the spinner and stop it?
self.waitDialogSpinner.stop()
self.waitDialog.hide()
def getAll(self):
# GUI
self.builder = Gtk.Builder()
self.builder.add_from_file(path to main GUI)
# ... getting stuff from a first glade file
# getting stuff from the waitDialog glade file
self.builder.add_from_file(path to waitDialog GUI)
self.waitDialog = self.builder.get_object("waitDialog") # GtkWindow
self.waitDialogBox = self.builder.get_object("waitDialogBox") # GtkBox
self.waitDialogLabel = self.builder.get_object("waitDialogLabel") # GtkLabel
self.waitDialogSpinner = self.builder.get_object("waitDialogSpinner") # GtkSpinner
self.waitDialog.hide()
I'm trying hardly since a couple of days to show a dialog with a label and a Gtk.Spinner. The best I obtain at the moment is to have the window showing up with no content. Please note that the self.waitDialog.hide() right after getting it with self.builder.get_object is needed because I set the property of the waitDialog Gtkwindow to Visibile. If I stop with the debugger before .hide() the waitDialog shows up perfectly. Afterwards its broken.
This is the waitDialog GUI file: http://pastebin.com/5enDQg3g
So my best guess is that I'm dooing something wrong, and I could find nothing on creating a new Gtk window over the main one, only basic examples and dialogs. A pointer to the documentation saying a bit about this would be a good starting point...

Scrolling Progress Bar in Tkinter

I have been trying to set up a progress bar in a python tkinter gui that shows that a process is running. The process is long and I have no way to really measure the progress, so I need to use an indeterminate progress bar. However, I really dislike the style of the ttk indeterminate progress bar that bounces back and forth. I want one that scrolls across the bar over and over again, kind of like this image
Is this possible with tkinter?
have you tried ttk's determinate Progressbar? You can make the progress just continuously scroll across the bar.
for example:
#!/usr/bin/env python3
import tkinter
import tkinter.ttk as ttk
root = tkinter.Tk()
frame = ttk.Frame()
pb = ttk.Progressbar(frame, length=300, mode='determinate')
frame.pack()
pb.pack()
pb.start(25)
root.mainloop()
I know its an old question, but I have found a way to do this for anyone else writing tkinter.
I've been working on a tkinter app for a bit now and have determined that to handle tkinter objects, you absolutely need a separate thread. Although it is apparently frowned upon to handle tkinter objects via something else than the mainloop() method, it has been working well for me. I've never had a main thread is not in main loop error and never experienced objects that didn't update correctly.
I edited Corey Goldberg's code a bit and got it working. Here's what I got (some explanations in the comments).
import tkinter
import tkinter.ttk as ttk
import threading
def mainProgram(): # secure the main program initialization in its own def
root = tkinter.Tk()
frame = ttk.Frame()
# You need to use indeterminate mode to achieve this
pb = ttk.Progressbar(frame, length=300, mode='indeterminate')
frame.pack()
pb.pack()
# Create a thread for monitoring loading bar
# Note the passing of the loading bar as an argument
barThread = threading.Thread(target=keepLooping, args=(pb,))
# set thread as daemon (thread will die if parent is killed)
barThread.daemon=True
# Start thread, could also use root.after(50, barThread.start()) if desired
barThread.start()
pb.start(25)
root.mainloop()
def keepLooping(bar):
# Runs thread continuously (till parent dies due to daemon or is killed manually)
while 1:
"""
Here's the tricky part.
The loading bar's position (for any length) is between 0 and 100.
Its position is calculated as position = value % 100.
Resetting bar['value'] to 0 causes it to return to position 0,
but naturally the bar would keep incrementing forever till it dies.
It works, but is a bit unnatural.
"""
if bar['value']==100:
bar.config(value=0) # could also set it as bar['value']=0
if __name__=='__main__':
mainProgram()
I've added if __name__=='__main__': because I feel it defines the scope a bit better.
As a side note I've found that running threads with while 1: will crank my CPU at about 20-30% usage for that one thread in particular. It's easily solvable by importing time and using time.sleep(0.05) thereafter significantly lowering the CPU usage.
Tested on Win8.1, Python 3.5.0.

Python - How do I continuously repeat a sequence without a While loop and still be able to stop the sequence at any time

I have a Raspberry Pi with the Piface adaptor board. I have made a GUI which controls the LED's on the Piface board.
I wrote a small piece of code to make the LED's run up and down continuously, like Knight Riders car, using a While loop.
I then wrote another piece of code that created a GUI. In the GUI is a button that starts the LED's running up and down continuously with the While loop piece of code.
What I want to do is to have that GUI button start the LED running sequence, and then the same button stop the sequence at any time.
I do understand that the code is sitting/stuck in the While loop. And hence any buttons in the GUI are not going to have an effect.
So is there a better way of doing it? Any pointers would be appreciated.
Thanks in advance.
Another option is to run the LED while loop in a separate thread. Like in
the next code. The while loop is stopped by toggling the shared led_switch
variable.
"""
blinking LED
"""
import tkinter as tk
import threading
import time
led_switch=False
def start_stop():
global led_switch
led_switch=not led_switch
if led_switch:
t=threading.Thread(target=LED)
t.start()
def LED():
while led_switch:
print('LED on')
time.sleep(1)
print('LED off')
time.sleep(1)
root=tk.Tk()
button=tk.Button(root,command=lambda: start_stop(),text='start/stop')
button.pack()
tk.mainloop()
If you have a while loop and a GUI you can use generators to still use the loop and let the GUI run properly.
I sketch the Idea here and create an example for the Tkinter GUI.
You want to write your code as a loop and still use it in a GUI:
from Tkinter import *
from guiLoop import guiLoop # https://gist.github.com/niccokunzmann/8673951
#guiLoop
def led_blink(argument):
while 1:
print("LED on " + argument)
yield 0.5 # time to wait
print("LED off " + argument)
yield 0.5
t = Tk()
led_blink(t, 'shiny!') # run led_blink in this GUI
t.mainloop()
Output while the GUI is responsive:
LED on shiny!
LED off shiny!
LED on shiny!
LED off shiny!
...
Sadly Tkinter is the only GUI I know and it is a bad example because you can always update the GUI in your loop with the update() method of GUI elements:
root = Tk()
while 1:
print("LED on")
t = time.time() + 0.5
while t > time.time(): root.update()
print("LED off")
t = time.time() + 0.5
while t > time.time(): root.update()
But with such a guiLoop you can have multiple loops:
t = Tk()
led_blink(t, 'red')
led_blink(t, 'blue')
led_blink(t, 'green')
t.mainloop()
Here are some examples for starting and stopping the loop with a button.
If you're using Tkinter, there's a very easy pattern for running a loop. Given that the UI (in just about every UI toolkit) is already running an infinite loop to process events, you can leverage this to run code periodically.
Let's assume you have a python object "led" which has a method for toggling it on and off. You can have it switch from on to off every 100ms with something as simple as these three lines of code:
def blink(led):
led.toggle()
root.after(100, blink, led)
The above code will run forever, causing the led to blink every 100ms. If you want to be able to start and stop the blinking with a button, introduce a flag:
def blink(led):
if should_blink:
led.toggle()
root.after(100, blink, led)
When you set the toggle to True, the led will start blinking. When it's False, it will stop blinking.
The main thing to take away from this is that you already have an infinite loop running, so there's no need to create one of your own, and no need to use something as complex as threading. Simply create a function that does one frame of animation, or calls some function or does some unit of work, then have the function request that it be run again in the future. How far in the future defines how fast your animation or blink will run.

Update a Tkinter text widget as it's written rather than after the class is finished

I'm in a bind, since this is being written on a classified machine I am unable to copy+paste here. Being somewhat a novice, my approach is probably unorthodox.
I have a GUI written in Tkinter with several buttons. Each button is linked to a class that, in effect, runs a short script. When the button is clicked, I inititalize a class log_window which is simply a Tkinter text widget. I then create a global variable linking log to the log_window I just created, and as the script runs I pipe sys.stdout/stderr to log (I created a write method specifically for this). Everything is kosher, except that the log_window text widget doesn't update with my piped stdout until after the class calling it is finished. However, if I simply print within the class, it will print in the order it is called.
Example
import Tkinter
from Tkinter import *
import time
class log_window:
def __init__(self,master):
self.textframe = Tkinter.Frame(master)
self.text = Text(self.textframe)
self.text.pack()
self.textframe.pack()
def write(self,text):
self.text.insert(END,text)
class some_func1: # This effectively waits 5 seconds then prints both lines at once
def __init__(self,master):
log.write("some text")
time.sleep(5)
log.write("some text")
class some_func2: # This prints the first object, waits 5 seconds, then prints the second
def __init__(self,master):
print "some text"
time.sleep(5)
print "some text"
if __name__ == '__main__':
global log
root = Tk()
log = log_window(root)
root.after(100,some_func1, root)
root.after(100,some_func2, root)
root.mainloop()
Sorry if my example is a little bit muffed, but I think it makes the point. The piping I do is through Popen and some system calls, but they aren't part of the issue, so I only highlighted what, I presume, is the LCD of the issue.
I don't know the details of Tkinter's concurrency, but fiddling around reveals that if you put
master.update_idletasks()
after each call to log.write, it updates on cue. You could give log a .flush() method to do that (like file handles have), or you could just make log.write call it after writing.
When you call sleep it causes your whole GUI to freeze. You must remember that your GUI runs an event loop, which is an infinite loop that wraps all your code. The event loop is responsible for causing widgets to redraw when they are changed. When a binding is fired it calls your code from within that loop, so as long as your code is running, the event loop can't loop.
You have a couple of choices. One is to call update_idletasks after adding text to the widget. This lets the event loop service "on idle" events -- things that are schedule to run when the program isn't doing anything else. Redrawing the screen is one such event, and there are others as well.
The other option is to run your functions in a thread or separate process. Because Tkinter isn't thread safe, these other threads or processes can't directly communicate with the GUI. What they must do is push a message onto a queue, and then your main (GUI) thread must poll the queue and pull messages off. It would be easy to build this code into your log class, and polling the queue can be done using the event loop -- just write a method that pulls messages off the queue and inserts them into the widget, the calls itself using after a few hundred milliseconds later.
You have to update your widget content by adding self.text.update() after self.text.insert(END,text)

Categories

Resources