Tkinter window freezes / lags whenever the user inputs text to text field - python-3.x

I am currently working on a server / client chat program which works fine on terminal but in trying to migrate it into a windowed program I've come across a few errors which I have managed to solve except for this one, for whatever reason, in the GUI class running on the main thread seems to lag considerably whenever the user focuses their attention on it or whenever they try to interact with it, I've tried to use threading but I'm not sure what the problem is.
...
class GUI():
def __init__(self):
self.txtval = []
self.nochngtxtval = []
self.root = tk.Tk()
self.root.title("Chatroom")
self.root.geometry("400x200")
self.labl = tk.Label(self.root, text="", justify=tk.LEFT, anchor='nw', width=45, relief=tk.RIDGE, height=8)
self.labl.pack()
count = 1
while events["connected"] == False:
if events["FError"] == True:
quit()
self.labl["text"] = "Loading"+"."*count
count += 1
if count > 3:
count = 1
self.labl["text"] = ""
self.inputtxt = tk.Text(self.root,height=1,width=40,undo=True)
self.inputtxt.bind("<Return>", self.sendinput)
self.sendButton = tk.Button(self.root, text="Send")
self.sendButton.bind("<Button-1>", self.sendinput)
self.sendButton.pack(padx=5,pady=5,side=tk.BOTTOM)
self.inputtxt.pack(padx=5,pady=5,side=tk.BOTTOM)
uc = Thread(target=self.updatechat,daemon=True)
uc.start()
self.root.protocol("WM_DELETE_WINDOW", cleanAndClose)
self.root.mainloop()
def updatechat(self):
global events
while True:
if events["recv"] != None:
try:
current = events["recv"]
events["recv"] = None
... # shorten to reasonable length (45 characters)
self.labl['text'] = ''
self.txtval = []
self.nochngtxtval.append(new+"\n")
for i in range(len(nochngtxtval)):
self.txtval.append(nochngtxtval[i])
self.txtval.pop(len(self.txtval)-1) # probably not necessary
self.txtval.append(new+"\n") # probably not necessary
self.txtval[len(self.txtval)-1] = self.txtval[len(self.txtval)-1][:-1]
for i in range(len(self.txtval)):
self.labl['text'] = self.labl['text']+self.txtval[i]
except Exception as e:
events["Error"] = str(e)
pass
def sendinput(self, event):
global events
inp = self.inputtxt.get("1.0", "end").strip()
events["send"] = inp
self.inputtxt.delete('1.0', "end")
... # start the logger and server threads
GUI()
I'm using my custom encryption which is linked on github https://github.com/Nidhogg-Wyrmborn/BBR
my server is named server.py in the same git repo as my encryption.
EDIT:
I added a test file which is called WindowClasses.py to my git repo which has no socket interactions and works fine, however the problem seems to be introduced the moment i use socket as displayed in the Client GUI. I hope this can help to ascertain the issue.

Related

Separating Tkinters GUI and control of the application

