Slow while loop in tkinter after reboot - python-3.x

i'm trying to add some windows on the top of an existing vb6 app.
`
from threading import Thread
from tkinter import *
import pyautogui
import win32gui
import time
#coordinates of the target image that i use to decide if i have to show the tk windows
target=(83,396)
target2=(612,73)
targetp=(153,204,0)
target2p=(247,87,70)
#windows geometry
wg1=(221,229,795,160)
wg2=(154,162,11,482)
f = True
#names of the vb program's windows that have to cover the left window
ws = ["Parametri di lavorazione", "Opzioni", "Configurazione tabelle DXF"]
#windows thread
def wins():
global win10
global win20
win10 = Tk()
win10.overrideredirect(True)
win10.geometry('%dx%d+%d+%d' % wg1)
img1 = PhotoImage(file = "c:\\users\\utente\\desktop\\img1.png")
l1= Label(win10, i=img1)
l1.pack()
win20 = Toplevel(win10)
img2 = PhotoImage(file = "c:\\users\\utente\\desktop\\img2.png")
l2 = Label(win20, i=img2)
l2.pack()
win20.geometry('%dx%d+%d+%d' % wg2)
win20.overrideredirect(True)
win10.mainloop()
#thread to check if the windows have to be showed based on the first target
def cl():
global win10
global win20
s = True
while True:
if s:
if pyautogui.pixel(target[0],target[1]) != targetp:
win10.withdraw()
win20.withdraw()
s = False
else:
if pyautogui.pixel(target[0],target[1]) == targetp:
win10.deiconify()
win20.deiconify()
s = True
#thread to handle other windows on the original app
def rd():
ws2 = []
while True:
for i in ws:
ws2.append(win32gui.FindWindow(0,i))
for i in ws2:
if i:
a,b,c,d= win32gui.GetWindowRect(i)
win32gui.MoveWindow(i,1024-(c-a-8),-8,c-a,d-b,True)
while True:
if pyautogui.pixel(target2[0],target2[1]) != target2p:
win10.withdraw()
break
while True:
if pyautogui.pixel(target2[0],target2[1]) == target2p:
win10.deiconify()
break
ws2=[]
t1 = Thread(target=wins)
t2 = Thread(target=cl)
t3 = Thread(target=rd)
t1.start()
time.sleep(1)
t2.start()
t3.start()
#while loop to make my windows always on top
while True:
win10.attributes("-topmost",True)
win20.attributes("-topmost",True)
`
The old app is set to be always on top, so i had to add a while loop at the end of the code that sets the two new windows topmost.
If i don't do this, my windows disappear as soon as i click on the old program's main window.
The code runs fine on my windows 10 machine until i reboot the operating system.
After the reboot, the last while loop become very slow, so that even the operation of minimizing the main window takes about 5 seconds.
Do you have any idea on how to fix this?
I also tried to insert the while loop in a thread letting the windows mainloop outside any method, but same result.
Until the reboot it all goes fine, after i power on the machine again in becomes very slow.

Related

How to make wx not block as focused window [Python]

