Python Tkinter row delete function only works once - python

Below is the code for a smaller version of a project I am working on. Basically when I try to delete one of the entries, the delete function works, but only once. After that it starts duplicating the entries I have deleted until giving me the IndexError: list assignment index out of range
from tkinter import *
gui = Tk()
details_list = []
counters = {'total_entires':0, 'entries_count':0}
error_print = Label(gui)
name_entry = Entry(gui)
name_entry.grid(column=1, row=0, sticky=W)
delete_row_entry = Entry(gui, width=5)
delete_row_entry.grid(column=4,row=3, sticky=W, pady=(2,0))
def print_entries():
global details_list, row_display, name_display
line_no = ""
for x in range(len(details_list)):
line_no = str(x + 1)
row_display = Label(gui, text=line_no)
row_display.grid(column=0, row=x+2)
name_display = Label(gui, text=(details_list[x][0]))
name_display.grid(column=1, row=x+2)
def delete_row():
global details_list, row_display
del details_list[int(delete_row_entry.get()) - 1]
entries_count = counters['entries_count']
counters['total_entires'] -= 1
delete_row_entry.delete(0,'end')
row_display.destroy()
name_display.destroy()
print_entries()
def append_name():
details_list.append([name_entry.get()])
counters['total_entires'] += 1
def labels():
Label(gui, text='Row').grid(column=0, row=1, sticky=W, padx=(0,5))
Label(gui, text='Name').grid(column=1, row=1, sticky=W, padx=5)
def buttons():
Button(gui, text='Quit', command=quit, width=15).grid(column=4, row=0, sticky=W)
Button(gui, text='Append Details', command=append_name, width=15).grid(column=4, row=1,
sticky=W)
Button(gui, text='Print Details', command=print_entries, width=15).grid(column=4, row=2,
sticky=W)
Button(gui, text='Delete Row', command=delete_row, width=10).grid(column=4, row=3,
sticky=E)
def main():
labels()
buttons()
gui.mainloop()
main()

There are two issues in your code:
you have created new set of labels whenever print_entries() is executed
the two .destroy() lines just destroy the labels in the last row of the set of labels created inside print_entries()
To fix the issues:
create a list to store those labels created inside print_entries()
delete existing labels before populating new labels inside print_entries()
Below are the modified print_entries() and delete_row() functions:
...
# create a list to store those labels
label_list = []
def print_entries():
# clear current table
for w in label_list:
w.destroy()
label_list.clear()
# show the new table
for x in range(len(details_list)):
row_display = Label(gui, text=x+1)
row_display.grid(column=0, row=x+2)
name_display = Label(gui, text=details_list[x][0])
name_display.grid(column=1, row=x+2)
# store the labels in the list
label_list.extend([row_display, name_display])
def delete_row():
try:
size = len(details_list)
idx = int(delete_row_entry.get()) - 1 # may raise exception
if size and 0 <= idx < size:
del details_list[idx]
# below line seems useless
#entries_count = counters['entries_count']
counters['total_entires'] -= 1
delete_row_entry.delete(0, 'end')
print_entries()
else:
print('row number is out of range' if size else 'empty list')
except ValueError as ex:
print(ex)
...

Related

How to pass two lists from different functions into save() function in Tkinter

I have two functions that create X number of entry widgets based on the number a user inputted:
def Pap_geo():
num_Pap = int(Pap.get())
Pap_GEOs = []
for i in range(num_Pap):
gt_geo = Entry(top, width=20)
gt_geo.focus_set()
gt_geo.grid(row=2+i, column=0, padx=20, pady=0, sticky="W")
Pap_GEOs.append(gt_geo)
return Pap_GEOs
and:
def Sap_geo():
num_Sap = int(Sap.get())
Sap_GEOs = []
for i in range(num_Sap):
Sap_geo = Entry(top, width=20)
Sap_geo.focus_set()
Sap_geo.grid(row=2 + i, column=1, padx=20, pady=0, sticky="W")
Sap_GEOs.append(Sap_geo)
return Sap_GEOs
I want to be able to click 'ok' and print the results of these two lists. I've gotten as far as:
def save():
Pap_GEOs2 = []
for j in Pap_geo():
Pap_GEOs2.append(j.get())
print(Pap_GEOs2)
Sap_GEOs2 = []
for j in Sap_geo():
Sap_GEOs2.append(j.get())
print(Sap_GEOs2)
button = Button(top, text="Save", command=save)
button.grid(row=1, column=1, padx=(170, 0), pady=(0, 10), sticky="W")
This prints two lists of the correct length, however they are empty. I had a similar question before, which was resolved. The solution was to create a list of the entry widgets then call get() on each of those widgets in the OK function. I thought that's what I was doing here but I am getting the same issue. Any input would be appreciated.
Thank you