1) What is my goal:
I’m creating an application that should read data every 60s from ModBusServer, append those data to Graphs and then when the app is closed save the data to excel file.
Site note:
The process of reading data from ModBusServer and appending them to graphs should start after a start button is pressed.
And end after stop button is pressed OR when ModBusServer sends a request to stop.
2) What I have so far:
I created the GUI without any major problems as a class “GUI_komora”.
Everything there works just fine.
3) What is the problem:
But now I’m lost on how to approach the “read data every 60 seconds”, and overall how to control the application.
I did some research on threading but still I’m confused how to implement this to my application.
I learned how to make functions run simultaneously in this tutorial.
And also how to call a function every few seconds using this question.
But none of them helped me to learn how to control the overall flow of the application.
If you could redirect me somewhere or tell me about a better approach I would be really glad.
Some of my code:
from tkinter import *
from GUI_komora import GUI
root = Tk()
my_gui = GUI(root) #my GUI class instance
#main loop
root.mainloop()
"""
How do I achieve something like this???
whenToEnd = False
while whenToEnd:
if step == "Inicialzation":
#inicializace the app
if step == "ReadData":
#read data every 60 seconds and append them to graphs
if step == "EndApp"
#save data to excel file and exit app
whenToEnd = True
"""
Here is an example of a loop that takes a decision (every 60 sec in your case) and pushes the outcome of the decision to tkinter GUI: https://github.com/shorisrip/PixelescoPy/blob/master/base.py
Parts:
main thread - starts tkinter window
control thread - reads some data and decides what to show in GUI
GUI class - has a method "add_image" which takes input an image and displays on GUI.(add_data_to_graph maybe in your case). This method is called everytime by the control thread.
Snippets:
def worker(guiObj, thread_dict):
# read some data and make decision here
time.sleep(60)
data_to_show = <outcome of the decision>
while some_logic:
pictureObj = Picture(chosen, timer)
pictureObj.set_control_thread_obj(thread_dict["control_thread"])
guiObj.set_picture_obj(pictureObj)
pictureObj.display(guiObj)
# do post display tasks
guiObj.quit_window()
# Keep GUI on main thread and everything else on thread
guiObj = GuiWindow()
thread_list = []
thread_dict = {}
thread_for_image_control = threading.Thread(target=worker, args=(guiObj,
thread_dict))
thread_dict["control_thread"] = thread_for_image_control
thread_list.append(thread_for_image_control)
thread_for_image_control.start()
guiObj.run_window_on_loop()
# thread_for_image_control.join()
Code for Picture class:
class Picture:
def __init__(self, path, timer):
self.path = path
self.timer = timer
self.control_thread_obj = None
def set_control_thread_obj(self, thread_obj):
self.control_thread_obj = thread_obj
def display(self, guiObj):
image_path = self.path
guiObj.add_image(image_path)
time.sleep(self.timer)
Code for GUI class
class GuiWindow():
def __init__(self):
self.picture_obj = None
self.root = Tk()
self.image_label = None
self.image = None
self.folder_path = None
self.timer = None
self.root.protocol("WM_DELETE_WINDOW", self.exit_button)
def add_image(self, image_path):
resized_img = self.resize(image_path)
image_obj = ImageTk.PhotoImage(resized_img)
image_label = Label(self.root, image=image_obj,
height=resized_img.height,
width=resized_img.width)
self.image = image_obj # DO NOT REMOVE - Garbage collector error
if self.image_label is not None:
self.remove_image()
image_label.grid(row=0, column=0, columnspan=3)
self.image_label = image_label
Here based on my control loop thread I am changing image (in your case graph data) of the tkinter GUI.
Does this help?

How to make selenium threads run (each thread with its own driver)

