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()
Related
I want to make Button2 do something, but only if Button1 has been pressed first and it's code was successfully run.
Also: Button2 should not execute button_1's function, and I do not want to use global variables.
The code below was my initial thought, and it did not work. I've searched for the right way, but neither of the methods I found gave me the result I hoped for.
Can anyone tell me how to correct this? I'm also open for easier or better ways to achieve my goal.
import tkinter as tk
from sys import exit
window = tk.Tk()
def button_1():
print("Button 1 pressed")
button_1_pressed = "Yes"
return button_1_pressed
def button_2():
if (button_1_pressed == "Yes"):
print("Button 1 was pressed, taking action")
# Action goes here
else :
print("Button 1 was NOT pressed, no action is done")
btn_1 = tk.Button(master=window, text="Button 1", width=10, height=2, command=button_1)
btn_1.pack()
btn_2 = tk.Button(master=window, text="Button 2", width=10, height=2, command=button_2)
btn_2.pack()
btn_Close = tk.Button(master=window, text="Close", width=10, height=2, command=exit)
btn_Close.pack()
window.mainloop()
Note: This is just a quick and simple script, illustrating exactly what I need help with in all simplicity.
How to pass variable from one function to another (button pressed)
To get returned value from the callback you should use invoke() method of the Button.
BUT it's just returned value, and unfortunately you cannot use it as a flag, as it doesn't track was the button pressed or not.
So the trick is to create your custom button with a flag which will be changed to True by callback and pass the flag as an argument to the button_2, to do so you can use lambda or partial func.
I hope it's fine for you, despite of it uses the vars out of function scope.
Updated code:
import tkinter as tk
from sys import exit
window = tk.Tk()
class MyButton(tk.Button):
pressed = False
def button_1():
print("Button 1 pressed")
button_1_pressed = "Yes"
btn_1.pressed = True
return button_1_pressed
btn_1 = MyButton(master=window, text="Button 1", width=10, height=2, command=button_1)
btn_1.pack()
def button_2(state):
if state:
print("Button 1 was pressed, taking action")
print('gotcha!')
else:
print("Button 1 was NOT pressed, no action is done")
btn_2 = tk.Button(master=window, text="Button 2", width=10, height=2, command=lambda: button_2(btn_1.pressed))
btn_2.pack()
btn_Close = tk.Button(master=window, text="Close", width=10, height=2, command=exit)
btn_Close.pack()
window.mainloop()
Useful links:
https://www.tutorialspoint.com/python/tk_button.htm
How to pass arguments to a Button command in Tkinter?
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()
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()
The Incorrect-Dimiss button erases on click and when Return key is pressed, but if I enter anything afterwards, nothing happens. The issue is that if I enter it correct, the login button pops up and it works. If I enter it incorrect, the dismiss button pops up, and clicking or pressing enter erases it. Now, anything I enter after an incorrect attempt whether correct or incorrect does nothing.
(1) In order to avoid this, I was wondering if the program could just restart on clicking/pressing enter the dismiss button, without the window closing, or another reopening, but I do not know how to do this.
(2)Also is there a max login attempts code that ends/restarts the program and if so how would I place it in this code? (something like if >3 incorrect then quit)
Here is the code (python3)- try it for yourself if you would like:
from tkinter import *
class Application(object):
def __init__(self, event=None):
self.root = Tk()
self.root.configure(bg="darkorchid1", padx=10, pady=10)
self.root.title("WELCOME")
self.username = "Bob"
self.welcome = Label(self.root, text="WELCOME TO MY PROGRAM", bg="lightgrey", fg="darkorchid1")
self.welcome.pack()
self.label0 = Label(self.root, text="ENTER NAME:", bg="purple", fg="white", height=5, width=50)
self.label0.pack()
self.entry = Entry(self.root, width=25)
self.entry.configure(fg= "white",bg="grey20")
self.entry.pack()
self.entry.bind("<Return>", self.submit)
self.button = Button(self.root, text="SUBMIT", highlightbackground="green", width=48, command=self.submit)
self.button.pack()
def submit(self, event=None):
username = self.entry.get()
if username == self.username:
self.button1 = Button(self.root, text='LOGIN', highlightbackground="green", width=28, command=self.root.destroy)
self.button1.pack()
self.entry.bind("<Return>", self.login)
else:
self.button2 = Button(self.root, text="INCORRECT- CLICK TO DIMISS THIS MESSAGE", highlightbackground="red", width=48, command=self.incorrect)
self.button2.pack()
self.entry.bind("<Return>", self.incorrect)
def incorrect(self, event=None):
self.button2.destroy()
def login(self, event=None):
self.root.destroy()
app=Application()
mainloop()
Instead of destroying the button I want this to restart the program but cannot find the correct command. This would destroy the button since it doesn't exist at the beginning of the program, as well as allow for incorrect or correct input to actually work after the first try.
def incorrect(self, event=None):
self.button2.destroy()
I am a beginner so the more simple, the better at this point. Thank you.
Not an expert myself but fiddled with TKinter some time ago. From what I know the only way to restart a TKinter app is by running it in a thread and then killing and restarting that thread. I would recommend you to take a look at Python's multiprocessing or threading module.
Something you could also try (which worked for me in that past, but isn't really the correct way of doing this I suppose) is to have ROOT = Tk()as a global variable, then having your submit-button as a stand-alone class and have it import global ROOT before executing ROOT.Destroy() and then have it call the application class again, which can also call the global variable (which would cause it to start again). It's a method I've used to have the TKinter window update while I was using it. But the threading method is often mentioned as the proper way of doing this as far as I know..
As a very limited example (short on time for a more extensive one), I did something like this in my old script:
ROOT_WINDOW = ""
VARIABLE1 = 'some variable here'
def func1():
global ROOT_WINDOW
global VARIABLE1
ROOT_WINDOW = Tk()
#(Insert application code that uses or requires variable1)
func2()
def func2():
global ROOT_WINDOW
global VARIABLE1
ROOT_WINDOW.Destroy()
Change variable1
func1()
With regard to setting a maximum number of login attempts. I solved this by creating a separate startup script. It works correctly but I dare not to post it here as it is a real ugly and incorrect way of solving the problem that also comes with a security issue (of having to store your sudo password within a variable).
Sorry for not being able to be of more help.
First of all I would change the bind to return to the root window instead of the entry (otherwise you have to click on the entry field for the return to have an effect)
Next you need 3 state variables for your class.
self.button1 = None
self.button2 = None
self.attempts = 0
Then by checking the state of each you can accomplish (what I think) you want
Here’s the whole code modified
from tkinter import *
class Application(object):
def __init__(self, event=None):
self.root = Tk()
self.root.configure(bg="darkorchid1", padx=10, pady=10)
self.root.title("WELCOME")
self.username = "Bob"
self.welcome = Label(self.root, text="WELCOME TO MY PROGRAM", bg="lightgrey", fg="darkorchid1")
self.welcome.pack()
self.label0 = Label(self.root, text="ENTER NAME:", bg="purple", fg="white", height=5, width=50)
self.label0.pack()
self.entry = Entry(self.root, width=25)
self.entry.configure(fg= "white",bg="grey20")
self.entry.pack()
self.root.bind("<Return>", self.submit)
self.button = Button(self.root, text="SUBMIT", highlightbackground="green", width=48, command=self.submit)
self.button.pack()
self.button1 = None
self.button2 = None
self.attempts = 0
def submit(self, event=None):
username = self.entry.get()
if username == self.username:
if (self.button2 != None): # after I added disabling the submit button this check might not be necessary
return
if (self.button1 == None):
self.button1 = Button(self.root, text='LOGIN', highlightbackground="green", width=28, command=self.root.destroy)
self.button1.pack()
self.root.bind("<Return>", self.login)
self.button.config(state="disabled")
else:
if (self.button2 == None):
self.button2 = Button(self.root, text="INCORRECT- CLICK TO DIMISS THIS MESSAGE", highlightbackground="red", width=48, command=self.incorrect)
self.button2.pack()
self.root.bind("<Return>", self.incorrect)
self.button.config(state="disabled")
def incorrect(self, event=None):
self.attempts += 1
if (self.attempts > 2):
self.root.destroy()
else:
self.root.bind("<Return>", self.submit)
self.button.config(state="normal")
self.button2.destroy()
self.button2 = None
def login(self, event=None):
self.root.destroy()
app=Application()
mainloop()
I am a Python beginning self-learner, running on MacOS.
I'm making a program with a text parser GUI in tkinter, where you type a command in a Entry widget, and hit a Button widget, which triggers my parse() funct, ect, printing the results to a Text widget, text-adventure style.
> Circumvent the button
I can't let you do that, Dave.
I'm trying to find a way to get rid of the need to haul the mouse over to the Button every time the user issues a command, but this turned out harder than I thought.
I'm guessing the correct code looks like self.bind('<Return>', self.parse())? But I don't even know where to put it. root, __init__, parse(), and create_widgets() don't want it.
To be clear, the only reason anyone should hit enter in the prog is to trigger parse(), so it doesn't need to be espoused to the Entry widget specifically. Anywhere it works is fine.
In response to 7stud, the basic format:
from tkinter import *
import tkinter.font, random, re
class Application(Frame):
def __init__(self, master):
Frame.__init__(self, master, ...)
self.grid()
self.create_widgets()
self.start()
def parse(self):
...
def create_widgets(self):
...
self.submit = Button(self, text= "Submit Command.", command= self.parse, ...)
self.submit.grid(...)
root = Tk()
root.bind('<Return>', self.parse)
app = Application(root)
root.mainloop()
Try running the following program. You just have to be sure your window has the focus when you hit Return--to ensure that it does, first click the button a couple of times until you see some output, then without clicking anywhere else hit Return.
import tkinter as tk
root = tk.Tk()
root.geometry("300x200")
def func(event):
print("You hit return.")
root.bind('<Return>', func)
def onclick():
print("You clicked the button")
button = tk.Button(root, text="click me", command=onclick)
button.pack()
root.mainloop()
Then you just have tweak things a little when making both the button click and hitting Return call the same function--because the command function needs to be a function that takes no arguments, whereas the bind function needs to be a function that takes one argument(the event object):
import tkinter as tk
root = tk.Tk()
root.geometry("300x200")
def func(event):
print("You hit return.")
def onclick(event=None):
print("You clicked the button")
root.bind('<Return>', onclick)
button = tk.Button(root, text="click me", command=onclick)
button.pack()
root.mainloop()
Or, you can just forgo using the button's command argument and instead use bind() to attach the onclick function to the button, which means the function needs to take one argument--just like with Return:
import tkinter as tk
root = tk.Tk()
root.geometry("300x200")
def func(event):
print("You hit return.")
def onclick(event):
print("You clicked the button")
root.bind('<Return>', onclick)
button = tk.Button(root, text="click me")
button.bind('<Button-1>', onclick)
button.pack()
root.mainloop()
Here it is in a class setting:
import tkinter as tk
class Application(tk.Frame):
def __init__(self):
self.root = tk.Tk()
self.root.geometry("300x200")
tk.Frame.__init__(self, self.root)
self.create_widgets()
def create_widgets(self):
self.root.bind('<Return>', self.parse)
self.grid()
self.submit = tk.Button(self, text="Submit")
self.submit.bind('<Button-1>', self.parse)
self.submit.grid()
def parse(self, event):
print("You clicked?")
def start(self):
self.root.mainloop()
Application().start()
Another alternative is to use a lambda:
ent.bind("<Return>", (lambda event: name_of_function()))
Full code:
from tkinter import *
from tkinter.messagebox import showinfo
def reply(name):
showinfo(title="Reply", message = "Hello %s!" % name)
top = Tk()
top.title("Echo")
Label(top, text="Enter your name:").pack(side=TOP)
ent = Entry(top)
ent.bind("<Return>", (lambda event: reply(ent.get())))
ent.pack(side=TOP)
btn = Button(top,text="Submit", command=(lambda: reply(ent.get())))
btn.pack(side=LEFT)
top.mainloop()
As you can see, creating a lambda function with an unused variable "event" solves the problem.
I found one good thing about using bind is that you get to know the trigger event: something like: "You clicked with event = [ButtonPress event state=Mod1 num=1 x=43 y=20]" due to the code below:
self.submit.bind('<Button-1>', self.parse)
def parse(self, trigger_event):
print("You clicked with event = {}".format(trigger_event))
Comparing the following two ways of coding a button click:
btn = Button(root, text="Click me to submit", command=(lambda: reply(ent.get())))
btn = Button(root, text="Click me to submit")
btn.bind('<Button-1>', (lambda event: reply(ent.get(), e=event)))
def reply(name, e = None):
messagebox.showinfo(title="Reply", message = "Hello {0}!\nevent = {1}".format(name, e))
The first one is using the command function which doesn't take an argument, so no event pass-in is possible.
The second one is a bind function which can take an event pass-in and print something like "Hello Charles! event = [ButtonPress event state=Mod1 num=1 x=68 y=12]"
We can left click, middle click or right click a mouse which corresponds to the event number of 1, 2 and 3, respectively. Code:
btn = Button(root, text="Click me to submit")
buttonClicks = ["<Button-1>", "<Button-2>", "<Button-3>"]
for bc in buttonClicks:
btn.bind(bc, lambda e : print("Button clicked with event = {}".format(e.num)))
Output:
Button clicked with event = 1
Button clicked with event = 2
Button clicked with event = 3
This worked for me while assigning buttons to functions.
Create a function to set buttons to other functions:
def enter(event=clr):
but_ch['command'] = clr
Then bind the function to the root and invoke:
root.bind('<Return>', lambda enter: but_ch.invoke())