Windows program not responding issue with tkinter gui - python-3.x

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()

Related

Slow while loop in tkinter after reboot

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.

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);

Python 3: Script exits with exit code -1073740771 (0xC000041D), while trying to get a screenshot of window using PyWin32

My script, using Tkinter to create a window, gets a bitmap image with PyWin32, convert the bitmap into a Pillow Image. Then save it to test.png.
The code:
import os
from tkinter import Tk, Frame, Button
import win32gui
import win32ui
from ctypes import windll
from PIL import Image
root = Tk()
frame = Frame(root, bg="#ff0000")
button = Button(frame, bg="#7c7c7c")
button.pack(pady=10, padx=10)
frame.pack(fill="both", expand=True)
root.update()
hwnd = win32gui.GetParent(root.winfo_id())
# Change the line below depending on whether you want the whole window
# or just the client area.
left, top, right, bot = win32gui.GetClientRect(hwnd)
# left, top, right, bot = win32gui.GetWindowRect(hwnd)
w = right - left
h = bot - top
hwndDC = win32gui.GetWindowDC(hwnd)
mfcDC = win32ui.CreateDCFromHandle(hwndDC)
saveDC = mfcDC.CreateCompatibleDC()
saveBitMap = win32ui.CreateBitmap()
saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)
saveDC.SelectObject(saveBitMap)
# Change the line below depending on whether you want the whole window
# or just the client area.
result = windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 1)
# result = windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 0)
print(result)
bmpinfo = saveBitMap.GetInfo()
bmpstr = saveBitMap.GetBitmapBits(True)
im = Image.frombuffer(
'RGB',
(bmpinfo['bmWidth'], bmpinfo['bmHeight']),
bmpstr, 'raw', 'BGRX', 0, 1)
win32gui.DeleteObject(saveBitMap.GetHandle())
saveDC.DeleteDC()
mfcDC.DeleteDC()
win32gui.ReleaseDC(hwnd, hwndDC)
if result == 1:
#PrintWindow Succeeded
im.save("test.png")
# root.wm_protocol("WM_DELETE_WINDOW", lambda: os.kill(os.getpid(), 0))
root.mainloop()
Also my idea is to have to get an image of the window, even another window overlaps it.
Even when the window is minimized.
This is the PyCharm output:
1
Process finished with exit code -1073740771 (0xC000041D)
It crashes when closing the window.
Does anyone know what's going wrong?
By the way, it seems it's happening with this bit of code: root.update(). #interflexo said that import win32ui is doing it. I think there's a conflict with these two.
Just adding this import at top of my code
import win32ui
triggers this behavior.

Why is Python Skipping Over Tkinter Code?

I'm in Python 3.x using Tkinter to make a button that changes a boolean variable's value from true to false then a if statement to check if that value is false. Here is my code for that:
import tkinter
import time
x = True
top = tkinter.Tk()
def helloCallBack():
x = False
print (x)
B = tkinter.Button(top, text ="Hello", command = helloCallBack)
B.pack()
if x == False:
print ('Hello')
top.mainloop()
This unfortunately didn't work, so I replaced the if statement with a time.sleep(10) and then print (x) so I would have enough time to press the button like so:
import tkinter
import time
x = True
top = tkinter.Tk()
def helloCallBack():
x = False
print (x)
B = tkinter.Button(top, text ="Hello", command = helloCallBack)
B.pack()
time.sleep(10)
print (x)
top.mainloop()
The issue with this is it skips over all of the Tkinter code and goes to the time.sleep(10), prints the value of x then brings everything from Tkinter up after. Why is it doing this?
The reason you see that the code does sleep() and then prints True before the tkinter windows opens is due to how the mainloop() works in tkinter.
sleep() is useful in python however due to tkinter's single threaded nature all sleep() can due is block the mainloop until it has completed sleeping. Tkinter has its own method to work around this issue called after() and you don't need it here but it is very useful for timing things in tkinter.
Tkinter does its updates in the mainloop and you have a sleep() command that occurs before your mainloop as well as a print command before the mainloop. Both of those things must finish execution before the mainloop is reached for the first time thus not allowing tkinter to start until they are complete.
What you want to do is to place this print statement into your function. As well as a few other quality of life clean ups.
See this example:
import tkinter as tk
top = tk.Tk()
x = True
def hello_call_back():
global x
x = False
if not x:
print('Hello')
tk.Button(top, text="Hello", command=hello_call_back).pack()
top.mainloop()
However in this case I think the simplest form of the code should look like this:
import tkinter as tk
top = tk.Tk()
def hello_call_back():
print('Hello')
tk.Button(top, text="Hello", command=hello_call_back).pack()
top.mainloop()
As your current use of x is redundant to the goal.

How to create GUI objects one by one with Tkinter

I like to create an object per second and make the show up one by one. However, the code below wait for 3 seconds and show them all at the same time.
from tkinter import *
import time
def create():
for i in range(3):
r4=Radiobutton(root, text="Option 1"+str(i), value=1)
r4.pack( anchor = W )
time.sleep(1)
root = Tk()
create()
root.mainloop()
Your code, as is, creates a one object per second as you desired it, but this objects need to be shown, and they're shown when code flow reaches the mainloop. Hence, for observer, it looks like there're no objects at all after one second.
Of course, you can use sleep and update, but beware - sleeping leads to unresponsive window, so it's OK option (to be honest - not OK at all), if your application isn't drawn and you're outside of mainloop, but if it's not - prepare for a "frozen" window, because GUI can redraw himself only in mainloop (an event loop, you can reach it with update as well) and sleep blocks this behaviour.
But there's a good alternative, the after method, take a look on it!
And there's a snippet, so you can see the difference:
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
import time
def create_with_sleep():
for _ in range(3):
tk.Radiobutton(frame_for_sleep, text="Sleep Option").pack(anchor='w')
time.sleep(int(time_entry.get()))
root.update()
def create_with_after(times=3):
if times != 0:
tk.Radiobutton(frame_for_after, text="After Option").pack(anchor='w')
times -= 1
root.after(int(time_entry.get()) * 1000, lambda: create_with_after(times))
root = tk.Tk()
test_yard_frame = tk.Frame(root)
frame_for_sleep = tk.Frame(test_yard_frame)
frame_for_after = tk.Frame(test_yard_frame)
test_yard_frame.pack()
frame_for_sleep.pack(side='left')
frame_for_after.pack(side='left')
button_frame = tk.Frame(root)
button_for_sleep = tk.Button(button_frame, text='Create 3 radiobuttons with sleep+update', command=create_with_sleep)
button_for_after = tk.Button(button_frame, text='Create 3 radiobuttons with after', command=create_with_after)
button_frame.pack()
button_for_sleep.pack(side='left')
button_for_after.pack(side='left')
time_label = tk.Label(root, text='Time delay in seconds:')
time_label.pack(fill='x')
time_entry = tk.Entry(root)
time_entry.insert(0, 1)
time_entry.pack(fill='x')
root.mainloop()
With 1 seconds delay there's no much difference, but you can try to increase delay to understand why after option is preferable in general.
You can use update to call a refresh on your objects.
In use, you would have to add the line root.update() in your for loop.

Resources