How to Add up multiple entry in Tkinter

I work on a program that calculates the macros of each meal. You can enter a value in gram and it calculates for each aliment your intake. I would like now to add these values together when I push multiple buttons. Then I'll display the value somewhere and maybe I could do a graph after.
Here I show what my program looks like :
import tkinter as tk
def value_kiwi():
value = ent_quantity.get()
formula = (float(value)/100)
cal = 53
pro = 1.6
glu = 11.1
li = 0.3
Calories = (cal) * formula
Protéines = (pro) * formula
Glucides = (glu) * formula
Lipides = (li) * formula
lbl_cal["text"] =f"{Calories}"
lbl_prot["text"] = f"{Protéines}"
lbl_glu["text"] = f"{Glucides}"
lbl_lip["text"] = f"{Lipides}"
def value_banane():
value = ent_quantity.get()
formula = (float(value)/100)
cal = 90
pro = 1.5
glu = 20.1
li = 0
Calories = (cal) * formula
Protéines = (pro) * formula
Glucides = (glu) * formula
Lipides = (li) * formula
lbl_cal["text"] =f"{Calories}"
lbl_prot["text"] = f"{Protéines}"
lbl_glu["text"] = f"{Glucides}"
lbl_lip["text"] = f"{Lipides}"
window = tk.Tk()
window.title("Calculateur de Calories et Nutriments")
frm_entry = tk.Frame(master=window)
ent_quantity = tk.Entry(master=frm_entry, width=5)
ent_quantity.grid(row=1, column=0,)
lbl_cal = tk.Label(master=window)
lbl_cal.grid(row=1, column=1,)
lbl_prot = tk.Label(master=window)
lbl_prot.grid(row=1, column=2)
lbl_glu = tk.Label(master=window)
lbl_glu.grid(row=1, column=3)
lbl_lip = tk.Label(master=window)
lbl_lip.grid(row=1, column=4)
btn_kiwi = tk.Button(
master=window,
text="Kiwi",
command=value_kiwi,
)
btn_banane = tk.Button(
master=window,
text="Banane",
command=value_banane,
)
lbl_calories = tk.Label(master=window, text="Calories",)
lbl_proteines = tk.Label(master=window, text="Protéines")
lbl_glucides = tk.Label(master=window, text="Glucides")
lbl_lipides = tk.Label(master=window, text="Lipides")
lbl_fruits = tk.Label(master=window, text="Fruits")
frm_entry.grid(row=1, column=0, padx=10)
lbl_calories.grid(row=0,column=1, padx=5, sticky="w")
lbl_proteines.grid(row=0, column=2, padx=10)
lbl_glucides.grid(row=0, column=3, padx=10)
lbl_lipides.grid(row=0,column =4, padx=10)
lbl_fruits.grid(row=1,column =5, padx=10)
btn_kiwi.grid(row=2, column=5, pady=10)
btn_banane.grid(row=3, column=5, pady=10)
window.mainloop()
Ok, I think I improved Your code:
from tkinter import Tk, Button, Entry, Label
class MainWindow(Tk):
def __init__(self):
Tk.__init__(self)
self.title('Nutrient calculator')
# entry
Label(self, text='Amount').grid(row=0, column=0)
self.amount = Entry(self, width=5, bg='light grey')
self.amount.grid(row=1, column=0)
# calories
Label(self, text='Calories').grid(row=0, column=1)
self.calories = Label(self)
self.calories.grid(row=1, column=1)
# proteins
Label(self, text='Proteins').grid(row=0, column=2)
self.proteins = Label(self)
self.proteins.grid(row=1, column=2)
# glucose
Label(self, text='Glucose').grid(row=0, column=3)
self.glucose = Label(self)
self.glucose.grid(row=1, column=3)
# lipids
Label(self, text='Lipids').grid(row=0, column=4)
self.lipids = Label(self)
self.lipids.grid(row=1, column=4)
# list of all labels
self.data_labels = [self.calories, self.proteins, self.glucose, self.lipids]
# error label for if no value is entered or it cannot be converted
self.error_label = Label(self, text='Use integer or float value!')
# stuff for adding multiple foods
self.do_add = False
self.nutrient_list = []
self.plus_btn = Button(self, text='Add', command=self.add)
self.plus_btn.grid(row=3, column=0, columnspan=4, sticky='nwes')
def display_values(self, value_list):
self.nutrient_list.append(value_list)
if len(self.nutrient_list) == 2 and not self.do_add:
self.nutrient_list.pop(0)
elif len(self.nutrient_list) > 2 and not self.do_add:
self.nutrient_list = [self.nutrient_list[-1]]
print(self.nutrient_list)
if self.do_add:
value_list = []
for i in range(len(self.nutrient_list[0])):
total_value = 0
for item in self.nutrient_list:
total_value += item[i]
value_list.append(total_value)
for text, label in zip(value_list, self.data_labels):
label.config(text=f'{text:.2f}')
self.do_add = False
def handle_value_error(self):
self.error_label.grid(row=2, column=0, columnspan=4)
def add(self):
self.do_add = True
self.amount.delete(0, 'end')
class Values:
def __init__(self, name, row, column, calories, proteins, glucose, lipids):
self.parent = root
self.name = name
self.row = row
self.column = column
self.calories = calories
self.proteins = proteins
self.glucose = glucose
self.lipids = lipids
self.stuff_list = [self.calories, self.proteins, self.glucose, self.lipids]
self.button = Button(self.parent, text=self.name, command=self.set_values)
self.button.grid(row=self.row, column=self.column, sticky='nwse')
def set_values(self):
value_list = []
value_ = self.parent.amount.get()
try:
formula = float(value_) / 100
self.parent.error_label.grid_forget()
for item in self.stuff_list:
item *= formula
value_list.append(item)
self.parent.display_values(value_list)
except ValueError:
self.parent.handle_value_error()
root = MainWindow()
kiwi = Values('Kiwi', 2, 4, calories=53, proteins=1.6, glucose=11.1, lipids=0.3)
banana = Values('Banana', 3, 4, calories=90, proteins=1.5, glucose=20.1, lipids=0)
root.mainloop()
it is not necessarily better however a few benefits from using this code are:
better organization (IMO) since it uses classes which make it pretty neat (however You have to have an understanding over them)
takes less space - what I mean is that now, to define a new food, You just have to initiate the Values class and give it the necessary arguments. You can take an example from the given instances.
possibly easier to expand (both because better organization and takes less space)
I edited the code so now it should have that functionality, the way it works is You choose an amount , then fruit and if You want to add more, press add button, then choose amount, press food and it will display the total so far, then again, if You want to add more press add and then put in an amount and press the fruit, currently the way to 'clear' is to press any food button and it should then reset the whole list and keep only the one that was just pressed and then repeat.