I made an overlay in wxpython which I use to display a bitmap on my computer. However, when I integrate it into an app that takes focus on a screen and moves my mouse, it blocks the app and never goes in the "apploop". If I comment the Overlay in the app constructor, the app works just fine but I wouldn't see the Overlay.
I tried threading the wx_app.Mainloop (I would comment self.wx_app.MainLoop()) and the apploop works fine but it now freezes the windows that's focused by the app so it's useless. Other processes like Chrome work fine.
How would I make the focused windows not blocked while having the overlay? I would need both of them at the same time.
Here's a simplified example of my code:
import win32gui, win32process, win32api
import wx
import threading
class App():
def __init__(self) -> None:
# Window focus
hwnd = win32gui.FindWindow(None, 'Exact window name')
win32gui.SetActiveWindow(hwnd)
remote_thread, _ = win32process.GetWindowThreadProcessId(hwnd)
win32process.AttachThreadInput(win32api.GetCurrentThreadId(), remote_thread, True)
prev_handle = win32gui.SetFocus(hwnd)
# Overlay
self.wx_app = wx.App()
self.overlay = Overlay()
self.wx_app.MainLoop()
# t = threading.Thread(target=self.wx_app.MainLoop)
# t.start()
self.apploop()
def apploop(self):
import time
while True:
print('Apploop running')
time.sleep(5)
class Overlay(wx.Frame):
def __init__(self):
style = ( wx.CLIP_CHILDREN | wx.STAY_ON_TOP | wx.NO_BORDER | wx.FRAME_SHAPED )
wx.Frame.__init__(self, None, title='Fancy', style = style)
self.bitmap = wx.StaticBitmap(parent=self)
self.SetSize( (320, 180) )
self.SetPosition( (10,10) )
self.SetBackgroundColour( [0,0,0,255] )
self.SetTransparent( 200 )
self.Show(True)
You were almost there. The wx.App has to be started in the main Python thread and cannot run in threading.Thread. So your long-running-code will work when running wx_app.MainLoop() in the main thread and your payload in a Thread:
self.wx_app = wx.App()
self.overlay = Overlay()
t = threading.Thread(target=self.apploop)
t.start()
self.wx_app.MainLoop()
See at the end of wx Wiki - LongRunningTasks for a very simple example, mind the wx.CallAfter or other thread-safe Python objects to communicate between worker and main thread.

How to efficiently use Schedule Module in tkinter without getting GUI Freeze

