How to ensure that the "bind" order is not skipped in tkinter? - python-3.x

I am trying to create a game using tkinter in which the players enter their names into an entry widget.
After entering their name, the user should press enter to call the function "player_names", which would ideally save the players name in a list, delete the text in the entry widget and then continue to the next loop (i.e player).
The script seems to be ignoring the bind and and moving straight to the line "self.name_entry.destroy()". How do I ensure that the script waits for the command before continuing?
def initialise_game(self, num_of_players):
self.players_list = []
for i in range(num_of_players):
player_num = i+1
self.name_label = tk.Label(self.bg_label, text='What is the name'
' of Player ' + str(player_num) + '?')
self.name_label.grid(row=0, padx=200, pady=120)
self.name_entry = tk.Entry(self.bg_label)
self.name_entry.grid(row=1, padx=200, pady=0)
self.name_entry.bind('<Return>', self.player_names)
self.name_entry.destroy()
def player_names(self, event):
self.players_list.append(self.name_entry.get())
self.name_entry.delete(0, 'end')

Entry doesn't work like input() it will not stop code and wait till you put text and press enter. GUI creates Entry and it executes code after Entry at once. You have bind to assign function which will be executed whey you press Enter and this function should get value from Entry, and replace widgets (or only text in Label). It also should remove Entry after last player so you have to count how many times function was executed (or how many players you already have - self.player_num)
I didn't test this code but it should works.
def initialise_game(self, num_of_players):
self.players_list = []
# remeber values in class variables, not local one
self.num_of_players = num_of_players
self.player_num = 0
# create only one Label - and change text in it
self.name_label = tk.Label(self.bg_label,
self.name_label.grid(row=0, padx=200, pady=120)
# create only one Entry and assign function `self.player_names`
self.name_entry = tk.Entry(self.bg_label)
self.name_entry.grid(row=1, padx=200, pady=0)
self.name_entry.bind('<Return>', self.player_names)
# set text for first player
self.player_num += 1
self.name_label["text"] = 'What is the name of Player {} ?'.format(player_num)
def player_names(self, event):
# get player's name from Entry
self.players_list.append(self.name_entry.get())
self.name_entry.delete(0, 'end')
# set text for next player or destroy Entry after last player
self.player_num += 1
if self.player_num <= self.num_of_players:
self.name_label["text"] = 'What is the name of Player {} ?'.format(player_num)
else:
self.name_label.destroy()
self.name_entry.destroy()
The same way it works in other GUIs (not only in tkinter) and other languages (not only in Python)

My idea of what are you trying to acomplish shuld be the same, to take an input from the player one by one until all player have added a name.
The ploblem with your code is the for, it needs to complite without an input from the player (tecnicly), insted i opted for the update mecanics that tkinter have, wen a value changes in a winget the winget will update itself and i bind to the button a change value from a increment.
If you want to reuse i , reference it a the start of the def the same wey
This example works, the input of initialise_game is the same as before, replacing the conde in the middle will work just fine, except if your code uses .grid() then replace ask_name.pack()
import tkinter as tk
root = tk.Tk()
root.bg_label = tk.Frame(root)
# Added code, top and bottom are for testing
#--------------------------------------------------------------------------------
root.players_list = [] # Global variabe witch can be accesd by all funcions
i = 1 # This global value is to keep a clear reference on the count of windows
def initialise_game(self, num_of_players):
ask_name = tk.Frame(self)
L = tk.Label(ask_name, text=('What is the name of Player ' + str(i) + '?'))
L.grid(row=0, padx=200, pady=120)
E = tk.Entry(ask_name)
def modify_entry(NULL):
root.players_list.append(E.get())
E.delete(0, 'end')
global i # Change the "search" of values to outside the funtion
i += 1
L.config(text=('What is the name of Player ' + str(i) + '?'))
if (i > num_of_players): # Close the Frame wen all were answered
root.players_list.append(E.get())
ask_name.destroy()
E.bind('<Return>', modify_entry)
E.grid(row=1, padx=200, pady=0)
ask_name.pack() # You can replace it with grin , this moves the Label + Entry were u want
#--------------------------------------------------------------------------------
initialise_game(root.bg_label, 3)
root.bg_label.pack()
root.mainloop()

Related

Mapping Tkinter entry box to variable in python 3.8

I am a complete beginner in python,I was hoping someone could help me figure out what I am trying to accomplish.
I built a small tkinter front end that will generate a string multiple times. I want the amount of times that the string is generated to be based off of an entry box. I have hit a wall.
Here is my Front End (help_FE.py)
from tkinter import *
import help_BE
def view_formula():
text1.delete('1.0',END)
text1.insert(END,help_BE.End_result)
pass
window = Tk()
window.wm_title=("Print:")
l1=Label(window,text = "Page to print:")
l1.grid(row=3, column=2)
e1 = Entry(window)
e1.grid(row=3, column=3)
text1=Text(window, height=20,width=35)
text1.grid(row=4,column=3, rowspan=10, columnspan=7, padx=5, pady=10)
sb1=Scrollbar(window)
sb1.grid(row=4,column=11,rowspan=10)
text1.configure(yscrollcommand=sb1.set)
sb1.configure(command=text1.yview)
text1.bind('<<TextboxSelect>>')
b1=Button(window, text = "Generate", width =10, command=view_formula)
b1.grid(row=3,column=6)
window.mainloop()
and my backend (help_BE.py)
currently, the generate button will print "Testing" 3 times, because i have set pages = 3 in the backend, but I want to be able to set pages to whatever is entered into the frontend entry box.
pages = 3
result=[]
def foo():
skip_zero = pages + 1
for x in range (skip_zero):
if x==0:
continue
result.append("Testing"+str(x))
listToStr = ''.join([str(element) for element in result])
full_formula = (listToStr)
return full_formula
End_result = foo()
The data inputted in the field can be accessed using the function Entry.get(), and you can convert the string to a number with the int function.
In order to get it to the backend, and in order to keep your value up to date, I would make sure that, as jasonharper mentioned, you call foo each time the button is pressed, passing the entry's value in as the argument pages. This means tweaking your code as such:
help_BE.py
def foo(pages):
skip_zero = pages + 1
for x in range (skip_zero):
if x==0:
continue
result.append("Testing"+str(x))
listToStr = ''.join([str(element) for element in result])
full_formula = (listToStr)
return full_formula
help_FE.py
def view_formula():
text1.delete('1.0',END)
text1.insert(END,help_BE.foo(int(e1.get())))

Change the label's text everytime a button is pressed

I'm doing a PYTHON table using FOR, in TKINTER, and I would like every time a new number is placed in ENTRY, the label changes to the new table. For example, a number will be placed in the ENTRY and the person will click the TAB button, when it is clicked, the table will appear, but if the person wants another number, and click again, the new table will go down from the previous one. My solution was to create a button that erases the previous table, but when the button is pressed, only the last multiplication is deleted. I would like to know how I click the tabuada button, and the previous one erases the new one without using another button .Get the code and a photo below, Thanks. Obs.: First photo shows reset button working, but it just erase the last multiplication,second photo shows the whole multiplication.
from tkinter import *
import tkinter as tk
win=tk.Tk()
win.title('Table')
lb=Label(win,text='Type a number:',font='Helvetica 12 bold')
lb.pack()
e=Entry(win)
e.pack()
def click():
global c
c=e.get()
print('requested number ',c)
for b in (range(0, 11)):
global lb2
lb2=Label(text='{} x {} = {} '.format(c, b, int(b)*int(c)))
lb2.pack()
def reset():
lb2['text'] = ' '
bt1=Button(win,text='GO',bg='lightblue',command=click)
bt1.pack()
bt2=Button(win,text='RESET',bg='lightblue',command=reset)
bt2.pack()
win.mainloop()
erasing:
whole multiplication:
Here are some fixes for your code; it was not entirely clear what you exactly meant by: "the new table will go down from the previous one.", so I went with having the new table replacing the previous one.
c was not defined in your code and that threw an exception.
I placed the construction of the label inside a function make_label that is called from the main block, and from click(), to rebuild it when a new number is requested.
reset was missing a call to pack on the label, to update the text displayed.
I think that should help you get started in the right direction; let me know if something is unclear.
edit:
I modified reset so the label is destroyed and re-created from view, thus removing the growth in size of the window.
from tkinter import *
import tkinter as tk
win=tk.Tk()
win.title('Table')
lb=Label(win,text='Type a number:',font='Helvetica 12 bold')
lb.grid(row=0, column=0)
lb2 = Label(text='')
e=Entry(win)
e.grid(row=1, column=0)
c = 2
def click():
global c
c = e.get()
print('requested number ', c)
reset()
make_label(c)
def make_label(c):
global lb2
txt = []
for b in (range(0, 11)):
txt.append('{} x {} = {} '.format(c, b, int(b)*int(c)))
text = '\n'.join(txt)
lb2 = Label(text=text)
lb2.grid(row=4, column=0)
def reset():
global lb2
lb2.destroy()
lb2 = Label()
lb2.grid(row=4, column=0)
make_label(c)
bt1=Button(win,text='GO',bg='lightblue',command=click)
bt1.grid(row=2, column=0)
bt2=Button(win,text='RESET',bg='lightblue',command=reset)
bt2.grid(row=3, column=0)
win.mainloop()

tkinker optionmenu not showing chosen result

import tkinter
window = tkinter.Tk()
def abc(event):
ans=0
numberss=['7','8','9']
omenu2['menu'].delete(0, 'end')
for number in numberss:
omenu2['menu'].add_command(label=numberss[ans], command=efg)
ans=ans+1
def efg(event=None):
print('yee')
numbers = ['1','2', '3']
number=['4','5','6']
var = tkinter.StringVar(window)
var1 = tkinter.StringVar(window)
omenu = tkinter.OptionMenu(window, var, *numbers, command = abc)
omenu.grid(row=1)
omenu2 = tkinter.OptionMenu(window, var1, *number, command = efg)
omenu2.grid(row=2)
after you have entered the first option menu, it will update the second one. when you enter data into the second one, it runs the command, but doesn't show you what you entered. i do not want to include a button, and i know that the command works and not on the second
i found some code that changed the options of the second menu, however when i ran this, the command wouldn't work as it was changed to tkinter.setit (i would also like to know what is does. i do not currently understand it)
omenu2['menu'].add_command(label=numberss[ans], command=tkinter._setit(var1, number))
this has been taken from a larger piece of code, and has thrown the same error
You should set your StringVar(var1) new value.
def abc(event):
numberss=['7','8','9']
omenu2['menu'].delete(0, 'end')
for number in numberss:
omenu2['menu'].add_command(label=number, command=lambda val=number: efg(val))
def efg(val, event=None):
print('yee')
var1.set(val)
You are using for loop so you don't need ans(at least not in this code) since it iterates over items themselves.

Label keeps on appearing

SO I am using Python 3.4 and tkinter.
And when I call a function again n again which contains a label, the label keeps on appearing in window but previous label doesn't go away?
How can I remove any printed label from GUI window as soon as function is called and then display new one?
Here is the code:-
#def prestart():
#here I check if number of match is okay, if not, user is redirected to setting else, I call start()
def start():
#CPU Choice
cpu_choice = Label(historyframe, text = "CPU Choosed: {}".format(dict['cpu_choice']))
#Played Match
#played_num_of_match = Label(scoreframe, text = "Number of Matches Played: {}".format(int(dict['match_played'])))
#Display Status
status_disp = Label(scoreframe, text = "Current Status: {}".format(dict['status']))
if(int(dict['match_played']) < int(dict['num_of_match'])):
playframe.grid(row = 1, column = 0)
historyframe.grid(row = 2, column = 1)
status_disp.pack(fill=X)
elif(int(dict['match_played']) == int(dict['num_of_match'])):
playframe.grid(row = 1, column = 0)
historyframe.grid(row = 2, column = 1)
status_disp.pack(fill=X)
cp = dict['cpu_point']
up = dict['user_point']
result(cp, up)
cpu_choice.pack(fill = X)
scoreframe.grid(row = 2, column = 0)
This function just updates the display!
def send_value(x):
#Here I run logic of game and change value of key in dictionary and call start() at end of change.
Now, the choice buttons are not in any definition as they don't need to be called again n again. I just make playframe disappear n appear!
Here is the code for them:-
#Display Question
question = Label(playframe, text = "Rock? Paper? Scissor?")
#Rock
rock = Button(playframe, text = "Rock!", command = lambda: send_value("ROCK"))
#Paper
paper = Button(playframe, text = "Paper!", command = lambda: send_value("PAPER"))
#Scissor
scissor = Button(playframe, text = "Scissor!", command = lambda: send_value("SCISSOR"))
So when user clicks Rock/Paper/Scissor, I just change key value in dictionary! But if I keep the label outside function, it doesn't get auto updated!
Everything else is working perfectly. I'll kind of now start to make code cleaner.
Try something like this instead of creating a new label every time:
import Tkinter as tk
class Window():
def __init__(self, root):
self.frame = tk.Frame(root)
self.frame.pack()
self.i = 0
self.labelVar = tk.StringVar()
self.labelVar.set("This is the first text: %d" %self.i)
self.label = tk.Label(self.frame, text = self.labelVar.get(), textvariable = self.labelVar)
self.label.pack(side = tk.LEFT)
self.button = tk.Button(self.frame, text = "Update", command = self.updateLabel)
self.button.pack(side = tk.RIGHT)
def updateLabel(self):
self.i += 1
self.labelVar.set("This is new text: %d" %self.i)
root = tk.Tk()
window = Window(root)
root.mainloop()
Important points:
1) A class is used, as it is much easier to pass values around when all Tkinter objects and variables are member variables, accessible from all of your GUI functions.
2) updateLabel does not create a new Label. It simply updates the StringVar() object to hold new text every time you call the function. This is accomplished with the textvariable = self.labelVar keyword when creating my Label widget.
PS: This is done in Python 2.5 so for this code to work for you, change Tkinter to tkinter
EDIT 06/19/2015:
If you want to implement something similar to what I have with your code, without using a class, you'll need to pass around references to your variables.
1) Change start:
Your Labels cpu_choice, status_disp, etc. should be created outside of the function; likely in the same location as question, rock, paper, scissors, etc. You will also pack them outside of the function as well. Same with all the calls to .grid inside of start; you shouldn't need to call pack or grid more than once: right when you create the widget.
The following lines:
playframe.grid(row = 1, column = 0)
historyframe.grid(row = 2, column = 1)
status_disp.pack(fill=X)
Can be done outside of the function as well; you execute these 3 statements under both the if and the elif conditions. This means they aren't really conditional statements; they are done regardless of the validity of the condition.
2) Create a StringVar for both cpu_choice & status_disp & edit the Labels as follows (remember, outside of the function):
cpu_choice_text = StringVar()
cpu_choice_text.set("Set this to whatever is shown at the start of the game")
cpu_choice = Label(historyframe, text = cpu_choice_text.get(), textvariable = cpu_choice_text)
cpu_choice.pack(fill = X)
# And do this same thing for status_disp
3) When you call start, you will now pass it cpu_choice_text & status_disp_text (or whatever they are called). Instead of trying to change the text field of the Label frame, you may now use a set call on the StringVar which is connected to the Label & the Label will automatically update. Example:
def start(cpu_choice_text, status_disp_text):
cpu_choice.set(text = "CPU Choice: {}".format(dict['cpu_choice']))
...
Alternatively, wrap it all in a class and make it much easier for yourself by using self on every Tkinter variable & widget. In this way you won't need to pass variables to your functions, just access member variables directly as I have with self.i, self.labelVar in my example.
Each time you call start you create new labels and use grid to place them in the same spot as the old labels. The best solution is to only create the labels once, but if you insist on creating new labels each time start is called, you need to delete the old labels first.
You can use the destroy() method of a label to destroy it, though for that to work you must keep a global reference of the label.

