How to create a threaded progress bar in Python Tkinter - python

I'm creating a Tkinter GUI application using Python and one of the function in this application is pinging different IP addresses. I'm trying to show an undeterminate progress bar during the pinging process but I can't seem to make it work. Here is what I have tried:
def pingSwitches(ipDictionary):
validIPDictionary = {}
switchCounter = 1
popup = tk.Tk()
popup.wm_title("Processing...")
progress = ttk.Progressbar(popup, orient="horizontal", length=len(ipDictionary.keys()), mode="determinate")
progress.pack()
progress.start(50)
popup.mainloop()
for name, ip in ipDictionary.items():
#progress.step(1)
response = subprocess.Popen(["ping", "-n", "1", ip],stdout = subprocess.PIPE).communicate()[0]
if((b"unreachable" in response) or (b"timed out" in response) or (b"demande" in response)):
print(ip + " is down")
else:
validIPDictionary[name] = ip
print(ip + " is up")
print("Pinged " + str(switchCounter) + " of " + str(len(ipDictionary.keys())) + " total ips")
switchCounter = switchCounter + 1
progress.stop()
popup.destroy()
return validIPDictionary
The problem is when I run the program, I can see that the code stops at the popup.mainloop() line and it does not continue. If I put the popup.mainloop() after the pinging process, the popup appears after the pinging is done, rendering it useless.
How can I make the popup appear while my code is pinging? Is it only by multithreading?

Related

How can I make tkinter wait for a line of code to finish before continuing?

so I am making a program on tkinter that gets a response from a server and depending on the answer, it will change the background color, to either green for success or red for error, the problem is that I realized that when running the code, the windows.after() method doesn't wait till is done to continue and when I do the request for the server, it have to do it three times to check if the response is correct, and it is suppossed to change the window background color each time, but it is only doing it one time. And not only the background color changing fails, also I want to change a label's text when it is doing the request,but it does it really quick and I'm not able to diferentiate the changes, so the question is: how can I
How can I make the program wait until one line finishes running to go to the next one and not everything happens at the same time and so fast?
Here is a piece of my code, I removed the request part because I'm trying to solve this problem first:
# import gpiozero
# import picamera
import json
import requests
import tkinter as tk
with open("config.json") as file:
config = json.load(file)
ENDPOINT = config["ENDPOINT"]
USUARIO = config["USUARIO"]
ESTACION = config["ESTACION"]
TIEMPO_ESPERA = config["TIEMPO_ESPERA"]
PIN_RELE = config["PIN_RELE"]
PATH_SALIDA = ENDPOINT + "Salida.getTicket/" + ESTACION + "/" + USUARIO + "/"
barcode = ""
# RELAY = gpiozero.OutputDevice(PIN_RELE, active_high=True, initial_value=False)
# CAMERA = picamera.PiCamera()
def check_scan_barcode(event=None):
info_label.config(text = "Wait...")
barcode = barcode_entry.get()
barcode_entry.delete(0, "end")
for i in range(3):
response = get_request(ENDPOINT + barcode)
if response["data"] == "True":
success()
open_barrier()
else:
error()
info_label.config(text = "Scan barcode")
def get_request(url):
response = requests.get(url)
response.raise_for_status()
response = response.json()
return response
def normal():
window.configure(bg="white")
info_label.configure(bg="white")
def success():
window.configure(bg="green")
info_label.configure(bg="green")
window.after(1000, normal)
def error():
window.configure(bg="red")
info_label.configure(bg="red")
window.after(1000, normal)
def open_barrier(barcode):
# CAMERA.capture(f"/home/pi/Pictures{barcode}.jpg")
# RELAY.on()
# window.after(TIEMPO_ESPERA, RELAY.off)
pass
window = tk.Tk()
# window.attributes('-fullscreen', True)
info_label = tk.Label(window, text= "Scan barcode.", font=("Arial", 40))
info_label.pack()
barcode_entry = tk.Entry(window, width=50)
barcode_entry.bind('<Return>', check_scan_barcode)
barcode_entry.pack(expand=True)
barcode_entry.focus()
window.mainloop()

Threading stops main program in Python3

