How do I use a Tkinter Button for two states? - python

I have the following code, which causes a color/text change when a Tkinter button is clicked. I would like to revert to the original color/text when the button is clicked a second time.
from Tkinter import *
window = Tk()
window.title("Start/Stop Button")
window.geometry('200x100')
def clicked_rf1():
btn_rf1.configure(text="Stop")
lbl_rf1.configure(text=" ON ", bg="green")
btn_rf1 = Button(window, text="Start", command=clicked_rf1)
btn_rf1.grid(column=1, row=1)
lbl_rf1 = Label(window, text=" OFF ", bg="red")
lbl_rf1.grid(column=2, row=1)
window.mainloop()
I want something that behaves a little more like a toggle, but I would like the look of a button.
Help gratefully received.

You will need an if block to choose what to do. You can make another flag variable to keep track of the state, or just use the current Label or Button text:
from Tkinter import *
window = Tk()
window.title("Start/Stop Button")
window.geometry('200x100')
def clicked_rf1():
if btn_rf1['text'] == "Start":
btn_rf1.configure(text="Stop")
lbl_rf1.configure(text=" ON ", bg="green")
else:
btn_rf1.configure(text="Start")
lbl_rf1.configure(text=" OFF ", bg="red")
btn_rf1 = Button(window, text="Start", command=clicked_rf1)
btn_rf1.grid(column=1, row=1)
lbl_rf1 = Label(window, text=" OFF ", bg="red")
lbl_rf1.grid(column=2, row=1)
window.mainloop()
This would be an ideal place to make a custom Button subclass, so you could have many of these in your program:
from Tkinter import *
window = Tk()
window.title("Start/Stop Button")
window.geometry('200x100')
class Christina(Frame):
def __init__(self, master=None, **kwargs):
Frame.__init__(self, master, **kwargs)
self.btn = Button(self, text="Start", command=self.clicked)
self.btn.grid(column=0, row=0)
self.lbl = Label(self, text=" OFF ", bg="red")
self.lbl.grid(column=1, row=0)
def clicked(self):
if self.btn['text'] == "Start":
self.btn.configure(text="Stop")
self.lbl.configure(text=" ON ", bg="green")
else:
self.btn.configure(text="Start")
self.lbl.configure(text=" OFF ", bg="red")
btn1 = Christina(window)
btn1.grid()
btn2 = Christina(window)
btn2.grid()
btn3 = Christina(window)
btn3.grid()
window.mainloop()

If you want a toggle, you can use the checkbutton without an indicator. It has options for the color in the selected and deselected state, and you can tie the value and the label together so that the label changes when you toggle the button.
Like any button, you can tie a command to it. The command can check the value of the variable to determine whether it should do the "on" function or the "off" function.
Here's a simple example:
import Tkinter as tk
def toggle():
if var.get() == "ON":
print("turning on...")
else:
print("turning off...")
root = tk.Tk()
var = tk.StringVar()
toggle = tk.Checkbutton(root, onvalue="ON", offvalue="OFF", width=4,
indicatoron=False,
variable=var, textvariable=var,
selectcolor="green", background="red",
command=toggle)
var.set("OFF")
toggle.pack()
root.mainloop()

Another approach might be to put the "pile of code" to run into different function, collect those in an iterator, and then get the next function from that iterator and execute it:
def bunchofcode():
print("foo")
def somethingelse():
print("bar")
whattodo = iter([bunchofcode, somethingelse])
def clicked_rf1():
try:
next(whattodo)()
except StopIteration:
print("nothing to do")
Or for cyclic behaviour:
from itertools import cycle
whattodo = cycle([bunchofcode, somethingelse])
For a two-state toggle button, you could also use a dict to map the current state to the next. You could also use the button's relief to mark the state.
def clicked_rf1():
transition = {"raised": "sunken", "sunken": "raised"}
btn_rf1["relief"] = transition[btn_rf1["relief"]]

Related

Adding button (with variables) by pressing button - tkinter