I am new to python and tkinter and I made very basic program which check IP address ping or reachability in a given time range. I used Schedule module to schedule ping but actually after clicking Start Task, the GUI freeze while the code keeps running in the background. Probably the while loop is causing the freeze and I failed to solve this issue even after reviewing all stackoverflow mentioned solutions since no one addressed how to flawlessly use Schedule module in tkinter.
I wonder if there is a work-around to implement Schedule module without using while loop or with while loop without causing the GUI to freeze.
Thank you very much.
I made this fast sample for my current case.
Using Python 3.8.5
Target OS: Windows10 pro
Tested on VS Code
import tkinter as tk
import tkinter.font as tkFont
from pythonping import ping
from tkinter import *
from win10toast import ToastNotifier
import schedule
class App:
def __init__(self, root):
root.title("Ping Check")
width=600
height=400
screenwidth = root.winfo_screenwidth()
screenheight = root.winfo_screenheight()
alignstr = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2)
root.geometry(alignstr)
root.resizable(width=False, height=False)
self.ip_address = StringVar()
self.seconds = IntVar()
self.n = ToastNotifier()
IP_Address=tk.Entry(root)
IP_Address["borderwidth"] = "1px"
ft = tkFont.Font(family='Times',size=10)
IP_Address["font"] = ft
IP_Address["fg"] = "#333333"
IP_Address["justify"] = "center"
IP_Address["textvariable"] = self.ip_address
IP_Address.place(x=250,y=70,width=270,height=32)
ip_address_label=tk.Label(root)
ft = tkFont.Font(family='Times',size=10)
ip_address_label["font"] = ft
ip_address_label["fg"] = "#333333"
ip_address_label["justify"] = "center"
ip_address_label["text"] = "Enter IP Address"
ip_address_label.place(x=60,y=70,width=139,height=30)
seconds_label=tk.Label(root)
ft = tkFont.Font(family='Times',size=10)
seconds_label["font"] = ft
seconds_label["fg"] = "#333333"
seconds_label["justify"] = "center"
seconds_label["text"] = "Enter Seconds"
seconds_label.place(x=50,y=170,width=143,height=30)
Seconds=tk.Entry(root)
Seconds["borderwidth"] = "1px"
ft = tkFont.Font(family='Times',size=10)
Seconds["font"] = ft
Seconds["fg"] = "#333333"
Seconds["justify"] = "center"
Seconds["textvariable"] = self.seconds
Seconds.place(x=250,y=170,width=272,height=30)
start_button=tk.Button(root)
start_button["bg"] = "#efefef"
ft = tkFont.Font(family='Times',size=10)
start_button["font"] = ft
start_button["fg"] = "#000000"
start_button["justify"] = "center"
start_button["text"] = "Start Task"
start_button.place(x=100,y=310,width=178,height=30)
start_button["command"] = self.Start_Task
stop_button=tk.Button(root)
stop_button["bg"] = "#efefef"
ft = tkFont.Font(family='Times',size=10)
stop_button["font"] = ft
stop_button["fg"] = "#000000"
stop_button["justify"] = "center"
stop_button["text"] = "Stop Task"
stop_button.place(x=330,y=310,width=172,height=30)
stop_button["command"] = self.Stop_Task
def ping_ip(self):
l = list(ping(self.ip_address.get()))
if not str(l[0]).startswith('Reply'):
self.n.show_toast("Warning!", "Unreachable IP Address, Error Ping Message: Request timed out!")
else:
self.n.show_toast("Successful reply!")
def Start_Task(self):
schedule.every(self.seconds.get()).seconds.do(self.ping_ip)
while True:
schedule.run_pending()
def Stop_Task(self):
schedule.cancel_job(self.Start_Task)
if __name__ == "__main__":
root = tk.Tk()
app = App(root)
root.mainloop()
GUI toolkits like tkinter are event-driven. To work properly, the
mainloop must continuously be able to process keyboard and mouse events.
When it is not processing events, it will start scheduled idle tasks.
So tkinter programs work quite differently from normal Python scripts.
A callback is called in response to activating a control (like clicking on
a button).
An idle task is started by the system after a specified number of milliseconds
when the system is not busy processing events. You can schedule idle tasks
with the Tk.after() method.
Basically, the callbacks and idle tasks are your program.
But they are run from within the main loop.
So whatever you do in a call-back, it should not take too long. Otherwise the GUI becomes unresponsive.
So using while True in a call-back is not a good idea.
In a Python 3 tkinter program, when you want to perform a long-running task especially if it involves disk access or network activity, you should probably do that in a second thread. (In Python 3 tkinter is mostly thread-safe.)
Edit 1:
Instead of using pythonping and schedule, use subprocess.Popen to run the ping program asyncronously:
import subprocess as sp
# class App:
# et cetera...
def query_task(self):
if self.process:
if self.process.returncode:
# The ping has finished
if self.process.returncode != 0:
self.n.show_toast("Warning:", f"ping returned {self.process.returncode}")
else: # ping has finished successfully.
# self.process.stdout and self.process.stderr contain the output of the ping process...
pass
# So a new task can be started.
self.process = None
else:
# check again after 0,5 seconds.
self.after(0.5, self.query_task);
# By convention, method names should be lower case.
def start_task(self):
if self.process is None:
self.process = sp.Popen(
["ping", self.ip_address.get()], stdout=sp.Pipe, stderr=sp.Pipe
)
# Check after 0.5 seconds if the task has finished.
self.after(0.5, self.query_task);

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?

Windows program not responding issue with tkinter gui

