Imagine the following simple example:
def doNothing():
sleep(0.5)
barVar.set(10)
sleep(0.5)
barVar.set(20)
sleep(0.5)
barVar.set(30)
mainWindow = Tk()
barVar = DoubleVar()
barVar.set(0)
bar = Progressbar(mainWindow, length=200, style='black.Horizontal.TProgressbar', variable=barVar, mode='determinate')
bar.grid(row=1, column=0)
button= Button(mainWindow, text='Click', command=doNothing)
button.grid(row=0, column=0)
mainWindow.mainloop()
What I get when I run this, the progressbar is already at 30% when clicking the button, no progress in front of me. Like attached:
What I need: I can see the progress in front of me (not hanging then suddenly 30%)
Update:
I upadted the code according to #Bernhard answer, but still I can not see the progress in front of me. Just a sudden jump of 30% after waiting 1.5 sec
Seocnd Update:
I'm only using sleep here as a simulation for a process that takes time, like connecting over ssh and grabing some info.
Do not use sleep() in tkinter. The entire reason for you problem is sleep() will freeze tkinter until it is done with its count so what you are seeing is a frozen program and when the program is finally released its already set to 30 percent on the next mainloop update.
Instead we need to use Tkinter's built in method called after() as after is specifically for this purpose.
import tkinter as tk
import tkinter.ttk as ttk
mainWindow = tk.Tk()
def update_progress_bar():
x = barVar.get()
if x < 100:
barVar.set(x+10)
mainWindow.after(500, update_progress_bar)
else:
print("Complete")
barVar = tk.DoubleVar()
barVar.set(0)
bar = ttk.Progressbar(mainWindow, length=200, style='black.Horizontal.TProgressbar', variable=barVar, mode='determinate')
bar.grid(row=1, column=0)
button= tk.Button(mainWindow, text='Click', command=update_progress_bar)
button.grid(row=0, column=0)
mainWindow.mainloop()
If you want the bar to appear to move smoothly you will need to speed up the function call and reduce the addition to the DoubbleVar.
import tkinter as tk
import tkinter.ttk as ttk
mainWindow = tk.Tk()
def update_progress_bar():
x = barVar.get()
if x < 100:
barVar.set(x+0.5)
mainWindow.after(50, update_progress_bar)
else:
print("Complete")
barVar = tk.DoubleVar()
barVar.set(0)
bar = ttk.Progressbar(mainWindow, length=200, style='black.Horizontal.TProgressbar', variable=barVar, mode='determinate')
bar.grid(row=1, column=0)
button= tk.Button(mainWindow, text='Click', command=update_progress_bar)
button.grid(row=0, column=0)
mainWindow.mainloop()
Because you are calling the function when the buttion is initialized, you need to loose the '(barVar') in the command=(barVar)). This way you bind the function to the button and don't call it when initializing it.
button= Button(mainWindow, text='Click', command=doNothing)
If you need to pass an argument you need to bypass the calling by using lambda:
button= Button(mainWindow, text='Click', command= lambda: doNothing(barVar))
I think I find the solution.
simply add mainWindow.update() after each progress. So the final code would be:
def doNothing():
sleep(0.5)
barVar.set(10)
mainWindow.update()
sleep(0.5)
barVar.set(20)
mainWindow.update()
sleep(0.5)
barVar.set(30)
mainWindow.update()
Related
I am trying to create a 10*10 board of buttons, which when clicked, the clicked button is destroyed and only that one. However, I don't know how to specify which button has been clicked.
from tkinter import *
root = Tk()
root.title("Board")
def buttonClick():
button.destroy()
for i in range(10):
for j in range(10):
button = Button(root, text="", padx=20, pady=10, command=buttonClick)
button.grid(row=i+1, column=j+1)
root.mainloop()
You have to create function which gets widget/button as argument and uses it with destroy()
def buttonClick(widget):
widget.destroy()
And first you have to create Button without command=
button = tk.Button(root, text="", padx=20, pady=10)
and later you can use this button as argument in command=.
button["command"] = lambda widget=button:buttonClick(widget)
It needs to use lambda to assign function with argument.
Because you create many buttons in loop so it also needs to use widget=button in lambda to create unique variable with value from button for every command. If you don't use it then all commands will use reference to the same (last) button - and click on every button will destroy only last button.
Full working code
import tkinter as tk # PEP8: `import *` is not preferred
# --- functions ---
def buttonClick(widget):
widget.destroy()
# --- main ---
root = tk.Tk()
root.title("Board")
for i in range(10):
for j in range(10):
button = tk.Button(root, text="x", padx=20, pady=10)
button["command"] = lambda widget=button:buttonClick(widget)
button.grid(row=i+1, column=j+1)
root.mainloop()
PEP 8 -- Style Guide for Python Code
This can be easily accomplished with a custom class if you're alright with that:
from tkinter import Button, Tk
root = Tk()
root.title("Board")
class Self_Destruct_Button(Button):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.configure(command=self.button_click)
def button_click(self):
self.destroy()
for i in range(10):
for j in range(10):
button = Self_Destruct_Button(root, text="", padx=20, pady=10)
button.grid(row=i + 1, column=j + 1)
root.mainloop()
So the custom class just assigns the command to a button_click method, which destroys the button.
As a side note, I also removed the wildcard import as that's not best practice.
Let me know if this works for you
I just new for the GUI and need little help.
a=int(input())
if a==0:
print("hi")
else:
print("hello")
I want to change input process to click button, like a switch.
left button -> a=0
right button -> a=1
window=tkinter.Tk()
window.title("")
window.geometry("640x640+100+100")
window.resizable(True, True)
a=tkinter.Button(window, text="left")
a.pack()
b=tkinter.Button(window, text="right")
b.pack()
window.mainloop()
I can see left, right button but I don't know how to put values.
Is there any example I can use?
Thanks
Does this example help You:
from tkinter import Tk, Button
def switch(btn1, btn2):
btn1.config(state='disabled')
btn2.config(state='normal')
print(btn1['text'])
window = Tk()
window.title("")
on = Button(window, text="On", command=lambda: switch(on, off))
on.pack(side='left', expand=True, fill='x')
off = Button(window, text="Off", command=lambda: switch(off, on))
off.pack(side='left', expand=True, fill='x')
off.config(state='disabled')
window.mainloop()
If You have questions ask but here is pretty good site to look up tkinter widgets and what they do, their attributes.
Also I suggest You follow PEP 8
You need to add a function to each one that will be executed when the buttons are clicked like this:
import tkinter as tk
def left_clicked():
print("Left button clicked")
right_button.config(state="normal") # Reset the button as normal
left_button.config(state="disabled") # Disable the button
def right_clicked():
print("Right button clicked")
right_button.config(state="disabled")
left_button.config(state="normal")
window = tk.Tk()
window.title("")
window.geometry("640x640+100+100")
# window.resizable(True, True) # Unneeded as it is already the default
left_button = tk.Button(window, text="left", command=left_clicked)
left_button.pack(side="left")
right_button = tk.Button(window, text="right", command=right_clicked,
state="disabled")
right_button.pack(side="right")
window.mainloop()
I made the following program to change screen brightness depending on the time of day. It uses an infinite loop to constantly check the time, but this also prevents the user from changing the values in the tkinter window or from closing it. Is there any way to avoid this?
from datetime import datetime
import screen_brightness_control as sbc
from tkinter import *
m=Tk()
m.title('Brightness Control')
def saver():
print("Saving")
global brit
brit=e3.get()
global frot
frot=e1.get()
global tot
tot=e2.get()
a=True
while a==True:
current_brightness=sbc.get_brightness()
now=datetime.now().time()
if now.hour>int(frot) and now.hour<int(tot) :
sbc.set_brightness(brit)
else:
sbc.set_brightness(40)
Label(m, text='Brightness').grid(row=0,column=0)
Label(m, text='From').grid(row=1,column=0)
Label(m, text='To').grid(row=1,column=2)
e1 = Entry(m)
e2 = Entry(m)
e3 = Entry(m)
e1.grid(row=1, column=1)
e2.grid(row=1, column=3)
e3.grid(row=0, column=1)
button = Button(m, text='Save', width=5,command=saver)
button.grid(row=2,column=3)
m.mainloop()
You have to add <tk.Tk object>.update() anywhere in your while loop like this:
import tkinter as tk
root = tk.Tk()
button = tk.Button(root, text="This is a button")
button.pack()
while True:
root.update()
root.update() updates tcl and that stops the "program isn't responding" message.
I'm currently writing a Python Tkinter START and STOP button.
As it uses tkinter when I click START the STOP button should appear in it's place. But to do this I need to use .destroy() on the button. So to get around this I've done the following (see code blow); however it seems bulky and can't but feel I've over complicated it. Any suggestions would be great
def stop():
global start_button
try:
stop_button.destroy()
except NameError:
pass
print("Stopped. Hit GO to go again!")
start_button = Button(self.b, text="GO!", font="calibri, 18", command=started, fg="white", width=10)
start_button.pack(side=BOTTOM)
start_button.config(bg="green")
def started():
global stop_button
try:
start_button.destroy()
except NameError:
pass
print("Started. Hit ENTER to Stop!")
stop_button = Button(self.b, text="STOP!", font="calibri, 18", command=stop, fg="white", width=10)
stop_button.pack(side=BOTTOM)
stop_button.config(bg="red")
def first_start():
start.destroy()
started()
start = Button(self.b, text="START!", font="calibri, 18", command=first_start, fg="white", width=10)
start.pack(side=BOTTOM)
start.config(bg="green")
Here is a minimalist start/stop button that toggles its aspect and behavior when clicked. There is no need to destroy and replace buttons with such a toggling mechanism.
import tkinter as tk
def do_the_start_things():
# replace print with the things to do at start
print('starting now')
def do_the_stop_things():
# replace print with the things to do at stop
print('stopped!')
def toggle_start_stop():
if startstop['text'] == 'START':
startstop.config(text='STOP', fg='red')
do_the_start_things()
else:
startstop.config(text='START', fg='green')
do_the_stop_things()
root = tk.Tk()
startstop = tk.Button(root, text='START', command=toggle_start_stop, fg='green')
startstop.pack()
root.mainloop()
I've got a program that does some simple webscraping. I'm working on giving it to my coworkers to use, but they are unfamiliar with code so I'd like to make a GUI. I've started using tkinter, and I'm currently working on making a progress bar showing where the program is at (it can take up to a couple hours to run). My problem is that I can't seem to get the progress bar to update, and all the online sources use Tkinter, which is an old version. Here is my code:
I've tried updating progressBar['value'] to whatever number I want, but that hasn't been working.
from tkinter import *
from tkinter import ttk
import time
def clicked(progressBar): # runs a couple functions and updates the progress bar when the button is clicked
num = 0
for item in range(5):
# functions go here
num += 10
progressBar['value'] = num
time.sleep(2)
window = Tk()
window.title("Welcome to my app")
window.geometry('600x400')
progressBar = ttk.Progressbar(window, orient='horizontal', length=300, mode='determinate', maximum=100, value=0)
progressBar.grid(columnspan=3, row=2, sticky=(W, E))
btn = Button(window, text="Click me!", command=clicked(progressBar))
btn.grid(column=1, row=1)
window.mainloop()
The tkinter window doesn't open up until 10 seconds after I run the program, and it has the progress bar already at 50% full. I'd like for the bar to slowly increment up, AFTER the button has been clicked. Any advice would be helpful! Thank you!
There are two problems with the code:
command=clicked(progressBar) indeed calls the function promptly.So simply use command=clicked. There is no need to pass progressBar as the argument since the function gets it from global scope.
while the function clicked() is running, GUI freezes. After 5*2sec, progressBar updates to 5*10 abruptly. To update the widgets in a loop, use update_idletastk method:
import tkinter as tk
from tkinter import ttk
import time
def clicked():
num = 0
for item in range(5):
num += 10
progressBar['value'] = num
window.update_idletasks()
time.sleep(2)
window = tk.Tk()
progressBar = ttk.Progressbar(window, orient='horizontal', length=300, mode='determinate', maximum=100, value=0)
progressBar.grid(columnspan=3, row=2, sticky=(tk.W, tk.E))
btn = tk.Button(window, text="Click me!", command=clicked)
btn.grid(column=1, row=1)
window.mainloop()