I'm making a point of sale system and trying to implement a button, that when pressed a new button appears but also, a window which asks the user to input an Item
def newButton ():
w = Toplevel()
w.title("New Item") #creates new window when button is pressed
w.geometry("200x200")
itemNameLabel = Label(w, font=("arial", 15), text="What is the item called?")
itemNameLabel.grid(row=0, padx=5)
itemName = Entry(w, width=18, borderwidth=5)
itemName.grid(row=1, padx=5)
newItemName = itemName.get
itemPriceLabel = Label(w, font=("arial", 15), text="What is the item's price?")
itemPriceLabel.grid(row=4, padx=5)
itemPrice = Entry(w, width=18, borderwidth=5)
itemPrice.grid(row=5, padx=5)
def item6_Button():
global item6_qty
item6_price = itemPrice.get
item6_text = newItemName
item6_qty += 1
item6_text = (item6_text + " "+str(item6_price) +" "+ str(item6_qty)) #concatonates text & variable
item6.config(text=item6_text) #updates label text - doesn't add multiple
item6.pack()
item6_Button = Button(itemFrame, text=newItemName, width=10, height=5, command=item6_Button)
item6_Button.grid(row=7, column=1, padx=5)
item6 = Label(receiptFrame)
w.mainloop()
newButton= Button(itemFrame, text="Add New Button", width=20, height=5, command=newButton) #creates button for new window
newButton.place(x=480, y=600)
newButton = Label(itemFrame)
*item6_qty and item6_price are declared near the beginning of the program
This is what I have so far and although the window appears, I don't think the variables are actually set, on top of the new button appearing in the item frame. I'm not entirely sure how to go about this - do I need to use .insert for the variables?
This is the standard code I have which creates the normal button
#Item1 Button + Function
def item1_Button():
global item1_qty #making qty variable global so it can used
item1_text = ("Chips")
item1_qty += 1 #increments qty variable by one everytime button is clicked
item1_text = (item1_text + " "+str(item1_price) +" "+ str(item1_qty)) #concatonates text & variable
item1.config(text=item1_text) #updates label text - doesn't add multiple
item1.pack() #places label within the frame
item1_Button = Button(itemFrame, text="Chips", width=10, height=5, command=item1_Button)
#creates button + links to function
item1_Button.grid(row=4, column=1, padx=5) #positions button
item1 = Label(receiptFrame)#creates label for button
I'm not sure if I've provided enough code of what I've done to give a better picture of what I'm trying to achieve but I know large chunks of code aren't very favoured
here is an example of what You could do (does this help?):
from tkinter import Tk, Button, Entry, Toplevel
class MainWindow(Tk):
def __init__(self):
Tk.__init__(self)
self.geometry('100x150')
self.btn = Button(self, text='Create New!', command=self.ask)
self.btn.pack()
def ask(self):
ask_window = InputWindow(self)
ask_window.focus_force()
def create(self, text):
button = Button(self, text=text)
button.pack()
class InputWindow(Toplevel):
def __init__(self, parent):
Toplevel.__init__(self, parent)
self.parent = parent
self.bind('<FocusOut>', self.destroy_)
self.user_input = Entry(self)
self.user_input.pack()
self.submit_btn = Button(self, text='Submit!', command=self.retrieve)
self.submit_btn.pack()
def retrieve(self):
text = self.user_input.get()
self.parent.create(text)
self.destroy()
def destroy_(self, event):
if isinstance(event.widget, Toplevel):
self.destroy()
root = MainWindow()
root.mainloop()

Clearing the screen in Tkinter

