making a menu(restaurant menu) in python with the tkinter module - python-3.x

using tkinter with python, i am making a menu that shows what you put in. then can be removed by pressing a button, my question is how do i delete the Labels in the right order. the order should be from top to bottom. the "orders" come in by date, the oldest being at the top and the newest being put at the bottom of the list. By using destroy(), i can only manage to delete the very last label(the newest). i need help to figure out how to delete a specific label.
#imports-----------------------
from tkinter import *
#from PIL import ImageTk,image
from tkinter import messagebox
import time
#imports----------------------
#start-----------
x=1
height=0
l="0"
root = Tk()
root.geometry("300x200")
#start-----------
def myClick():
global x #number of the label
global height
global l #name of the label
global vartoprint
global y #to retain the old value of x
if x == 1:
global statehold
statehold=1
l=str(statehold)
vartoprint="order one"
y=1
if x > y: #when x is different than the old value of y, we assume the state has gone up
#and therefor a new entry has come, time to print this new value
height=height+1
statehold= statehold + 1
l=str(statehold)
vartoprint="order 'xxx'" #in this case, the new value is always "order xxx" for testing
l = Label(root, text=vartoprint)
l.grid(row=height, column=L)
x=x+1
mybutton = Button(root, text="next", padx=10, pady=8, command=myClick, fg="black", bg="white")
mybutton.grid(row=1, column=5)
def mydelete():
global statehold
global x
global l
statehold= statehold - 1 #tells the system that 1 label has been removed
l.destroy() #destroys the label
x=x-1 #tells the system that 1 label has been removed
DeleteButton = Button(root, text="next and delete", command=mydelete)
DeleteButton.grid(row=1, column=6)
#end-------------
time.sleep(3)
root.mainloop()
#end-------------

thanks using a list and deleting the first list item, i am able to do my task.
I use
my_list.append(l)
to insert the variable at the end of the list.
and like you said, i use
(my_list.pop(0)).destroy()
do remove the first item in the list.
thanks, you were great!

Related

Change the width of an updated listbox in a ttk Combobox

This is an extension to the question and answers here.
I want to create two Comboboxes, with the items in the second Combobox depending on the selection in the first Combobox. Furthermore, I would like the dropdown listbox to resize to fit the text in the list, as in the answer here. However, I'm having some difficulties with this second part. I'd like to solve this problem using Python.
I've had varying results using .pack() and .forget() instead of .place() and .place_forget(), however I haven't been able to create a robust solution. Using .place is preferable to .pack or .grid if possible.
As an MWE, I've extended the code from one of the answers in the previous question. The dropdown listbox of c2 resizes fine, however that of c1 does not.
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.font as tkfont
def on_combo_configure(event):
combo = event.widget
style = ttk.Style()
# check if the combobox already has the "postoffest" property
current_combo_style = combo.cget('style') or "TCombobox"
if len(style.lookup(current_combo_style, 'postoffset'))>0:
return
combo_values = combo.cget('values')
if len(combo_values) == 0:
return
longest_value = max(combo_values, key=len)
font = tkfont.nametofont(str(combo.cget('font')))
width = font.measure(longest_value + "0") - event.width
if (width<0):
# no need to make the popdown smaller
return
# create an unique style name using widget's id
unique_name='Combobox{}'.format(combo.winfo_id())
# the new style must inherit from curret widget style (unless it's our custom style!)
if unique_name in current_combo_style:
style_name = current_combo_style
else:
style_name = "{}.{}".format(unique_name, current_combo_style)
style.configure(style_name, postoffset=(0,0,width,0))
combo.configure(style=style_name)
def update_c1_list(event):
c1.place_forget()
_ = c.get()
if _ == "fruit":
c1['values'] = ('apples are the best', 'bananas are way more better')
elif _ == "text":
c1['values'] = ("here's some text", "and here's some much longer text to stretch the list")
else:
pass
c1.place(x=10,y=40)
root = tk.Tk()
root.title("testing the combobox")
root.geometry('300x300+50+50')
c = ttk.Combobox(root, values=['fruit','text'], state="readonly", width=10)
c.bind('<Configure>', on_combo_configure)
c.bind('<<ComboboxSelected>>', update_c1_list)
c.place(x=10,y=10)
c1 = ttk.Combobox(root, state="readonly", width=10)
c1.bind('<Configure>', on_combo_configure)
c1.place(x=10,y=40)
c2 = ttk.Combobox(root, state="readonly", width=10)
c2.bind('<Configure>', on_combo_configure)
c2.place(x=10,y=70)
c2['values']=('this list resizes fine','because it is updated outside of the function')
root.mainloop()
Any help is welcome, thanks.
After playing around I came up with a function which updates the listbox width of a combobox "on the fly". However, it's a bit like fixing a window with a hammer and causes some issues.
def Change_combo_width_on_the_fly(combo,combo_width):
style = ttk.Style()
# check if the combobox already has the "postoffest" property
current_combo_style = combo.cget('style') or "TCombobox"
combo_values = combo.cget('values')
if len(combo_values) == 0:
return
longest_value = max(combo_values, key=len)
font = tkfont.nametofont(str(combo.cget('font')))
width = font.measure(longest_value + "0") - (combo_width*6+23)
if (width<0):
# no need to make the popdown smaller
return
# create an unique style name using widget's id
unique_name='Combobox{}'.format(combo.winfo_id())
# the new style must inherit from curret widget style (unless it's our custom style!)
if unique_name in current_combo_style:
style_name = current_combo_style
else:
style_name = "{}.{}".format(unique_name, current_combo_style)
style.configure(style_name, postoffset=(0,0,width,0))
combo.configure(style=style_name)
As a MWE, the code can be used as follows:
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.font as tkfont
root = tk.Tk()
c1 = ttk.Combobox(root, state="readonly", width=10)
c1.place(x=10,y=40)
Change_combo_width_on_the_fly(c1,10)
root.mainloop()
While the function does the job, it causes problems elsewhere in my code. In particular, it messes with a previously packed widget (scrollbar). I think it is changing the style the last placed widget, but I don't know how to fix this.