Tkinter change number in multiple labels

I am making a program that lets a user input a flower type, and it will make a new row with row#, name, and days remaining before it dies. At the moment the UI is a bit messy and code could be improved a lot but that's not the point. I would like to know how I would go about making multiple new labels that I can change the days remaining with the click of a button.
Here is my code so far:
It runs ok but only the lastest made row can be changed, this is because every time one is made, the last one can't be edited anymore, and that's what I want to change.
from tkinter import *
#Flower Types
flowers_days = {
"peony": 1,
"rose": 2,
"daffodil": 3,
"dandelion": 4,
"lavender": 5
}
day_change = {}
#Variables
day = 1
days_left = 5
row_Num = 0
name = ""
#Commands
def new_flower():
#make a new line with the new flower
global row_Num
global days_left
global name
global new_row
row_Num += 1
name = str(clicked.get())
print("Test:" + name)
days_left = flowers_days[clicked.get()]
day_change[days_left] = int(row_Num)
new_row = Label(main_Frame, text=str(row_Num)+" "+name+" " + str(days_left))
new_row.pack()
return new_row
def next_day():
global days_left
global name
days_left -= 1
new_row.config(text=str(row_Num)+" "+name+" " + str(days_left))
root = Tk()
new_row = Label()
clicked = StringVar()
clicked.set("No option Selected")
#FLOWER TYPE
flower_Type_Frame = LabelFrame(root, text="New Flowers", padx=5, pady=5)
flower_Type_Frame.grid(row=0, rowspan=4, column=0, columnspan=2, padx=10, pady=10)
flower_Type_Label = Label(flower_Type_Frame, text="Flower Type:")
flower_Type_Label.grid(row=0, column=0, columnspan=2, padx=5, pady=5)
flower_Type_Drop = OptionMenu(flower_Type_Frame, clicked, """
No option Selected
""", "peony", "rose", "daffodil", "dandelion", "lavender")
flower_Type_Drop.grid(row=1, column=0, columnspan=2, pady=5, padx=5)
flower_Type_Submit = Button(flower_Type_Frame, text="Submit", padx=10, pady=10, command=new_flower)
flower_Type_Submit.grid(row=2, column=0, columnspan=2, rowspan=2)
#Empty slot
space_Frame = LabelFrame(root, text="Empty", padx=5, pady=5)
space_Frame.grid(row=0, rowspan=4, column=3, columnspan=2, padx=10, pady=10)
space_Fill = Label(space_Frame, text="Space ").grid(row=0, column=0)
#Day Pass
day_Pass_Frame = LabelFrame(root, text="Day Pass", padx=5, pady=5)
day_Pass_Frame.grid(row=0, rowspan=2, column=6, columnspan=4, padx=10, pady=10)
day_Pass = Button(day_Pass_Frame, text="Next Day", padx=10, pady=10, command=next_day)
day_Pass.grid(row=0, rowspan=2, column=3, columnspan=2)
#Row Delete
#Main stuff
main_Frame = LabelFrame(root, text="Flowers In Stock", padx=5, pady=5)
main_Frame.grid(row=5, column=0, columnspan=7, padx=10, pady=10)
header = Label(main_Frame, text=" Row # / Flower Type / Days Remaining ", padx=5, pady=5)
header.pack(padx=5, pady=5)
root.mainloop()
Once this is sorted I also plan on making it to have a remove row button, so the row numbers need to be able to be changed too if possible.
Thanks for any help.
You're keeping only one 'days_left' information (in a global variable), but you
need to keep one for each flower. So your main data structure needs to be a
list of flowers, and you should remove the 'global' statements for days_left,
name, new_row, as that information needs to be secific to each flower.
Add this to the global scope (just before the new_flower() definition):
# List of [name, days_left, label], one per flower
flowers = []
In the new_flower() function, add the newly-created flower to the list with 'append':
new_row = Label(main_Frame, text=str(row_Num)+" "+name+" " + str(days_left))
new_row.pack()
flowers.append([name, days_left, new_row])
The next_day function should look like this:
def next_day():
for i, f in enumerate(flowers):
# f is a 3-element list [name, days_left, label]
f[1] -= 1
name, days_left, label = f
label.config(text=str(i+1)+" "+name+" " + str(days_left))
The 'enumerate' call iterates over a list, returning both the current index in
the list (in 'i') and the current list item (in 'f'). The index gives you the
row number.