I have tried all the other posts on this topic but none of them have worked for me...
Here is my code:
from tkinter import *
window=Tk()
window.geometry('600x400')
window.title('hello world')
def wrong():
root=Tk()
text=Text(root)
text.insert(INSERT,"WRONG!, you stupid idiot!!!!")
text.pack()
def right():
root=Tk()
text=Text(root)
text.insert(INSERT,"CORRECT, good job!")
text.pack()
def reset():
hide_widgets()
class UIProgram():
def setupUI(self):
buttonlist=[]
button= Button(window,text='Sanjam',command=wrong).pack()
button2=Button(window,text='Sunny the Bunny',command=wrong).pack()
button3= Button(window, text='Sunjum',command=right).pack()
button4= Button(window, text='bob',command=wrong).pack()
button5= Button(window, text='next',command=reset)
button5.pack()
self.label=Label(window)
self.label.pack()
window.mainloop()
program= UIProgram()
program.setupUI()
I am aware of pack_forget() and tried it, but it keeps giving me an error. Also, is it possible to make a command(like the 'reset' one I have) and use that in the command for a clear screen button. Please help, I am new to Tkinter and don't know much about these things..
Thanks
Example code
I'm tired to describe the same mistakes hundreds of times - some comments in code.
import tkinter as tk
# --- classes ---
class UIProgram():
def __init__(self, master):
self.master = master # use to add elements directly to main window
self.buttons = [] # keep buttons to change text
# frame to group buttons and easily remove all buttons (except `Next`)
self.frame = tk.Frame(self. master)
self.frame.pack()
# group button in frame
button = tk.Button(self.frame, text='Sanjam', command=self.wrong)
button.pack()
self.buttons.append(button)
button = tk.Button(self.frame, text='Sunny the Bunny', command=self.wrong)
button.pack()
self.buttons.append(button)
button = tk.Button(self.frame, text='Sunjum', command=self.right)
button.pack()
self.buttons.append(button)
button = tk.Button(self.frame, text='bob', command=self.wrong)
button.pack()
self.buttons.append(button)
# button outside frame
button_next = tk.Button(self.master, text='Next >>', command=self.reset)
button_next.pack()
self.label = tk.Label(self.frame)
self.label.pack()
def wrong(self):
# create second window with message and closing button
win = tk.Toplevel()
tk.Label(win, text="WRONG!, you stupid idiot!!!!").pack()
tk.Button(win, text='close', command=win.destroy).pack()
def right(self):
# create second window with message and closing button
win = tk.Toplevel()
tk.Label(win, text="CORRECT, good job!").pack()
tk.Button(win, text='close', command=win.destroy).pack()
def reset(self):
# remove frame with all buttons
self.frame.pack_forget()
tk.Label(self.master, text="frame removed").pack()
# or only remove text in labels
#for button in self.buttons:
# button['text'] = '-- place for new text --'
# --- main ---
root = tk.Tk()
root.geometry('600x400')
root.title('hello world')
program = UIProgram(root)
root.mainloop()
BTW: if you do var = Widget(...).pack() then you assign None to var because pack()/grid()/place() return None. You have to do it in two lines
var = Widget(...)
var.pack()
or in one line if you don't need var
Widget(...).pack()

tkinter button animation stuck after using wait_window()

This is a dialog form class :
** update full workable source code showing the problem
from tkinter import *
class SGForm:
created_form = False
def __init__(self, root, title=""):
self.form = Toplevel(root)
self.form.wm_title(title)
self.input = dict()
self.var = StringVar()
SGForm.created_form = True
def getform(self):
return self.form
def addinput(self, name, text ,var = None):
p = Frame(self.form)
p.pack(side="top", fill="both", expand=True, padx=10, pady=10)
l = Label(p, text=text)
l.pack(side="left", fill="both", expand=True, padx=10, pady=10)
self.input[name] = Entry(p, textvariable=var)
self.input[name].pack(side="left", fill="both", expand=True, padx=10, pady=10)
def addbutton(self, text, signal, func):
p = Frame(self.form)
p.pack(side="top", fill="both", expand=True, padx=10, pady=10)
b = Button(p, text=text)
b.pack(side="left", fill="both", expand=True, padx=10, pady=10)
b.bind(signal, func)
def showandreturn(self):
value = dict()
value['firstname'] = self.var.get()
SGForm.created_form = False
return value
def closeform(self, event):
self.form.destroy()
def customform(self):
self.addinput('entfirstname', 'frist name', self.var)
self.addbutton('close','<Button-1>', self.closeform)
#example calling dialog class
root = Tk()
def evntshow(event):
form = SGForm(root)
form.customform()
root.wait_window(form.getform())
test = form.showandreturn()
print(test)
button = Button(root, text='show')
button.pack()
button.bind('<Button-1>', evntshow)
root.mainloop()
Each time the button get pressed eventaddperson get triggered, when exiting the function the button animation of the main window get stuck on press status, I am looking for a way to refresh the gui or what if I am doing something wrong how to fix it?
If I use command= instead of bind() then problem disappers
BTW: if you use command= then def evntshow()has to be without event
def evntshow(): # <--- without event
form = SGForm(root)
form.customform()
root.wait_window(form.getform())
test = form.showandreturn()
print(test)
# use `command=` instead of `bind('<Button-1>',...)
button = Button(root, text='show', command=evntshow)
button.pack()
I was experiencing kind of laggy button animations when using bind() as well, switching to command= made it a look a lot better!
from tkinter import *
import time
def func1():
print('waiting for 1 second...')
time.sleep(1)
def func2(event):
print('waiting for 1 second...')
time.sleep(1)
root = Tk()
# button animation runs smoothly
Button1 = Button(root, text="button with command=", command=func1)
Button1.pack()
Button2 = Button(root, text="button with bind()") # button animation does not occur
Button2.bind('<Button-1>', func2)
Button2.pack()
root.mainloop()
I am working with python 3.6 and windows 10