Tkinter create multiple buttons in a loop and change text of clicked ones

I try to access/change button attributes for buttons created in a loop.
My idea was to collect the buttons in a list so that i can access every single button. Is there a better way?
At the moment i try to use the buttons-command to change the text of the clicked button. In the "action"-function i get the error-code "list index out of range" when i try to run the code!?
Since i am a newbie in python and tkinter hours over hours passed so far to find a solution without success.
Every idea would be very appreciated.
I used quite the same code without creating a list. The code was working but when i clicked a button only the last created button changed the text. Could it be that somehow i have to use the "StringVar"-function or "textvariable"?
import tkinter as tk
window = tk.Tk()
window.geometry("300x150")
window.title("Tic Tac Toe")
def action(i):
btns[i].configure(text = 'X')
btn_nr = -1
btns = []
for x in range(1,4):
for y in range(1,4):
btn_nr += 1
print(btn_nr)
btns.append(tk.Button(text='-', command = action(int(btn_nr))))
btns[int(btn_nr)].grid(row=x, column=y)
exit_button = tk.Button(text='Exit Game', command=window.destroy)
exit_button.grid(row=4, column=1, columnspan=15)
window.mainloop()
You were almost there. See below matter being resolved as you need a lambda to pass the btn_nr to the function action. By the way there is no need for int()
import tkinter as tk
window = tk.Tk()
window.geometry("300x150")
window.title("Tic Tac Toe")
def action(button):
if btns[button].cget('text') == '-':
btns[button].configure(text='X')
else:
btns[button].configure(text='-')
btn_nr = -1
btns = []
for x in range(1, 4):
for y in range(1, 4):
btn_nr += 1
print(btn_nr)
btns.append(tk.Button(text='-', command=lambda x=btn_nr: action(x)))
btns[btn_nr].grid(row=x, column=y)
exit_button = tk.Button(text='Exit Game', command=window.destroy)
exit_button.grid(row=4, column=1, columnspan=15)
window.mainloop()
I changed the action function a little to make it toggle between 'X' and '-'.

Tkinter : Recording entries dynamically created by buttons