I have a python 3 script that needs to make thousands of requests to multiple different websites and check if their source code pass in some pre defined rules.
I am using selenium to make the requests because I need to get the source code after JS finishes it excecution, but due to the high number of urls I need to check, I am trying to make it run multiple threads simultaneously. Each thread creates and maintain an instance of webdriver to make the requests. The problem is after a while all threads go silent and simply stop executing, leaving just a single thread doing all the work. Here is the relevant part of my code:
def get_browser(use_firefox = True):
if use_firefox:
options = FirefoxOptions()
options.headless = True
browser = webdriver.Firefox(options = options)
browser.implicitly_wait(4)
return browser
else:
chrome_options = ChromeOptions()
chrome_options.add_argument("--headless")
browser = webdriver.Chrome(chrome_options=chrome_options)
browser.implicitly_wait(4)
return browser
def start_validations(urls, rules, results, thread_id):
try:
log("thread %s started" % thread_id, thread_id)
browser = get_browser(thread_id % 2 == 1)
while not urls.empty():
url = "http://%s" % urls.get()
try:
log("starting %s" % url, thread_id)
browser.get(url)
time.sleep(0.5)
WebDriverWait(browser, 6).until(selenium_wait_reload(4))
html = browser.page_source
result = check_url(html, rules)
original_domain = url.split("://")[1].split("/")[0].replace("www.","")
tested_domain = browser.current_url.split("://")[1].split("/")[0].replace("www.","")
redirected_url = "" if tested_domain == original_domain else browser.current_url
results.append({"Category":result, "URL":url, "Redirected":redirected_url})
log("finished %s" % url, thread_id)
except Exception as e:
log("couldn't test url %s" % url, thread_id )
log(str(e), thread_id)
results.append({"Category":"Connection Error", "URL":url, "Redirected":""})
browser.quit()
time.sleep(2)
browser = get_browser(thread_id % 2 == 1)
except Exception as e:
log(str(e), thread_id)
finally:
log("closing thread", thread_id)
browser.quit()
def calculate_progress(urls):
progress_folder ="%sprogress/" % WEBROOT
if not os.path.exists(progress_folder):
os.makedirs(progress_folder)
initial_size = urls.qsize()
while not urls.empty():
current_size = urls.qsize()
on_queue = initial_size - current_size
progress = '{0:.0f}'.format((on_queue / initial_size * 100))
for progress_file in os.listdir(progress_folder):
file_path = os.path.join(progress_folder, progress_file)
if os.path.isfile(file_path) and not file_path.endswith(".csv"):
os.unlink(file_path)
os.mknod("%s%s" % (progress_folder, progress))
time.sleep(1)
if __name__ == '__main__':
while True:
try:
log("scraper started")
if os.path.isfile(OUTPUT_FILE):
os.unlink(OUTPUT_FILE)
manager = Manager()
rules = fetch_rules()
urls = manager.Queue()
fetch_urls()
results = manager.list()
jobs = []
p = Process(target=calculate_progress, args=(urls,))
jobs.append(p)
p.start()
for i in range(THREAD_POOL_SIZE):
log("spawning thread with id %s" % i)
p = Process(target=start_validations, args=(urls, rules, results, i))
jobs.append(p)
p.start()
time.sleep(2)
for j in jobs:
j.join()
save_results(results, OUTPUT_FILE)
log("scraper finished")
except Exception as e:
log(str(e))
As you can see, first I thought I could only have one instance of the browser, so I tried to run at least firefox and chrome in paralel, but this still leaves only a thread to do all the work.
Some times the driver crahsed and the thread stopped working even though it is inside a try/catch block, so I started to get a new instance of the browser everytime this happens,but it still didn't work. I also tried waiting a few seconds between creating each instance of the driver still with no results
here is a pastebin of one of the log files:
https://pastebin.com/TsjZdRYf
A strange thing that I noticed is that almost everytime the only thread that keeps running is the last one spawned (with id 3).
Thanks for your time and you help!
EDIT:
[1] Here is the full code: https://pastebin.com/fvVPwPVb
[2] custom selenium wait condition: https://pastebin.com/Zi7nbNFk
Am I allowed to curse on SO? I solved the problem, and I don't think this answer should exist on SO because nobody else will benefit from it. The problem was a custom wait condition that I had created. This class is in the pastebin that was added in edit 2, but I'll also add it here for convenince:
import time
class selenium_wait_reload:
def __init__(self, desired_repeating_sources):
self.desired_repeating_sources = desired_repeating_sources
self.repeated_pages = 0
self.previous_source = None
def __call__(self, driver):
while True:
current_source = driver.page_source
if current_source == self.previous_source:
self.repeated_pages = self.repeated_pages +1
if self.repeated_pages >= self.desired_repeating_sources:
return True
else:
self.previous_source = current_source
self.repeated_pages = 0
time.sleep(0.3)
The goal of this class was to make selenium wait because the JS could be loading additional DOM.
So, this class makes selenium wait a short time and check the code, wait a little again and check the code again. The class repeats this until the source code is the same 3 times in a row.
The problem is that there are some pages that have a js carrousel, so the source code is never the same. I thought that in cases like this the WebDriverWait second parameter would make it crash with a timeoutexception. I was wrong.

How to thread with tkinter in python3 using queue?