Hey I am runnig my python code under Linux on a Raspberry Pi and it consists of a main.py file that opens threads to do various tasks
For more insights:
my filestructure looks like this:
main.py
modules
----controller.py
----server.py
----reader.py
----logger.py
----dbhandling.py
-the controller has a loop in witch it looks for new entrys in the database and reacts to them
-the server is just a flask application
-the logger has a cons_log() function in which I can output data in the same format to the terminal. It uses termcolor, which doesnt work with the coderunner output, thats why there is this weird asci shown sometimes
-the dbhandler is just there to read and write the db conveiniently
-the reader reads out a rfid reader via the raspberrypi gpio. It listens all the time for new rfid tags.
The whole main.py looks like this:
import time
import threading
from datetime import datetime
from modules.server import Serv
from modules.logger import cons_log
from modules.reader import ReaderInteraction as ReaderI
from modules.controller import ControllerLoop
from modules.dbhandling import DBinteractions as Db
while True:
try:
cons_log("MAIN", "Starting ControllerLoop...", "yellow", "0")
controller_threat = threading.Thread(target=ControllerLoop().loop)
controller_threat.name = "Controller Threat"
controller_threat.start()
time.sleep(1)
cons_log("MAIN", "Successfully started ControllerLoop", "green", "202")
controller_threat.join()
break
except Exception as e:
cons_log(
"MAIN", "Could not start ControllerLoop, Error: \n" + str(e), "red", "404"
)
time.sleep(5)
while True:
try:
cons_log("MAIN", "Starting Server...", "yellow", "0")
server_threat = threading.Thread(target=Serv)
server_threat.name = "Server Threat"
server_threat.start()
time.sleep(1)
cons_log("MAIN", "Successfully started Server", "green", "202")
server_threat.join()
break
except Exception as e:
cons_log("MAIN", "Could not start Server, Error: \n" + str(e), "red", "404")
time.sleep(5)
while True:
try:
cons_log("MAIN", "Starting Reader...", "yellow", "0")
reader_threat = threading.Thread(target=ReaderI().runreader)
reader_threat.name = "Reader Threat"
reader_threat.start()
time.sleep(1)
cons_log("MAIN", "Successfully started Reader", "green", "202")
reader_threat.join()
break
except Exception as e:
cons_log("MAIN", "Could not start Reader, Error: \n" + str(e), "red", "404")
time.sleep(5)
The controller looks like this:
# module used to control the hardware and electric systems via constant database readouts
import RPi.GPIO as GPIO
import time
class ControllerLoop:
def __init__(self):
None
def loop(self):
while True:
time.sleep(0.3)
# check for new controlls to do based on entrys in database
The server looks like this:
from flask import Flask, flash
def Serv():
app = Flask(__name__, template_folder="../templates")
# all the routs in here
app.secret_key = "tpsecret..."
app.run(debug=False, port=8000, host="0.0.0.0")
The reader looks like this:
import time
import RPi.GPIO as GPIO
from mfrc522 import SimpleMFRC522
class ReaderInteraction:
def __init__(self):
self.reader = SimpleMFRC522()
def runreader(self):
while True:
try:
id, text = self.reader.read()
print(id)
time.sleep(1)
finally:
GPIO.cleanup()
The logger looks like this:
from datetime import datetime
from termcolor import colored
from time import time
from flask import Flask, redirect, flash, url_for
def cons_log(process, message, color, code):
flask_ip = "127.0.0.1"
time = datetime.today()
print(
flask_ip
+ " - - ["
+ time.today().strftime("%d/%m/%Y")
+ " "
+ time.today().strftime("%H:%M:%S")
+ "] "
+ '"'
+ colored(process + " " + message + '"', color)
+ " "
+ code
+ "-"
)
def flask_log(process, message, color, code, flashmsg, flashtype, redir):
# def to communicate with user
flask_ip = "127.0.0.1"
time = datetime.today()
print(
flask_ip
+ " - - ["
+ time.today().strftime("%d/%m/%Y")
+ " "
+ time.today().strftime("%H:%M:%S")
+ "] "
+ '"'
+ colored(process + " " + message + '"', color)
+ " "
+ code
+ "-"
)
flash(flashmsg, flashtype)
return redirect(url_for(redir))
When using .join() at the end of the main.py for all threads the output stops after the first .join() call.
Question: If I use the .join() on the controllerloop does the main program then waits with the execution of the next code untill the controllerloop is finished? Because if so the controllerloop is running endless and the main would never continue right?

tkinter unable to print a label before using root.destroy()