I'm having an issue with a program I created, Im trying to make a program that images a drive usind dism. When the dism command is run in cmd it gives a percentage of completion, so I am trying to take that percentage (using regex) and put it in my own progress bar gui however when I run my program, windows gives me a program is not responding error and I'm not sure why. The program continues to to run in the background and upon completion the error disappears, but the progress bar does not update. I have a feeling it is because I my run function has its own loop so maybe the tkinter gui isn't updating until that loop completes. If someone can confirm this or give me other reasoning that would be greatly appreciated. Also if you have any ideas for a solution to this I am all ears.
import shlex
from tkinter import *
from tkinter import ttk
def run(command):
progressBar['maximum'] = 100
process = subprocess.Popen(shlex.split(command), stdin= subprocess.PIPE
,stdout=subprocess.PIPE)
while True:
output = process.stdout.readline().decode()
if output == '' and process.poll() is not None:
break
if output:
matchObj = re.search(r'\d+\.\d%',output.strip())
if matchObj:
percentNum = re.search(r'\d+\.\d',matchObj.group())
progressBar["value"] =
round(float(percentNum.__getitem__(0)))
print(type(progressBar["value"]))
print(progressBar["value"])
else:
print(output.strip())
rc = process.poll()
progressBar["value"] = 0
return rc
root = Tk()
root.title('Progress Bar')
root.geometry("300x100")
buttonFrame = LabelFrame(text="Options")
buttonFrame.grid(column=0,row=0)
backupCmd = 'Dism /Capture-Image /ImageFile:F:\my-windows-partition.wim
/CaptureDir:"E:" /name:Windows \n'
button1 = Button(master=buttonFrame, text="Backup",command= lambda:
run(backupCmd))
button1.grid(column = 0, row = 0)
restoreCmd = ''
button2 =Button(master=buttonFrame, text="Restore",command= lambda:
run(restoreCmd))
button2.grid(column = 50, row = 0)
button3 =Button(master=buttonFrame, text="Exit",command= lambda: exit())
button3.grid(column = 100, row = 0)
progressBar = ttk.Progressbar(root, orient="horizontal",
length=286,mode="determinate")
progressBar.grid(column = 0, row = 3, pady=10)
root.mainloop()
I figured out the issue. For anyone struggling with this, you need to add a call to update the progress bar. For me I said, progressBar.update in my run methon
I was stuck with this issue for a long time until I found an answer on stackoverflow about using multi-threading.
Tkinter window says (not responding) but code is running You to create a thread for sql queries function, like this
from threading import Thread
Thread(target = run, args =(command, )). Start()

Calling a def from a thread

Does any one know how to call a def form a thread.
Clock Program:
import sys
from tkinter import *
from tkinter import messagebox
from tkinter import filedialog
from time import sleep
import threading
class MyThread ( threading.Thread ):
def mclock(): # function that it can't call
x = 1
z = 0
while x != -1:
Label(mGui,text = str(x) + "second(s)").pack()
x = x+1
sleep(1)
if x == 60:
x = 1
z = z+1
Label(mGui, text= str(z) + " minute(s) has past.").pack()
return
return
MyThread().start()
mGui = Tk()
mGui.geometry("300x200+100+100")
mGui.title("Jono's Clock")
menubar = Menu(mGui)
filemenu = Menu(menubar, tearoff = 0)
filemenu.add_command(label = "Clock",command = mclock) # can't use function
menubar.add_cascade(label = "File",menu = filemenu)
mGui.config(menu = menubar)
mGui.mainloop()
If any one sees any other errors please state. I am also using windows 7 and python 3.3.
There are several syntax errors in the code you've posted, and I'm not sure exactly what you intended with them, so here's an overview of how to run stuff from threads.
If you want your thread to run your own code from a custom thread class, the usual way to do that is to put the code in a method named run, which will be executed automatically when the thread is started:
import threading
class MyThread(threading.Thread):
def run(self):
# do your stuff here
print("Hello World")
MyThread().start()
Alternatively, if you don't need a class, you can create your function at the top level of your module, and pass it as an argument to threading.Thread's constructor:
def my_function():
print("Hello World")
threading.Thread(target=my_function).start()
Note that you often want to keep a reference to the thread object, rather than letting it go as the code above does. This requires you to use two lines to create and then start the thread:
thread = MyThread() # or the alternative version
thread.start()
This lets you later do:
thread.join()
Which ensures that the thread has finished its work.

Resources