I'm trying to thread definitions in tkinter using queue in specifically python3
I've had similar code in python2 work great using a similar method without queue but in python3 from what i've read tkinter doesn't allow for multithreading with gui. I found some examples that uses Queue process. They outline i'm suppose to create an Queue object, a new thread with access to that queue and check for input in the main thread
#!/usr/bin/python3
from tkinter import *
import time
import threading
import queue
import subprocess
def temp_sensor(queue_list):
warning = 0
while True:
var = "cat /sys/class/thermal/thermal_zone*/temp"
temp_control = subprocess.check_output([var], shell=True)
temp_length = len(temp_control)
temp_control = temp_control[35:]
temp_control = temp_control[:-4]
temp_control = int(temp_control)
degree_sign= u'\N{DEGREE SIGN}'
displayed_temp = "Tempature: " + str(temp_control) + degree_sign + "C"
if temp_control > 79:
warning = warning + 1
if warning == 3:
print ("Warning Core Tempature HOT!")
warning = 0
if temp_control > 90:
time.sleep(3)
print ("Warning EXTREMLY to HOT!!!")
queue_list.put(displayed_temp)
time.sleep(1)
class Gui(object):
def __init__(self, queue_list):
self.queue_list = queue_list
self.root = Tk()
self.root.geometry("485x100+750+475")
main_tempature_status = StringVar(self.root)
Ts = Entry(self.root, textvariable=main_tempature_status)
Ts.pack()
Ts.place(x=331, y=70, width=160, height=25)
Ts.config(state=DISABLED, disabledforeground="Black")
self.root.after(1000, self.read_queue)
def read_queue(self):
try:
temp = self.queue.get_nowait()
self.main_tempature_status.set(temp)
except queue_list.Empty:
pass
self.root.after(1000, self.read_queue)
if __name__ == "__main__":
queue_list = queue.Queue()
gui = Gui(queue_list)
t1 = threading.Thread(target=temp_sensor, args=(queue_list,))
t1.start()
gui.root.mainloop()
My desired result is to run a some of these definitions to do various tasks and display their variables in the tkinter entry using python3.
when i run my code it gives me the variable from the queue but it won't post to the GUI. please forgive my less then pythonic code.
Change you Gui class to this:
class Gui(object):
def __init__(self, queue_list):
self.queue_list = queue_list
self.root = Tk()
self.root.geometry("485x100+750+475")
self.main_tempature_status = StringVar(self.root)
self.Ts = Entry(self.root, textvariable=self.main_tempature_status)
self.Ts.pack()
self.Ts.place(x=331, y=70, width=160, height=25)
self.Ts.config(state=DISABLED, disabledforeground="Black")
self.root.after(1000, self.read_queue)
def read_queue(self):
try:
temp = self.queue_list.get_nowait()
self.Ts.config(state=NORMAL)
self.main_tempature_status.set(temp)
self.Ts.config(state=DISABLED)
except queue.Empty:
pass
self.root.after(1000, self.read_queue)
Explanation:
variable main_temperature_status is used in function read_queue as class variable, but not defined as class variable.
You cannot show the change in value of Entry widget if it is always disabled, so enabling it before value change in read_queue.

RaspberryPi Stopwatch (TKinter) with Trigger