I want to print a message on the UI when the user wishes to exit.
This is a part of code relevant to it
if word in quit_words:
user_to_quit = True
Print = "Pleasure serving you!"
print_bot_reply(Print)
time.sleep(5)
root.destroy()
The print bot_reply function is as follows:
def print_bot_reply(response):
response = "\nKalpana: " + str(response)
label = Label(frame,text = response,bg="#b6efb1", borderwidth=5, relief="raised")
label.pack(anchor = "w")
The window is closing after 5 seconds, as desired but not displaying the message.
Please point me the mistake or suggest some other method to do this
Thanks!

Python threaded tkinter GUI unresponsive unless sleep time is used.

I'm using python 2.7 and have built a UI using Tkinter. I'm using threads and queues to keep the UI responsive while the main script is working. The basic summary is the script reads a text file, parses out some information on it and puts that info in a dictionary and a list in the dictionary, then uses that info to send TCP modbus request (using pyModbus). It then writes the responses/results to a text file. The results also get printed a Text widget included in the UI. The updates to the Text widget is handled by the mainloop.
I'm still fairly new to threads and queues and I'm having trouble figuring out this issue.
The problem I'm running into is I need to include a ~10ms sleep after it loops through each item in the list for the UI to remain responsive. If I include the sleep time it works as expected, if not it freezes up until the threaded process is finished then updates the UI all at once (as it would if threads weren't used). The 10ms sleep can be slightly shorter. Any amount longer also works.
Here's the code that handles updating the log:
textQueue = Queue.Queue()
def TextDisplay(message, disTime="false", myColor="black", bgColor="white"):
textQueue.put([message, disTime, myColor, bgColor])
class LogUI:
def __init__(self, master):
self.master = master
'''other ui elements, not relevent'''
self.mainLogFrame = Frame(self.master)
self.mainLogFrame.pack(side="top", fill="both", expand="yes", padx=5, pady=2)
self.logText = Text(self.mainLogFrame, height=2)
self.logText.pack(side="left", fill="both", expand="yes", padx=5, pady=2)
self.ThreadSafeTextDisplay()
def ThreadSafeTextDisplay(self):
while not textQueue.empty():
tempText = textQueue.get(0)
message = tempText[0]
disTime = tempText[1]
myColor = tempText[2]
bgColor = tempText[3]
message = str(message) + "\n"
'''bunch of formating stuff'''
logUI.logText.insert(END, message)
print message
#NOTE: tried to include a sleep time here, no effect
self.logText.after(10, self.ThreadSafeTextDisplay)
Here's the non-threaded function that's called when the user clicks a button.
def ParseInputFile():
'''non-threaded function, called when user clicks button'''
inputList = []
inputFile = mainUI.fullInFileEntry.get()
with open(inputFile, 'r') as myInput:
'''open file and put contents in list'''
for line in myInput:
inputList.append(line.strip())
outFile = mainUI.outFileEntry.get().strip() + '.txt'
i = 1
tableBol = False
inputDict = {}
inputKeys = []
tableID = None
for item in inputList:
'''parses out inputKeys, inputDict using regular expressions'''
runInputGetQueue.put([inputKeys, inputDict, outFile, inputFile])
Here's the threaded function that receives the parsed information and handles the modbus request (note: i tried commenting out the actual modbus request, no effect):
def RunInputThread():
time.sleep(.1)
while 1:
while not runInputGetQueue.empty():
tempGet = runInputGetQueue.get(0)
inputKeys = tempGet[0]
inputDict = tempGet[1]
outFile = tempGet[2]
inputFile = tempGet[3]
outFile = open(outFile, 'w')
TextDisplay('< Start of %s input file > ' % inputFile, True, 'blue')
for key in inputKeys:
'''loops through the keys in the dictionary'''
TextDisplay(key) #just used as an example.
for lineIndex in range(len(inputDict[key]['tableLines'])):
'''lots of code that loops thorugh the lines of input file, frequently calls the TextDisplay() function'''
TextDisplay(inputDict[key][lineIndex]) #just used as an example.
time.sleep(0.01) #UI will become unresponseive if not included.
outFile.close()
time.sleep(0.001)
Found a way to get the UI mostly responsive. As stated in the comments above, the queue was receiving stuff to fast the function would be constantly working causing the UI to lock up. I made it so it will print at most 5 messages before taking a 1ms break and recalling the function which allows the UI to 'catch up.' The messages are printed almost as fast as they come in.
The UI will be slightly non-responsive if you move it or resize it. I have no issues interacting with other UI elements while this is running though.
You could also change the while loop to a if statement if you don't mind it being slow. The one process i ran went from 14 seconds with an if statement down to around 5 or 6 seconds using the code below. It would be same as if you changed the pullCount break point to 1 instead of 5.
def ThreadSafeTextDisplay(self):
pullCount = 0
while not textQueue.empty():
pullCount += 1
tempText = textQueue.get(0)
message = tempText[0]
disTime = tempText[1]
myColor = tempText[2]
bgColor = tempText[3]
message = str(message) + "\n"
'''bunch of formatting stuff'''
logUI.logText.insert(END, message)
print message
if pullCount >= 5: break #you can change the 5 value to whatever you want, the higher the number the faster stuff will print but the UI will start to become unresponsive.
self.logText.after(1, self.ThreadSafeTextDisplay)

