How to get each tkinter Entry's unique value from loop - python

I'm trying to create a hangman game which produces a random amount of entries. Problem is, I can't get the unique value of each entry.
This is what I have tried so far:
from tkinter import *
import random
root = Tk()
root.geometry('500x500')
color = 'blue'
a1 = Canvas(root, height = 500, width = 500, bg = color)
a1.pack()
#Variable Declaration
var = StringVar()
x = ''
words = ['penguin','wolves','skyscraper','dinosaur','time travel']
lists = {}
def Enter():
for i in lists:print(lists[i])
x = 'entry'
a=150
b=350
for i in range(len(random.choice(words))):
lists[f'entry{i}'] = Entry(textvariable = StringVar(),width =3, font = 'tahoma',justify=CENTER).place(x=a,y=b)
a+=30
b1 = Button(root, text = "Enter",command=Enter)
b1.place(x=225,y=400)
root.mainloop()

The first thing you must do is separate the creation of the widget from the layout of the widget. In python, x().y() returns the value of y(), so Entry(...).place(...) returns the value of place(...) and place(...) always returns None.
The second thing I would do is not use f'entry{i}' for the index into your lists. It will work, but it's more cumbersome than it needs to be.
for i in range(len(random.choice(words))):
lists[i] = Entry(textvariable = StringVar(),width =3, font = 'tahoma',justify=CENTER)
lists[i].place(x=a,y=b)
a+=30
The above lets you easily reference any entry widget with something like lists[0], lists[1], etc.
Finally, in your Enter method (which should be named enter according to PEP8 guidelines), you can directly loop over the entries without using the index at all:
def Enter():
for index, entry in lists.items():
value = entry.get()
print(value)

In your case I would advice the make 1 input field for text and split the letter later.
A string in python can be accessed like a list, you have len(string) and string[3] gives the 4 letter ect.

Related

Making multiple selections in tkinter