i would like to build a stopwatch for my carrera track which i built a little bit bigger.
So I have bought a Raspberry Pi 3 with an additional 7 "touch screen and the individual modules for triggering.
Everything works fine individually.
Now I found a great stopwatch on the net which I also used. Works really great.
In another script, that I've written by myself, the triggering with the gpios works also fantastic.
Now I want to combine both and fail.
Does anyone have an idea or suggested solution where my mistake is?
Here is my code
#!/usr/bin/python
import tkinter as tk
import RPi.GPIO as GPIO
import time
GPIO_TRIGGER_PIN = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(GPIO_TRIGGER_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setwarnings(False)
def update_timeText():
if (state):
global timer
timer[2] += 1
if (timer[2] >= 100):
timer[2] = 0
timer[1] += 1
if (timer[1] >= 60):
timer[0] += 1
timer[1] = 0
timeString = pattern.format(timer[0], timer[1], timer[2])
timeText.configure(text=timeString)
root.after(10, update_timeText)
def start():
global state
state = True
print('Pressed Start')
def stop():
global state
state = False
print('Pressed Stop')
def reset():
global timer
timer = [0, 0, 0]
timeText.configure(text='00:00:00')
print('Pressed Reset')
while GPIO.input(GPIO_TRIGGER_PIN) == True:
if GPIO.input(GPIO_TRIGGER_PIN):
print('CAR DETECTED')
time.sleep(0.1)
state = False
# BULDING TKinter GUI
root = tk.Tk()
root.wm_title('Stopwatch')
timer = [0, 0, 0]
pattern = '{0:02d}:{1:02d}:{2:02d}'
timeText = tk.Label(root, text="00:00:00", font=("Helvetica", 150))
timeText.pack()
startButton = tk.Button(root, text='Start', command=start)
startButton.pack()
stopButton = tk.Button(root, text='Stop', command=stop)
stopButton.pack()
resetButton = tk.Button(root, text='Reset', command=reset)
resetButton.pack()
update_timeText()
root.mainloop()
Currently I get the trigger in the console as output "CAR DETECTED". However, I do not get the TKinter panel.
If I remove
while GPIO.input(GPIO_TRIGGER_PIN) == True:
if GPIO.input(GPIO_TRIGGER_PIN):
print('CAR DETECTED')
time.sleep(0.1)
then the display appears and works. Without triggering.
If I put it all down, I get also the panel but it also triggers nothing more.
Any ideas ?
Thanks for help
Not really my area of expertise, but since nobody else answered, I'll give it a shot.
I think you want to use GPIO.add_event_callback()* to add a callback that gets called when an event happens.
# Callback function
def on_trigger(channel_number):
# Just update the flag
global state
state = False
# Set the callback
GPIO.add_event_callback(GPIO_TRIGGER_PIN , callback=on_trigger)
Then, delete the while loop.
*According to the docs, you need the darksidesync library to use this function. Instead of the callback, you could also create a separate thread. Personally, I would prefer the callback.
from threading import Thread
def thread_function(arg):
global state
# Listen for state change
while GPIO.input(GPIO_TRIGGER_PIN) == True:
if GPIO.input(GPIO_TRIGGER_PIN):
# Update var
state = False
print('CAR DETECTED')
time.sleep(0.1)
thread = Thread(target = thread_function)
thread.start()
Hope this helps.

How to constantly receive requests from socket python

I'm learning about sockets in python and as practice I made a python gui app with a text box, two entry boxes and a button. It works like a chat app, I can run many modules at once, and in each, when a user enters text and click send, it shows the message entered, on the text box of the user's module and all other modules open. I've gotten most of it working but the issue is that it only updates the text box when the send button is pressed but I want to update constantly so as soon as a new message is sent, it shows it on the screen.
Here's my code so far:
#!/usr/bin/python
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.root = tk.Tk()
self.create_widgets()
def create_widgets(self):
... #code that creates widgets shortended for readability
#text box widget named self.txt
self.send_btn["command"] = self.send_msg //this handles sending messages to the server when the button is pressed
... #code that creates widgets shortended for readability
def send_msg(self):
s = socket.socket()
host = socket.gethostname()
port = 9999
address = (host, port)
msg=self.name_bar.get() + ": " +self.input_bar.get()
#name bar is another widget so I can enter an identity for each module
#input bar is the entry box where text is entered in
self.input_bar.delete(0, len(msg))
s.connect(address)
s.send(msg.encode())
#Wrote this code that updates textbox when button pushed
#self.txt.insert(tk.END, s.recv(1024))
#self.txt.insert(tk.END, "\n")
#Method I created to call constantly to update textbox
def rcv_msg(self):
s = socket.socket()
host = socket.gethostname()
port = 9999
address = (host, port)
s.connect(address)
if s.recv(1024).decode() != "":
#self.txt.insert(tk.END, s.recv(1024))
#self.txt.insert(tk.END, "\n")
Also I been doing Java lately so I apologise if my terminology is mixed up.
I've already made the method to update the text box, I just don't know how to call it, I tried a while loop but it just stops the app from running. Also, the server is really simple and just sends the message from the client above to all the all modules of the client open. I din't know if the code was necessary, I was told previously to try to keep questions short but if it's needed please tell me. Thank you!
You could have something listening for new information. A separate thread that doesn't interfere with the GUI. (Pseudo code ahead!)
import socket
from threading import Thread, Lock
import time
class ListenThread(Thread):
def __init__(self, app, lock):
Thread.__init__(self)
self._app = app
self._lock = lock
self._terminating = False
def destroy(self):
self._terminating = True
def run(self):
s = socket.socket()
host = socket.gethostname()
port = 9999
address = (host, port)
while True:
if self._terminating:
break;
if s.recv(1024).decode() != "":
self._lock.acquire()
self._app.post_new_data(s.recv(1024))
self._lock.release()
time.sleep(0.1)
class App(object):
# ...
def __init__(self):
self._lock = Lock()
self._listener = ListenThread(self, self._lock)
self._listener.start()
def post_new_data(self, data):
# Post information to the GUI
def all_data(self):
self._lock.acquire()
# ...
self._lock.release()
def destroy(self):
self._listener.destroy()
# ... Tk mainloop ...
Breakdown
class ListenThread(Thread):
This is the item that will listen for new data coming from other connections and post them to the GUI via self._app.post_new_data(...) call in the run(self) operation.
def run(self):
s = socket.socket()
When we first start the execution on a thread (via start()) we create our socket and get a connection going. This way, all incoming transmissions will route through this in order to keep our GUI free for things it likes to do (paint the interface, take on user input, etc.)
The while loop on the ListenThread.run will keep looking for new data until we've killed the thread. As soon as it receives data, it will push that information to our App. In the post_new_data function, you can add our new data onto the GUI, store it, whatever you would like.
def post_new_data(self, data):
self.txt.insert(tk.END, data)
self.txt.insert(tk.END, "\n")

Resources