Continuing code after executing a File - Python

I have made a Simple GUI that launches when i run my Twitch TV IRC Bot. But, the main bot doesn't continue until after i close the GUI. How would i make it so the Script runs at the same time as the GUI?
This is the GUI:
##--GUI--##
def whitelist_wipe():
execfile('command_whitelist_wipe.py')
def modpack():
modpack = modpack_input.get()
return
root = Tk()
modpack_input = StringVar()
root.title('Geekster_Bot')
root.geometry('450x450+500+300')
WhitelistButton = Button(root, text = 'Clear Whitelist!', command + whitelist_wipe).pack()
SubmitButton = Button(root, text = 'Change Modpack', command = modpack).pack()
ModpackName = Entry(root, textvariable=modpack_input).pack()
root.mainloop()
This is the part where it launches the GUI from the main Bot scrpit:
try:
execfile('GUI.py')
except:
print('Error loading GUI')
How would i continue the code with the GUI open?
EDIT
GUI.py;
##--GUI--##
def whitelist_wipe():
execfile('command_whitelist_wipe.py')
def modpack_var():
modpack_input = modpack_user.get()
root = Tk()
modpack_user = StringVar()
root.title('Geekster_Bot')
root.geometry('350x100+500+300')
Label1 = Label(root, text = 'Geekster_Bot Controls!').pack()
WhitelistButton = Button(root, text = 'Clear Whitelist!', command =whitelist_wipe).pack(side = LEFT)
SubmitButton = Button(root, text = 'Change Modpack', command = modpack_var).pack()
ModpackName = Entry(root, textvariable=modpack_user).pack()
root.mainloop()
Main Bot;
try:
#Create thread
gui_thread = threading.Thread( target = execfile, args = ('GUI.py',) )
#Start thread
gui_thread.start()
#Code to run in the main thread
except:
print('Error loading GUI')
def message(msg): #function for sending messages to the IRC chat
global queue
queue = queue + 1
print queue
if queue < 20: #ensures does not send >20 msgs per 30 seconds.
twitch = ('OUTPUT ON ' + channel + ' :' + msg + '\r\n')
irc.send('PRIVMSG ' + channel + ' :' + msg + '\r\n')
print (twitch)
else:
print 'Message deleted'
def socialtimer(): #function for announcing social every 3 minutes
global ntimer
z = open(r'E:\Geekster_Bot\Twitter.txt')
SOCIAL = z.read()
message (SOCIAL)
print 'Social Timers Started!'
ntimer = threading.Timer(1200,socialtimer)
ntimer.start()
def queuetimer(): #function for resetting the queue every 30 seconds
global queue
print 'queue reset'
queue = 0
threading.Timer(30,queuetimer).start()
def modpack_var():
modpack_input = modpack_user.get()
##--Main Bot--##
#General variables
newsmsg = 'whitelist'
modpack = modpack_input
Since the gui itself runs in a loop it blocks the thread you start it in until you stop the loop (close the gui). You need to start the gui in a different thread from the main one.
You can do this using the threading module:
import threading
try:
#Create thread
gui_thread = threading.Thread( target = execfile, args = ('GUI.py',) )
#Start thread
gui_thread.start()
#Code to run in the main thread
except:
print('Error loading GUI')
In this code the gui_thread object is a thread, which runs the execfile callable (a function in this case) with the parameter 'GUI.py' on start.
Check the threading module documentation for further info: https://docs.python.org/3.3/library/threading.html

Categories