I'm trying to allow the user to enter as many strings as he wants by clicking some sort of '+' button, and keep the strings in a list. (He enters a first string, clicks '+', another entry box appears, etc.)
For the moment, here's what I've got:
def addEntry(window, r, e):
if r < 9:
global entries
entries.append(e.get())
r += 1
e = tk.Entry(window)
e.grid(column=1, row=r)
add = tk.Button(window, text=' + ', command=lambda:addEntry(window, r, e))
add.grid(column=2, row=r, sticky=tk.W)
else:
errmsg = 'Max. 10 items'
tk.Label(window, text=errmsg).grid(column=1, row=r+1)
import tkinter as tk
global entries # the main list of strings
entries = []
r = 0 # index for rows ; will not be 0 in the final code as there will be other widgets
win = tk.Tk()
e = tk.Entry(win)
e.grid(column=1, row=r)
add = tk.Button(win, text=' + ', command=lambda:addEntry(win, r, e))
add.grid(column=2, row=r, sticky=tk.W)
win.mainloop()
This isn't elegant, and the last entry is not recorded.
I've tried making entries a list of Entry() items (not e.get() items), but then I can't access the strings (TclError: invalid command name ".!entry4"). I've tried emulating this, which led me to make entries a global variable ; I've tried using this, but I don't fully understand the first answer, and as far as I can tell the strings aren't recorded ; I've tried adapting the class defined in the second answer, but I wasn't able to add buttons dynamically. (I like the idea of making a class, though.) I feel like I should be able to do this, but after a wasting a day on it, might as well ask for help.
Don't put the value in a list, put the actual widget. You should only call the get method when you actually need the values.
Also, I strongly recommend you put the entries in a dedicated frame. That way you don't have to worry about what other widgets might be in the window, and you don't have to juggle row and column numbers. Put them in a frame and use pack since they are stacked top-to-bottom and all share the same size.
Example:
import tkinter as tk
def addEntry(window):
global entries
if len(entries) > 10:
error_message.configure(text="Max. 10 items")
else:
error_message.configure(text="")
entry = tk.Entry(window)
entry.pack(side="top", fill="x")
entries.append(entry)
def show():
for entry in entries:
print("=>", entry.get())
entries = []
win = tk.Tk()
entry_frame = tk.Frame(win)
error_message = tk.Label(win)
error_message.grid(row=1, column=0, sticky="nsew")
add = tk.Button(win, text=' + ', command=lambda: addEntry(entry_frame))
show = tk.Button(win, text="Show values", command=show)
add.grid(row=0, column=1, sticky=tk.NW)
show.grid(row=0, column=2, sticky=tk.NW)
entry_frame.grid(row=0, column=0, sticky="nsew")
# create the first entry automatically
addEntry(entry_frame)
win.mainloop()

Python 3 Radio button controlling label text

