Why is Python Skipping Over Tkinter Code? - python-3.x

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.

Related

How to create a progressbar capturing the progress of a subprocess in tkinter with threading

I am currently programming a GUI with tkinter in Python 3.6 for another Program, which is called through subprocess. I would like to represent the progress of the Program called, with a Progressbar, popping up in another window as soon as I hit the 'Run Program' Button. The value of the progressbar is set by arguments passed from the subprocess script, where I calculate the percentage of progress in a function. What I need:
1. How do I properly use the Threading-Module for spawning the subprocess?
2. How do I import a function (and therefore its arguments) of a script, that runs with command line parameters?
I was looking for a answer in other posts the whole day, but couldn't really connect them to my problem.
I tried to spawn the subprocess by simply calling its function with threading.Thread(target=self.runProgram()).start()
Apparently the GUI-program still waits until the subprocess/Thread is finished and then continues with creating the progressbar, but I expected it to happen at the same time. I then tried to import the percentage value of the subprocess-Program, which didn't work due to missing parameters. Also I don't think that this would really work the way I want it to, because I want the arguments passed during the runtime, when the subprocess is called. It's very hard to properly explain what I am looking for, so here is some code:
GUI-Program:
import tkinter as tk
from tkinter import ttk
from tkinter import *
from tkinter.filedialog import askdirectory,askopenfilename
import os,subprocess
import threading
class eodApplication(tk.Tk):
def __init__(self):
#different entryboxes are created, not necessary for understanding
self.runButton=''
self.progress=''
self.window = tk.Tk()
self.window.geometry("500x350")
self.window.configure(background="white")
self.window.title("End of Day Application")
self.create_widgets()
def create_widgets(self):
#creating entryboxes initiated in __init__-method, containing paths one can browse for and a submit/'Run Program'-Button
self.submit()
def submit(self):
#Button calls function when pressed
self.runButton=ttk.Button(self.window,text='Run Program',command=lambda:self.bar_init())
self.runButton.grid(row=7, columnspan=2)
def bar_init(self):
barWindow = tk.Tk()
barWindow.geometry("250x150")
barWindow.configure(background="white")
barWindow.title("Progress")
self.progress=ttk.Progressbar(barWindow, orient="horizontal", length=200, mode="determinate",value=0)
self.progress.grid(row=0, column=0, sticky=tk.W)
threading.Thread(target=self.runProgram()).start()
from PythonProgrammeDWH import get_percentage
self.progress["value"] = percentage
def runProgram(self):
devnull = open(os.devnull, 'w')
subprocess.call(f"{self.entryboxPyExe.get()} {self.entryboxPyCode.get()} {self.entrybox64.get()} {self.entrybox66.get()} {self.entryboxXsd.get()} {self.entryboxOutput.get()} {self.entryboxXmlExe.get()}",cwd=os.getcwd(),stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,shell=True)
eodApplication=eodApplication()
eodApplication.window.mainloop()
PythonProgrammeDWH (subprocess):
def main(sys.argv[1],sys.argv[2],sys.argv[3],sys.argv[4]):
~ a lot of stuff going on ~
sumFiles=len(os.listdir(path))
checkedFiles=0
for file in os.listdir(path):
checkedFiles+=1
get_percentage(sumFiles, checkedFiles)
def get_percentage(sumFiles, checkedFiles):
percentage= round((checkedFiles/sumFiles)*100)
return percentage
#percentage is the argument I want to pass to the other script for updating the progressbar

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

how to make the python program light, which is written to print the text on screen directly without window?

From last few days, I have been working on a program which one part is to show the text directly on the window screen, and also update/change them as per requirement. I have completed this part with Tkinter module in python.
The problem is whenever I run the program it behaves like a heavy program due to which other processes become slow. Also If I tried to do some other process in parallel, the background of the text becomes black, which is absolutely undesirable.as the shown in image
I also want to show some dynamic symbol like loading but the use of two Tkinter widget make it slower. if possible please make it more modular and light.
from tkinter import *
import win32api, win32con, pywintypes
from time import sleep
f=Tk()
var = StringVar()
var.set(' ')
f =Label(textvariable = var, font=('Cooper','60'), fg='blue', bg='white')
f.master.overrideredirect(True)
f.master.geometry("+900+200")
f.master.lift()
f.master.wm_attributes("-topmost", True)
f.master.wm_attributes("-disabled", True)
f.master.wm_attributes("-transparentcolor", "white")
f.pack()
for i in range(10):
sleep(5) # Need this to slow the changes down
var.set(u'[ A ]' if i%2 else u'[ B ]')
f.update_idletasks()
also, want to ask can we do it without using the Tkinter module.so it becomes more light and modular. and dependency will be less.
Here is a code which makes your code responsive and also don't use your cpu too much.
from tkinter import *
import win32api, win32con, pywintypes
import time
f=Tk()
var = StringVar()
var.set(' ')
f =Label(textvariable = var, font=('Cooper','60'), fg='blue', bg='white',bd=0)
f.master.overrideredirect(True)
f.master.geometry("+900+200")
f.master.lift()
f.master.wm_attributes("-topmost", True)
f.master.wm_attributes("-disabled", True)
f.master.wm_attributes("-transparentcolor", "white")
f.pack()
for i in range(10):
f.update()
t = time.time()
while time.time() - t < 5:
f.update()
var.set(u'[ A ]' if i%2 else u'[ B ]')
f.update_idletasks()
f.update()
Here is an image of task manager. Its taking only 15 MB memory and no cpu:

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.

Tkinter copy to clipboard not working in PyCharm

I just installed PyCharm and opened up a script I had been using in IDLE that did some string manipulation then copied it to the clipboard, but it doesn't work when I run it in PyCharm.
from tkinter import Tk
r = Tk()
r.withdraw()
r.clipboard_clear()
r.clipboard_append("test")
r.destroy()
When I run this in IDLE I am able to paste "test" after, but in PyCharm it just says "Process finished with exit code 0" but there is nothing in the clipboard (even if there was before running). I have Python 3.5 as the selected interpreter.
There seems to be problem if the clipboard is manipulated and the program closes too quickly soon after. The following program worked for me but was unreliable when the call to root.after only used one millisecond for the delay. Other possibilities were tried, but code down below should work:
import random
import string
import tkinter
def main():
root = tkinter.Tk()
root.after_idle(run_code, root)
root.after(100, root.destroy)
root.mainloop()
def run_code(root):
root.withdraw()
root.clipboard_clear()
root.clipboard_append(''.join(random.sample(string.ascii_letters, 10)))
print('Clipboard is ready.')
if __name__ == '__main__':
main()
The following is a mildly more useful version of the program and demonstrates that you can make many calls to root.after_idle to run your code in a sequential manner. Its design is primarily for use to process command-line arguments and send them to your clipboard for you:
import sys
import tkinter
def main(argv):
root = tkinter.Tk()
root.after_idle(root.withdraw)
root.after_idle(root.clipboard_clear)
root.after_idle(root.clipboard_append, ' '.join(argv[1:]))
root.after_idle(print, 'The clipboard is ready.')
root.after(100, root.destroy)
root.mainloop()
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))

Resources