Get input values from a grid with several Entry widgets on Tkinter

I'm trying to create a sudoku solver. I have my grid with the entries at this point but I don't know how to get the values that the user has put in them.
I have no clue yet as to how I'm going to make the Sudoku solver but I think I'll first have to find a way to store the input in some variable(s) so I can work with them later on.
So my question is, how do I get the values that have been filled into the entries?
This is my code thus far:
from tkinter import *
root = Tk()
root.title('Sudoku Solver')
root.geometry('500x400')
mylabel = Label(root, text='Fill in the numbers and click solve').grid(row=0, column=0, columnspan=10)
# Create the grid
def beg():
global e
cells = {}
for row in range(1, 10):
for column in range(1, 10):
if ((row in (1,2,3,7,8,9) and column in (4,5,6)) or (row in (4,5,6) and column in (1,2,3,7,8,9))):
kleur='black'
else:
kleur='white'
cell = Frame(root, bg='white', highlightbackground=kleur,
highlightcolor=kleur, highlightthickness=2,
width=50, height=50, padx=3, pady=3, background='black')
cell.grid(row=row, column=column)
cells[(row, column)] = cell
e = Entry(cells[row, column], width=4, bg='white', highlightthickness=0, fg='black', relief=SUNKEN)
e.pack()
# Tell the button what to do
def solve():
global e
test = e.get()
print(test)
# Create the buttons and give them a command
clearer = Button(root, text='Clear', command=beg)
solver = Button(root, text='Solve', command=solve)
# Locate the buttons
clearer.grid(row=11, column=3, pady=30)
solver.grid(row=11, column=7, pady=30)
# Run it for the first time
beg()
root.mainloop()
I also tried changing e to e[row, column] but that gave me a syntax error.
Standard rule: if you have many elements then keep them on list or dictionary.
Do the same as with cells
Create dictionary
entries = {}
add to dictionary
entries[(row, column)] = e
and get from dictionary
def solve():
for row in range(1, 10):
for column in range(1, 10):
print(row, column, entries[(row, column)].get() )
# from tkinter import * # PEP8: `import *` is not preferred
import tkinter as tk
# --- functions ---
# Create the grid
def beg():
# remove old widgets before creating new ones
for key, val in cells.items():
print(key, val)
val.destroy()
for row in range(1, 10):
for column in range(1, 10):
if ((row in (1,2,3,7,8,9) and column in (4,5,6)) or (row in (4,5,6) and column in (1,2,3,7,8,9))):
kleur='black'
else:
kleur='white'
cell = tk.Frame(root, bg='white', highlightbackground=kleur,
highlightcolor=kleur, highlightthickness=2,
width=50, height=50, padx=3, pady=3, background='black')
cell.grid(row=row, column=column)
cells[(row, column)] = cell
e = tk.Entry(cell, width=4, bg='white', highlightthickness=0, fg='black', relief='sunken')
e.pack()
entries[(row, column)] = e
# Tell the button what to do
def solve():
for row in range(1, 10):
for column in range(1, 10):
print(row, column, entries[(row, column)].get() )
# --- main ---
entries = {}
cells = {}
root = tk.Tk()
root.title('Sudoku Solver')
root.geometry('500x400')
mylabel = tk.Label(root, text='Fill in the numbers and click solve')
mylabel.grid(row=0, column=0, columnspan=10)
# Create the buttons and give them a command
clearer = tk.Button(root, text='Clear', command=beg)
solver = tk.Button(root, text='Solve', command=solve)
# Locate the buttons
clearer.grid(row=11, column=2, pady=30, columnspan=3) # I added `columnspan=3`
solver.grid(row=11, column=6, pady=30, columnspan=3) # I added `columnspan=3`
# Run it for the first time
beg()
root.mainloop()