I am in the process of learning Python3 and more of a necessity, the TkInter GUI side. I was working my way through a book by James Kelly, when I encountered this problem. All his examples made a new window with just label/canvas/check box etc which seemed to work OK.
But as I wanted to experiment in a more real world scenario I put most things on one window. This where I encountered my problem. I can not get the radio button in the frame to alter the wording of a label in the parent window.
Complete code is:-
#! /usr/bin/python3
from tkinter import *
def win_pos(WL,WH,xo=0,yo=0) :
# Screen size & position procedure
# Screen size
SW = home.winfo_screenwidth()
SH = home.winfo_screenheight()
# 1/2 screen size
sw=SW/2
sh=SH/2
# 1/2 window size
wl=WL/2
wh=WH/2
# Window position
WPx=sw-wl+xo
WPy=sh-wh+yo
# Resulting string
screen_geometry=str(WL) + "x" + str(WH) + "+" + str(int(WPx)) + "+" \ + str(int(WPy))
return screen_geometry
# Create a window
home=Tk()
home.title("Radio buttons test")
# Set the main window
home.geometry(win_pos(600,150))
lab1=Label(home)
lab1.grid(row=1,column=1)
fraym1=LabelFrame(home, bd=5, bg="red",relief=SUNKEN, text="Label frame text")
fraym1.grid(row=2,column=2)
laybl1=Label(fraym1, text="This is laybl1")
laybl1.grid(row=0, column=3)
var1=IntVar()
R1=Radiobutton(fraym1, text="Apple", variable=var1, value=1)
R1.grid(row=1, column=1)
R2=Radiobutton(fraym1, text="Asus", variable=var1, value=2)
R2.grid(row=1, column=2)
R3=Radiobutton(fraym1, text="HP", variable=var1, value=3)
R3.grid(row=1, column=3)
R4=Radiobutton(fraym1, text="Lenovo", variable=var1, value=4)
R4.grid(row=1, column=4)
R5=Radiobutton(fraym1, text="Toshiba", variable=var1, value=5)
R5.grid(row=1, column=5)
# Create function used later
def sel(var) :
selection="Manufacturer: "
if var.get() > 0 :
selection=selection + str(var.get())
lab1.config(text=selection)
R1.config(command=sel(var1))
R2.config(command=sel(var1))
R3.config(command=sel(var1))
R4.config(command=sel(var1))
R5.config(command=sel(var1))
R1.select()
mainloop()
I realise that there is room for improvement using classes/functions but I need to get this resolved in my head before I move on. As it can be hopefully seen, I'm not a complete novice to programming, but this is doing my head in.
Can a solution, and reasoning behind the solution, be given?
You can modify your label's text by assigning the same variable class object, var1 as its textvariable option as well but since lab1's text is slightly different, try removing:
R1.config(command=sel(var1))
R2.config(command=sel(var1))
R3.config(command=sel(var1))
R4.config(command=sel(var1))
R5.config(command=sel(var1))
R1.select()
and modify sel to:
def sel(*args) :
selection="Manufacturer: "
selection=selection + str(var1.get())
lab1.config(text=selection)
and then call var1.trace("w", sel) somewhere before mainloop as in:
...
var1.trace("w", sel)
mainloop()
Also for a simple example:
import tkinter as tk
root = tk.Tk()
manufacturers = ["man1", "man2", "man3", "man4", "man5"]
lbl = tk.Label(root, text="Please select a manufacturer.")
lbl.pack()
# create an empty dictionary to fill with Radiobutton widgets
man_select = dict()
# create a variable class to be manipulated by radiobuttons
man_var = tk.StringVar(value="type_default_value_here_if_wanted")
# fill radiobutton dictionary with keys from manufacturers list with Radiobutton
# values assigned to corresponding manufacturer name
for man in manufacturers:
man_select[man] = tk.Radiobutton(root, text=man, variable=man_var, value=man)
#display
man_select[man].pack()
def lbl_update(*args):
selection="Manufacturer: "
selection=selection + man_var.get()
lbl['text'] = selection
#run lbl_update function every time man_var's value changes
man_var.trace('w', lbl_update)
root.mainloop()
Example with label's identical to that of radiobutton's value:
import tkinter as tk
root = tk.Tk()
# radiobutton group will the button selected with the value=1
num = tk.IntVar(value=1)
lbl = tk.Label(root, textvariable=num)
zero = tk.Radiobutton(root, text="Zero", variable=num, value=0)
one = tk.Radiobutton(root, text="One", variable=num, value=1)
#display
lbl.pack()
zero.pack()
one.pack()
root.mainloop()

how to create clear entry text in tkinter and display on to the window

I'm having trouble with finishing a program and I need some help with. I have to clear the Entry text area so that I can retype from the start when I click the "save" button. Also, it would be great if someone could help me with a way to display the Entry text data into the window display if that is possible, but right now, I mainly need to figure out how to clear the entry text area.
Here's the current program, its looks awful but it works.
Also, is it possible to create a function definition to make the window clear if the script is done? Thanks
from tkinter import *
#this function will save the data from tkinter to .txt file.
def save_data():
fileD = open("names.txt", "a")
fileD.write("Name_List:\n")
fileD.write("%s\n" % name.get())
#this section will create GUI widget window containing lable, Entry and buttons here.
app = Tk()
app.title('Name Library')
Label(app, text = "Please Enter Name Here:", fg="black").pack()
name = Entry(app)
name.pack()
Label(app, text="New Name will Display Here if Name Changed: ", fg="gold").pack()
Button(app, text = "Save", fg="red", command = save_data).pack()
app.configure(background="green")
app.mainloop()
To clear your entry just use .delete(0, END)
name = Entry(root)
name.delete(0, END) # clear the entry field
For entry widgets the first character starts at 0. You can use 'end' or END to delete up to the last character of the entry widget or use a set value as well.
Look towards this guide for your tkinter neeeds.
Example Code:
import tkinter as tk
import os
def save_data():
text = name.get().strip()
if text: # checks for empty entries
f = open('names.txt', 'a')
f.write(text + '\n')
f.close()
name.delete(0, tk.END)
# Checks if the file exists
# if not then create it and
# write the header 'Name List'
if not os.path.exists('names.txt'):
f = open('names.txt', 'w')
f.write('Name_List:\n')
f.close()
root = tk.Tk()
tk.Label(root, text = "Please Enter Name Here:").pack()
name = tk.Entry(root)
name.pack()
tk.Button(root, text = 'Save', command = save_data).pack()
root.mainloop()

Resources