Tkinter: creating an arbitrary number of buttons/widgets

So, I've got a list with entries that look like this:
Option1 Placeholder1 2 Placeholder2 0
Option2 Placeholder1 4
Option3 Placeholder1 2 Placeholder2 -2 Placeholder3 6
I have a listbox of the Options and a button that creates a new window with the values for the selected Option. What I want to do is to create n number of buttons when this new window is created, where n is the number of values of the selected Options (i.e. 2, 1 and 3 for Options 1 through 3, respectively). I want it to look something like this:
Option1
Placeholder1 [button1 containing value=2]
Placeholder2 [button2 containing value=0]
... which is of course quite simple if I just assign a button for the maximum number of n that I know will be present, but I'm wondering if there's a way to do it more arbitrarily. Obviously the same problem applies to the arbitrary number of Labels I would need to use for the value names (the 'PlaceholderX's) as well.
I've been trying to do some reading on this type of thing, variable variables, etc., and it seems it's a very big NO-NO most (if not all) of the time. Some advocate the use of dictionaries, but I don't really get how that's supposed to work (i.e. naming variables from entries/values in a dict).
Is this something that can (and should) be done, or am I better off just creating all the buttons manually?
[EDIT: added code]
from tkinter import *
import csv
root = Tk()
root.wm_title("RP")
listFrame = Frame(root, bd=5)
listFrame.grid(row=1, column=2)
listbox1 = Listbox(listFrame)
listbox1.insert(1, "Option1")
listbox1.insert(2, "Option2")
listbox1.insert(3, "Option3")
listbox1.pack()
infoFrame = Frame(root, bd=5)
infoFrame.grid(row=1, column=3)
info_message = Message(infoFrame, width=300)
info_message.pack()
# Read stats from file
stat_file = open('DiceTest.csv', 'rU')
all_stats = list(csv.reader(stat_file, delimiter=';'))
def list_selection(event):
# gets selection and info/stats for info_message
index = int(listbox1.curselection()[0])
stats = all_stats[index]
infotext = str(stats[0]) # just the name
for n in range(int((len(stats)-2)/2)): # rest of the stats
infotext += ('\n' + str(stats[n*2 + 2]) + '\t' + str(stats[n*2 + 3]))
info_message.config(text=infotext)
listbox1.bind('<ButtonRelease-1>', list_selection)
def load():
top = Toplevel()
top.geometry('300x100')
index = int(listbox1.curselection()[0])
stats = all_stats[index]
# some way to create arbitrary buttons/labels here (?)
load_button = Button(root, text='Load', command=load)
load_button.grid(row=2, column=2)
root.mainloop()
Oh, and every button should have the same command/function, which reduces whatever value currently is in the button by 2.
Figured it out! Creating the widgets dynamically with a dictionary worked just fine, but calling the correct widget on the various button presses was more difficult. This is what I had:
buttons = dict()
for k in range(len(info)):
buttons[k] = Button(top, text=info[k], command=lambda: my_function(buttons[k]))
... which would work, but all button presses would call the function with the last created button as the target. All that was needed was a few extra characters in the command part of the buttons:
buttons = dict()
for k in range(len(info)):
buttons[k] = Button(top, text=info[k], command=lambda a=k: my_function(buttons[a]))
... which I assume works because it somehow stores the value of k inside a rather than taking the last known value of k, i.e. equivalent to the last created button. Is this correct?
You can store Buttons in a list:
from tkinter import *
master = Tk()
buttons = []
n = 10
for i in range(n):
button = Button(master, text = str(i))
button.pack()
buttons.append(button)
master.mainloop()

Resources