How to change colour for each sorting algorithms step in tkinter

This is my main code that creates a GUI. The user can enter the numbers or generate random numbers and it will sort it. However, i want it to change colour with each step taken in the sorting algorithm, and then change to a green colour once the array has been sorted.
import random
from tkinter import *
import Swap
numberArray = list()
def arrayList(number):
arrayListEntry.delete(0, END)
numberArray.append(number)
e.config(text=str(numberArray))
print numberArray
def randomiser():
for i in range(10):
numberArray.append(random.randint(1,100))
e.config(text=str(numberArray))
print numberArray
# Main selection sort function
def selectionSort(numberList):
for i in range(len(numberList)):
minimumIndex = i
print("\nMinimum index: " + str(minimumIndex))
for l in range(i + 1, len(numberList)):
if numberList[minimumIndex] > numberList[l]:
minimumIndex = l
print(numberArray)
Swap.startSwap(numberList, i, minimumIndex)
e.config(text=str(numberArray))
root.update()
root.after(1000)
e.config(text=str(numberArray))
width = 300
height = 250
root = Tk()
frame = Frame(root, width=width, height=height)
frame.grid(row=3, column=0, columnspan=3)
e = Label(root, text="Arraylist")
e.grid(row=1, column=1)
arrayListLabel = Label(root, text="Array List")
arrayListEntry = Entry(root)
arrayListButton = Button(root, text="Enter", command=lambda: arrayList(arrayListEntry.get()))
arrayListButton.grid(row=0, column=3)
sortButton = Button(root, text="Sort", command=lambda: selectionSort(numberArray))
sortButton.grid(row=2, column=1)
randomButton = Button(root, text="Randomise", command=lambda: randomiser())
randomButton.grid(row=3, column=1)
arrayListLabel.grid(row=0, column=0)
arrayListEntry.grid(row=0, column=1)
root.mainloop()
This is my swap method in another class, ignore the last line as that is only for the console for me to see what is happening
# Swap function for swaping the index in the array
def startSwap(array, firstIndex, secondIndex):
swapObject = array[firstIndex]
array[firstIndex] = array[secondIndex]
array[secondIndex] = swapObject
print("Number " + str(array[firstIndex]) + " swapped with number " + str(array[secondIndex]) + " in array\n")
I removed the frame widget as it was complicating the task of changing the color of the entire background.
I made the following changes inside the startSwap() function to change the colors at every step of the algorithm
First I created a list of color names:
colors = ['red', 'blue', 'orange', 'yellow']
Next, I randomly set the foreground color from the colors list. You can add other colors to increase randomness of color selection.
e.config(fg=random.choice(colors))
Finally when the the list of numbers are sorted, set the foreground color to green inside of the selectionSort() function
e.config(fg='green')
Here's the entire working code:
import random
from tkinter import *
#import Swap
root = Tk()
# define label before using it
e = Label(root, text="Arraylist")
e.grid(row=1, column=1, padx=10, pady=10)
numberArray = list()
def arrayList(number):
arrayListEntry.delete(0, END)
numberArray.append(number)
e.config(text=str(numberArray))
#print numberArray
def randomiser():
for i in range(10):
numberArray.append(random.randint(1,100))
e.config(text=str(numberArray))
#print numberArray
def startSwap(array, firstIndex, secondIndex):
# color list
colors = ['red', 'blue', 'orange']
# set a random color from the colors list
e.config(fg=random.choice(colors))
swapObject = array[firstIndex]
array[firstIndex] = array[secondIndex]
array[secondIndex] = swapObject
# Main selection sort function
def selectionSort(numberList):
for i in range(len(numberList)):
minimumIndex = i
for l in range(i + 1, len(numberList)):
if numberList[minimumIndex] > numberList[l]:
minimumIndex = l
#Swap.startSwap(numberList, i, minimumIndex)
startSwap(numberList, i, minimumIndex)
e.config(text=str(numberArray))
root.update()
root.after(1000)
e.config(text=str(numberArray))
# once numbers are sorted, set foreground as green
e.config(fg='green')
arrayListLabel = Label(root, text="Array List")
arrayListEntry = Entry(root)
arrayListButton = Button(root, text="Enter", command=lambda: arrayList(arrayListEntry.get()))
arrayListButton.grid(row=0, column=3, padx=10, pady=10)
sortButton = Button(root, text="Sort", command=lambda: selectionSort(numberArray))
sortButton.grid(row=2, column=1, pady=10)
randomButton = Button(root, text="Randomise", command=lambda: randomiser())
randomButton.grid(row=3, column=1, pady=10)
arrayListLabel.grid(row=0, column=0, padx=10, pady=10)
arrayListEntry.grid(row=0, column=1)
root.mainloop()
Lastly, I added some padding to the Label and Button widgets.

Categories

Resources