Is there any way to make multiple selections in tkinter?
Here's the code:
from tkinter import *
root = Tk()
text = Text(root , width = 65 , height = 20 , font = "consolas 14")
text.pack()
text.insert('1.0' , "This is the first line.\nThis is the second line.\nThis is the third line.")
mainloop()
Here, I want be able to select multiple text from where ever I want.
Here is an Image(GIF) that explains what I mean:
Is there any way to achieve this in tkinter?
It would be great if anyone could help me out.
I made a short demo, with Control key hold you could select multiple text. Check this:
import tkinter as tk
class SelectableText(tk.Text):
def __init__(self, master, **kwarg):
super().__init__(master, **kwarg)
self.down_ind = ''
self.up_ind = ''
self.bind("<Control-Button-1>", self.mouse_down)
self.bind("<B1-Motion>", self.mouse_drag)
self.bind("<ButtonRelease-1>", self.mouse_up)
self.bind("<BackSpace>", self.delete_)
def mouse_down(self, event):
self.down_ind = self.index(f"#{event.x},{event.y}")
def mouse_drag(self, event):
self.up_ind = self.index(f"#{event.x},{event.y}")
if self.down_ind and self.down_ind != self.up_ind:
self.tag_add(tk.SEL, self.down_ind, self.up_ind)
self.tag_add(tk.SEL, self.up_ind, self.down_ind)
def mouse_up(self, event):
self.down_ind = ''
self.up_ind = ''
def delete_(self, event):
selected = self.tag_ranges(tk.SEL)
if len(selected) > 2:
not_deleting = ''
for i in range(1, len(selected) - 1):
if i % 2 == 0:
not_deleting += self.get(selected[i-1].string, selected[i].string)
self.delete(selected[0].string, selected[-1].string)
self.insert(selected[0].string, not_deleting)
return "break"
root = tk.Tk()
text = SelectableText(root, width=50, height=10)
text.grid()
text.insert('end', "This is the first line.\nThis is the second line.\nThis is the third line.")
root.mainloop()
So I was trying to delete each selection with the Text.delete(index1, index2) but when the first selection in one line is deleted, the indices changes, making the subsequent delete deleting indices not selected (or out of range in the particular line.
I had to work around another way - first deleting from the first selected to the last selected, just like what BackSpace would do by default, then put back every unselected part in the middle. The Text.tag_ranges gives you a list of ranges selected in this way:
[start1, end1, start2, end2, ...]
where each entry is a <textindex object> with a string property (the index). So you can extract the text between end1 and start2, between end2 and start3, etc. to the end, and store these into a variable (not_deleting) so you can insert them back into the text.
There should be better and neater solutions but for now this is what it is... Hope it helps.
For the delete_ method proposed by Qiaoxuan Zhang, I advise to use the Text.tag_nextrange method in a loop like this :
def delete_sel(self):
while 1:
result = self.tag_nextrange(tk.SEL, 1.0)
if result :
self.delete(result[0] , result[1])
else :
break
For those who would like to add the missing features:
correction of selection when changing direction of sliding
take into account the double-click
Take a look here : French site
Short answer: set the exportselection attribute of each Text widget to False
Tkinter gives you control over this behaviour with the exportselection configuration option for the Text widget as well as the Entry and Listbox widgets. Setting it to False prevents the export of the selection to the X selection, allowing the widget to retain its selection when a different widget gets focus.
For example:
import tkinter as tk
...
text1 = tk.Text(..., exportselection=False)
text2 = tk.Text(..., exportselection=False)
you can find more info here: http://tcl.tk/man/tcl8.5/TkCmd/options.htm#M-exportselection

How to identify and delete a tkinter canvas drawing through tags kept in a tags list?

I'm trying to delete a rectangle from the canvas through a second button click through making use of a list of tags. See the function 'insert_or_delete_cells' below. Although the code keeps the tags list up-to-date, it does not delete the rectangle itself from the canvas. It appears from the print statements that the code does not retrieve the id of the rectangle, so nothing is deleted. How to improve this code?
import tkinter as tk
def insert_or_delete_cells():
def xyposition(event):
x = canvas.canvasx(event.x)
y = canvas.canvasy(event.y)
x_matrix=int(x//cell)
y_matrix=int(y//cell)
x0 = x_matrix*cell+1
y0 = y_matrix*cell+1
mark = str(x_matrix)+str(y_matrix)
c = cell-1
if mark not in tags_list:
rect = canvas.create_rectangle(x0,y0,x0+c,y0+c, width=0, fill='green', tags = mark)
tags_list.append(mark)
print('new cell id: ', rect, 'new cell tag: ', mark)
else:
r=canvas.find_withtag(mark)
print('removed cell id: ', r, 'removed cell tag: ', mark)
canvas.delete(mark) # canvas.delete(r) doesn't work neither
tags_list.remove(mark)
canvas.bind("<Button-1>", xyposition)
def stop_insert_cells():
canvas.unbind('<Button-1>')
# MAIN
root = tk.Tk()
root.geometry("400x400")
n=30
m=30
cell=10
w = n*cell
h = m*cell
# canvas
canvas = tk.Canvas(root,width = w, height = h, borderwidth = 0, highlightthickness=0)
canvas.place(x=20, y=20)
# border canvas
canvas.create_rectangle(0,0,w-1,h-1, outline = 'black')
# cell filling
tags_list = []
start_insert_button = tk.Button(root, text='start', command = insert_or_delete_cells)
stop_insert_button = tk.Button(root, text = 'stop', command = stop_insert_cells)
stop_insert_button.pack(side='bottom')
start_insert_button.pack(side='bottom')
root.mainloop()
When you create a tag, you are using strings that look like integers. Tags can be anything at all except strings that look like integers, since tkinter won't know whether you're referring to a tag or an item id.
Your code will work if you make sure that mark is not composed only of digits. For example, changing the definition of mark to something like the following will allow your code to work:
mark=f"mark-{x_matrix},{y_matrix}"
From the canonical tcl/tk documentation:
Each item may also have any number of tags associated with it. A tag is just a string of characters, and it may take any form except that of an integer. For example, “x123” is OK but “123” is not. The same tag may be associated with many different items. This is commonly done to group items in various interesting ways; for example, all selected items might be given the tag “selected”.

Tkinter - Creating multiple check boxes using a loop

I am trying to create a program that allows the user to select any number of check boxes and hit a button to return a random result from those check boxes. Since I am basing my list off the roster of Smash bros ultimate, I am trying to avoid creating 70+ variables just to place check boxes. However, I am unable to figure out how to iterate this. The various values set for rows are just placeholders until I can figure this out. I would also like to have a reset button at the top that allows the user to automatically uncheck every box. This code is what I have so far. Any help would be greatly appreciated.
#!/usr/bin/python3
from tkinter import *
window = Tk()
#window name and header
window.title("Custom Random SSBU")
lbl = Label(window, text="Select the fighters you would like to include:")
lbl.grid(column=1, row=0)
f = [] #check boxes
ft = open("Fighters.txt").readlines() #list of all the character names
fv=[0]*78 #list for tracking what boxes are checked
ff=[] #list to place final character strings
def reset():
for i in fv:
fv[i]=0
rst = Button(window, text="Reset", command=reset)
rst.grid(column=0, row=3)
for y in range (0,77):
f[y] = Checkbutton(window, text = ft[y], variable = fv[y])
f[y].grid(column=0, row=4+y)
def done():
for j in fv:
if fv[j] == 1:
ff.append(fv[j])
result = random.choice(ff)
r=Label(window, text=result)
d = Button(window, text="Done", command=done)
d.grid(column=0, row = 80)
window.mainloop()
Unfortunately I'm afraid you are going to have to create variables for each checkbox.
tkinter has special purpose Variable Classes for holding different types of values, and if you specify an instance of one as the variable= option when you create widgets like Checkbutton, it will automatically set or reset its value whenever the user changes it, so all your program has to do is check its current value by calling its get() method.
Here's an example of the modifications to your code needed to create them in a loop (and use them in the done() callback function):
import random
from tkinter import *
window = Tk()
#window name and header
window.title("Custom Random SSBU")
lbl = Label(window, text="Select the fighters you would like to include:")
lbl.grid(column=1, row=0)
with open("Fighters.txt") as fighters:
ft = fighters.read().splitlines() # List of all the character names.
fv = [BooleanVar(value=False) for _ in ft] # List to track which boxes are checked.
ff = [] # List to place final character strings.
def reset():
for var in fv:
var.set(False)
rst = Button(window, text="Reset", command=reset)
rst.grid(column=0, row=3)
for i, (name, var) in enumerate(zip(ft, fv)):
chk_btn = Checkbutton(window, text=name, variable=var)
chk_btn.grid(column=0, row=i+4, sticky=W)
def done():
global ff
ff = [name for name, var in zip(ft, fv) if var.get()] # List of checked names.
# Randomly select one of them.
choice.configure(text=random.choice(ff) if ff else "None")
d = Button(window, text="Done", command=done)
d.grid(column=0, row=len(ft)+4)
choice = Label(window, text="None")
choice.grid(column=1, row=3)
window.mainloop()
I wasn't sure where you wanted the Label containing the result to go, so I just put it to the right of the Reset button.
variable = fv[y]
This looks up the value of fv[y] - i.e, the integer 0 - at the time the Checkbutton is created, and uses that for the variable argument.
You need to use an instance of one of the value-tracking classes provided by TKinter, instead. In this case we want BooleanVar since we are tracking a boolean state. We can still create these in a list ahead of time:
text = open("Fighters.txt").readlines()
# Let's not hard-code the number of lines - we'll find it out automatically,
# and just make one for each line.
trackers = [BooleanVar() for line in text]
# And we'll iterate over those pair-wise to make the buttons:
buttons = [
Checkbutton(window, text = line, variable = tracker)
for line, tracker in zip(text, trackers)
]
(but we can not do, for example trackers = [BooleanVar()] * len(text), because that gives us the same tracker 78 times, and thus every checkbox will share that tracker; we need to track each separately.)
When you click the checkbox, TKinter will automatically update the internal state of the corresponding BooleanVar(), which we can check using its .get() method. Also, when we set up our options for random.choice, we want to choose the corresponding text for the button, not the tracker. We can do this with the zip trick again.
So we want something more like:
result_label = Label(window) # create it ahead of time
def done():
result_label.text = random.choice(
label
for label, tracker in zip(text, trackers)
if tracker.get()
)

Python passing default value to combobox class : all instances show same value

I am writing a program which analyses a CSV file containing my credit card monthly statement. I am trying to create a gui with tkinter in which I have a row of label headings followed by rows which consist of 3 more labels and then a combobox in the 4th column which has a number of categories. I have written a class which defines each row of the table. I want to pass a value to the instance of the class which specifies the initial value for the combobox. This is different for each instance or row. Or at least should be....
My problem is that in passing in different numbers for each row/instance I always get all rows having the value specified for the first created instance.
My classes are as follows:
class CatFormApp:
def __init__(self,parent):
self.parent = parent
self.datelabel = Tkinter.Label(root, text='Date',font ='Verdana 12 bold',borderwidth=5).grid(row=0,column=0)
self.transactionlabel = Tkinter.Label(root, text='Transactions',font ='Verdana 12 bold',borderwidth=5).grid(row=0,column=1)
self.paidoutlabel = Tkinter.Label(root, text='Paid Out',font ='Verdana 12 bold',borderwidth=5).grid(row=0,column=2)
self.catlabel = Tkinter.Label(root, text='Category',font ='Verdana 12 bold',borderwidth=5).grid(row=0,column=3)
class RowFormat:
def __init__(self,parent,rownumbval,defaultval,dateval,transactionval,paidoutval,CatTupleVals,bkg):
self.parent = parent
self.datevallabel = Tkinter.Label(root, text=dateval, borderwidth=5).grid(row=rownumbval,column=0)
self.transactionvallabel = Tkinter.Label(root, text=transactionval, borderwidth=5, background = bkg).grid(row=rownumbval,column=1)
self.paidoutvallabel = Tkinter.Label(root, text=paidoutval, borderwidth=5).grid(row=rownumbval,column=2)
self.combo(rownumbval,defaultval,CatTupleVals)
def combo(self,rownumb,default,CatTuple):
self.box_value = '1'
self.box = ttk.Combobox(self.parent,textvariable = self.box_value)
self.box['values']=CatTuple
self.box.current(default)
self.box.grid(row=rownumb,column=3)
The relevant bit of my main code is:
root = Tkinter.Tk()
app = CatFormApp(root)
row = ['']*(len(CreditData['Transactions'])+1)
for r in range(1,len(CreditData['Transactions'])):
if CreditData['Paid out'][r-1] != '':
if noMatch[r-1] == True:
print 1
row[r] = RowFormat(root,r,1,CreditData['Date'][r-1],CreditData['Transactions'][r-1],CreditData['Paid out'][r-1][1:],tuple(CatHeadings[:-1]),bkg = 'red')
else:
print 2
row[r] = RowFormat(root,r,2,CreditData['Date'][r-1],CreditData['Transactions'][r-1],CreditData['Paid out'][r-1][1:],tuple(CatHeadings[:-1]),bkg = 'white smoke')
root.mainloop()
In the above example all comboboxes have the value associated with 1. Whereas I should get some with a 1 and some with a 2 depending on the if statement. I'm puzzled because I do something very similar for the other label values passed and these all come out differently as expected.
If someone could explain where I'm going wrong I'd be really grateful
This code is wrong:
self.box_value = '1'
self.box = ttk.Combobox(...,textvariable = self.box_value)
The value for the textvariable attribute must be an instance of the tkinter class StringVar (or one of the other special tkinter variables).
Use it like this:
self.box_value = Tkinter.StringVar()
self.box = ttk.Combobox(self.parent,textvariable = self.box_value)
To set values, use:
self.box['values'] = ('A', 'B', 'C')
To select the first item, use:
self.box.current(0)

Trying to put drop down list on a code using Tkinter

I'm tring to put some drop down list on a graphic interface I'm building.
I've found the following code for a drop down list, but I'm not able to adapt it to my code.
from Tkinter import *
def print_it(event):
print var.get()
root = Tk()
var = StringVar()
var.set("a")
OptionMenu(root, var, "a","b","c", command=print_it).pack()
root.mainloop()
This is my code, it's quite simple what I've done so far. A menu shows up, it asks for how many (n) components does the users want to enter, and it shows n options to entry. The code above shows 'blank' entrys after you put the desired number of components. I want to replace those three blank entrys with three drop down list.
It's marked when I want to put those dropdown lists.
from Tkinter import *
import Image
import ImageTk
import tkFileDialog
class Planificador:
def __init__(self,master):
master.title("Planificador")
self.frameOne = Frame(master)
self.frameOne.grid(row=0,column=0)
# Logo
self.imgLabel = Label(self.frameOne, image = None)
self.imgLabel.grid(row=0,column=0)
self.img = ImageTk.PhotoImage(file = "logo.png")
self.imgLabel["image"] = self.img
self.botones()
def botones(self):
self.piezastext = Label(self.frameOne, text = " number of components ", justify="center")
self.piezastext.grid(row=1, column=0)
self.entrypiezas = Entry(self.frameOne,width=5)
self.entrypiezas.grid(row=2, column=0)
self.aceptarnumpiezas = Button(self.frameOne,text="Aceptar", command=self.aceptar_piezas,width=8)
self.aceptarnumpiezas.grid(row=6, column=0)
def aceptar_piezas(self):
num_piezas = self.entrypiezas.get()
print num_piezas
self.piezastext.grid_remove()
self.entrypiezas.grid_remove()
self.aceptarnumpiezas.grid_remove()
n = 1;
while n <= int(num_piezas):
self.textopieza = Label(self.frameOne, text = "Pieza", justify="left")
self.textopieza.grid(row=n, column=0)
// INSTEAD THESE 'n' BLANK ENTRYS, I WANT TO PUT 'n' DROP DOWN LISTS
self.entrypiezas = Entry(self.frameOne,width=5)
self.entrypiezas.grid(row=n, column=1)
self.aceptarpiezas = Button(self.frameOne,text="Aceptar",width=8)
self.aceptarpiezas.grid(row=int(num_piezas)+1, column=0)
n += 1
# Main
if __name__ == "__main__":
# create interfacE
root = Tk()
movieApp = Planificador(root)
root.mainloop()
So I want to know how can I put that drop down list on a given frame, frameOnein my case, instead of a full window. Thanks in advance.
I modified your aceptar_piezas function to do what I think you want:
def aceptar_piezas(self):
num_piezas = self.entrypiezas.get()
print num_piezas
self.piezastext.grid_remove()
self.entrypiezas.grid_remove()
self.aceptarnumpiezas.grid_remove()
# Create a list of tuples to hold the dynamically created Optionmenus
# The first item in the tuple is the menu, the second is its variable
self.optionmenus = list()
n = 1
while n <= int(num_piezas):
self.textopieza = Label(self.frameOne, text = "Pieza", justify="left")
self.textopieza.grid(row=n, column=0)
# Variable for the Optionmenu
var = StringVar()
# The menu
menu = OptionMenu(self.frameOne, var, "a","b","c")
menu.grid(row=n, column=1)
# Set the variable to "a" as default
var.set("a")
# Add the menu to the list of Optionmenus
self.optionmenus.append((menu, var))
n += 1
def clicked():
"""This function was made just to demonstrate. It is hooked up to the button"""
for optionmenu in self.optionmenus:
print optionmenu[1].get()
print self.optionmenus
# This button doesn't need to be in the while loop
self.aceptarpiezas = Button(self.frameOne, text="Aceptar", command=clicked, width=8)
self.aceptarpiezas.grid(row=int(num_piezas)+1, column=0)
The tuples in the list are in the order that the Optionmenus were created. So, the first tuple contains the data for the first Optionmenu, the second for the second, and so forth.

Categories

Resources