How Transfer cursor in tkinter?

i wrote bellow code in python 3.6.2 by tkinter,I want the cursor move to password textbox when user press Enter key in username textbox.
from tkinter import *
class Application(Frame):
def __init__(self,master):
super(Application, self).__init__(master)
self.grid()
self.create_main()
def create_main(self):
print("testing")
self.title = Label(self, text=" Stuck In The Circle ")
self.title.grid(row=0, column=2)
self.user_entry_label = Label(self, text="Username: ")
self.user_entry_label.grid(row=1, column=1)
self.user_entry = Entry(self)
self.user_entry.grid(row=1, column=2)
self.pass_entry_label = Label(self, text="Password: ")
self.pass_entry_label.grid(row=2, column=1)
self.pass_entry = Entry(self)
self.pass_entry.grid(row=2, column=2)
self.user_entry = Entry(self, justify="right")
self.pass_entry = Entry(self, justify="right")
self.sign_in_butt = Button(self, text="Sign In",command = self.logging_in)#SIGN IN BUTTON
self.sign_in_butt.grid(row=5, column=2)
def logging_in(self):
user_get = self.user_entry.get()
pass_get = self.pass_entry.get()
root = Tk()
root.title("Stuck in the Circle")
root.geometry("400x100")
app = Application(root)
root.mainloop()
How can do it?
This is actually a lot simpler than I expected it to be.
We can use .bind() to get a callback on the <Return> event. This means that every time the return character is pressed whenever the defined widget is in focus we get a callback.
In order to get it to cycle to the next widget we can use this answer from Bryan Oakley:
def focus_next_window(event):
event.widget.tk_focusNext().focus()
return("break")
text_widget=Text(...) text_widget.bind("<Tab>", focus_next_window)
Important points about this code:
The method tk_focusNext() returns the next widget in the keyboard
traversal hierarchy. the method focus() sets the focus to that widget
returning "break" is critical in that it prevents the class binding
from firing. It is this class binding that inserts the tab character,
which you don't want.
So, applying the same logic in our situation we can use something like the below:
from tkinter import *
class App:
def __init__(self, root):
self.root = root
self.entry1 = Entry(self.root)
self.entry2 = Entry(self.root)
self.entry1.pack()
self.entry2.pack()
self.entry1.bind("<Return>", self.callback)
def callback(self, event):
event.widget.tk_focusNext().focus()
root = Tk()
App(root)
root.mainloop()

Can I double-click a tkinter Listbox option to invoke function in Python?

I have a Listbox with an associated "Select" button. I want my GUI such that a double-click on any Listbox value invokes this button's command. My attempt (below) works when an option is selected and the user double-clicks ANYWHERE in the window. I want it to work only when the selection itself (blue highlighted row) is being double-clicked.
What is the best way to do this?
from tkinter import *
def func1():
print("in func1")
def func2():
print("in func2")
def selection():
try:
dictionary[listbox.selection_get()]()
except:
pass
root = Tk()
frame = Frame(root)
frame.pack()
dictionary = {"1":func1, "2":func2}
items = StringVar(value=tuple(sorted(dictionary.keys())))
listbox = Listbox(frame, listvariable=items, width=15, height=5)
listbox.grid(column=0, row=2, rowspan=6, sticky=("n", "w", "e", "s"))
listbox.focus()
selectButton = Button(frame, text='Select', underline = 0, command=selection)
selectButton.grid(column=2, row=4, sticky="e", padx=50, pady=50)
root.bind('<Double-1>', lambda x: selectButton.invoke())
root.mainloop()
Change root.bind(...) to listbox.bind(...)
In binding you should use sequence which should be passes inside < > and pass function:
listbox.bind('<Double-Button>', function)

Categories

Resources