Python Tkinter, can't get value of checkbutton as child - python

I created some widgets in loop. And i need to get values of em all. I coded:
from tkinter import *
class App():
def __init__(self):
self.ws = Tk()
self.frame = LabelFrame(self.ws)
self.frame.grid(row=1,column=1)
for i in range(16):
e = Label(self.frame, text=str(i + 1) + '.')
e.grid(row=i+1, column=1)
e1 = Entry(self.frame, width=8)
e1.grid(row=i+1, column=2)
e2 = Entry(self.frame)
e2.grid(row=i+1, column=3)
check = Checkbutton(self.frame, variable=BooleanVar(), onvalue=True, offvalue=False)
check.grid(row=i+1, column=4, sticky=E)
but = Button(self.ws,text='Get ALL',command=self.getall)
but.grid(row=17,column=1)
self.ws.mainloop()
def getall(self):
list = []
self.frame.update()
print('Child List:',self.frame.winfo_children())
for wid in self.frame.winfo_children():
if isinstance(wid,Entry):
list.append(wid.get())
elif isinstance(wid,Checkbutton):
self.frame.getvar(wid['variable'])
print('List:',list)
if __name__ == '__main__':
App()
It returns:
return self.tk.getvar(name)
.TclError: can't read "PY_VAR0": no such variable
If i click all checkbuttons, it doesn't error but returns empty strings..Whats wrong here?

TKinter couldn't retrieve the variables of your checkboxes, because you have created these variables on the fly inside __init__() scope, hence these variables are living in __init__() call stack only, and once __init__() finished its work, the garbage collector cleans these variables, so they are not reachable any more since they are not live in your main stack.
So, you need to keep them living in your main program stack. I have edited your code by adding a long-lived dict() for storing these checkboxes variables to be able to access them later.
from tkinter import *
class App():
def __init__(self):
self.ws = Tk()
self.frame = LabelFrame(self.ws)
self.frame.grid(row=1, column=1)
self.checkboxesValues = dict()
for i in range(16):
self.checkboxesValues[i] = BooleanVar()
self.checkboxesValues[i].set(False)
e = Label(self.frame, text=str(i + 1) + '.')
e.grid(row=i+1, column=1)
e1 = Entry(self.frame, width=8)
e1.grid(row=i+1, column=2)
e2 = Entry(self.frame)
e2.grid(row=i+1, column=3)
check = Checkbutton(self.frame, variable=self.checkboxesValues[i])
check.grid(row=i+1, column=4, sticky=E)
but = Button(self.ws, text='Get ALL', command=self.getall)
but.grid(row=17, column=1)
self.ws.mainloop()
def getall(self):
list = []
self.frame.update()
print('Child List:', self.frame.winfo_children())
for wid in self.frame.winfo_children():
if isinstance(wid, Entry):
list.append(wid.get())
elif isinstance(wid, Checkbutton):
list.append(self.frame.getvar(wid['variable']))
print('List:', list)
if __name__ == '__main__':
App()
Now, checkboxesValues variable lives in your App's object stack, so your checkboxes variables are existing in your memory as long as your object is not destroyed.

Related

Retrieving attribute from checkbuttons - Tkinter, checkbuttons, python

I am trying to make a todo-program. Every Task has some attributes, one of them is a value based on some of the user input.
When you're adding a New Task there's an option to check all existing tasks which the new task could be somehow related to (e.g. maybe the New Task is to do the dishes and one of the existing tasks is to buy soap for it - so they're related somehow).
Here's a picture if it clarifies anything:
Let's say I have 3 boxes/existing tasks checked.
I want to retrieve each value attribute (val_var in code) associated with each of the checked task buttons. The sum of all the checked task-values will then be an attribute, connectivity, of the New Task currently being added.
However, I am not sure how I can "grab" all the checkbutton-values of the buttons that have been checked even though it most likely is a trivial issue.
Simplified code:
from tkinter import Tk, Frame, Button, Entry, Label, Canvas, OptionMenu, Toplevel, Checkbutton
import tkinter.messagebox
task_list = []
task_types = ['Sparetime', 'School', 'Work']
class Task:
def __init__(self, n, h, v,):
self.name = n
self.hours = h
self.value = v
#self.connectivity = c
def show_tasks():
task = task_list[-1]
print('\n')
print('Value:')
print(task.value)
def open_add_task():
taskwin = Toplevel(root)
taskwin.focus_force()
#Name
titlelabel = Label(taskwin, text='Title task concisely:', font=('Roboto',11,'bold')).grid(column=1, row=0)
name_entry = Entry(taskwin, width=40, justify='center')
name_entry.grid(column=1, row=1)
#HOURS(required)
hourlabel = Label(taskwin, text='Whole hours \n required', font=('Roboto',10)).grid(column=1, row=16)
hour_entry = Entry(taskwin, width=4, justify='center')
hour_entry.grid(column=1, row=17)
#CONNECTIVITY
C_lab = Label(taskwin,text="Check tasks this task is related to").grid(column=1, row=18)
placement=19
for task in task_list:
Checkbutton(taskwin, text=(task.name)).grid(column=1, row=placement, sticky="w")
placement+=1
def add_task():
if name_entry.get() != '':
val_var = (int(hour_entry.get())/10)
task_list.append(Task(name_entry.get(), hour_entry.get(), val_var))
show_tasks()
listbox_tasks.insert(tkinter.END, name_entry.get())
name_entry.delete(0, tkinter.END)
taskwin.destroy()
else:
tkinter.messagebox.showwarning(title='Whoops', message='You must enter a task')
Add_button = Button(taskwin, text='Add', font=('Roboto',10), command=add_task).grid(column=2, row=placement, sticky="e")
placement+=1
root = Tk()
task_frame = Frame()
# Create UI
your_tasks_label = Label(root, text='THESE ARE YOUR TASKS:', font=('Roboto',10, 'bold'), justify='center')
your_tasks_label.pack()
listbox_tasks = tkinter.Listbox(root, height=10, width=50, font=('Roboto',10), justify='center')
listbox_tasks.pack()
#BUTTONS
New_Task_Button = Button(root, text='New Task', width=42, command=open_add_task)
New_Task_Button.pack()
root.mainloop()
You can use a list to hold tkinter DoubleVar which is used in each task's Checkbutton with its value as the onvalue option. Then you can sum all the values in the list of DoubleVar to get the connectivity.
Below is a modified example based on your code:
from tkinter import Tk, Frame, Button, Entry, Label, Canvas, OptionMenu, Toplevel, Checkbutton, DoubleVar
import tkinter.messagebox
task_list = []
task_types = ['Sparetime', 'School', 'Work']
class Task:
def __init__(self, n, h, v, c): # enable the "connectivity"
self.name = n
self.hours = h
self.value = v
self.connectivity = c
# added to show the task details
def __str__(self):
return f"{self.name}: hours={self.hours}, value={self.value}, connectivity={self.connectivity}"
def show_tasks():
task = task_list[-1]
print(task) # show the task details
def open_add_task():
taskwin = Toplevel(root)
taskwin.focus_force()
#Name
titlelabel = Label(taskwin, text='Title task concisely:', font=('Roboto',11,'bold')).grid(column=1, row=0)
name_entry = Entry(taskwin, width=40, justify='center')
name_entry.grid(column=1, row=1)
#HOURS(required)
hourlabel = Label(taskwin, text='Whole hours \n required', font=('Roboto',10)).grid(column=1, row=16)
hour_entry = Entry(taskwin, width=4, justify='center')
hour_entry.grid(column=1, row=17)
#CONNECTIVITY
C_lab = Label(taskwin,text="Check tasks this task is related to").grid(column=1, row=18)
placement=19
vars = [] # list to hold the DoubleVar used by Checkbutton
for task in task_list:
# add a DoubleVar to the list
vars.append(DoubleVar())
# use the task.value as the "onvalue" option
Checkbutton(taskwin, text=task.name, variable=vars[-1], onvalue=task.value, offvalue=0).grid(column=1, row=placement, sticky="w")
placement+=1
def add_task():
if name_entry.get() != '':
val_var = (int(hour_entry.get())/10)
# calculate the "connectivity" of the new task
connectivity = sum(v.get() for v in vars)
task_list.append(Task(name_entry.get(), hour_entry.get(), val_var, connectivity))
show_tasks()
listbox_tasks.insert(tkinter.END, name_entry.get())
name_entry.delete(0, tkinter.END)
taskwin.destroy()
else:
tkinter.messagebox.showwarning(title='Whoops', message='You must enter a task')
Add_button = Button(taskwin, text='Add', font=('Roboto',10), command=add_task).grid(column=2, row=placement, sticky="e")
placement+=1
root = Tk()
task_frame = Frame()
# Create UI
your_tasks_label = Label(root, text='THESE ARE YOUR TASKS:', font=('Roboto',10, 'bold'), justify='center')
your_tasks_label.pack()
listbox_tasks = tkinter.Listbox(root, height=10, width=50, font=('Roboto',10), justify='center')
listbox_tasks.pack()
#BUTTONS
New_Task_Button = Button(root, text='New Task', width=42, command=open_add_task)
New_Task_Button.pack()
root.mainloop()

Python/Tkinter: Use button to control for loop

I have a for loop that is meant to run through a list, display some items in tkinter, wait for a button to be pushed, and then store some Entry and Checkbutton data. The code below is a MRE of the basics of what I'm trying to do. In the case below, when the Button is hit, I want to return to the loop_function and gather the variables from the button_function.
I thought perhaps using something like lambda: continue or lambda: return might bring it back to the first function, but those throw errors.
Any ideas?
from tkinter import *
class TestClass(Frame):
def __init__(self, parent=None):
self.parent = parent
Frame.__init__(self)
self.main = self.master
self.f = Frame(self.parent)
self.f.pack()
(Button(self.f, text='Start',
command = self.loop_function)
.grid(column=0, row=0, padx=10, pady=10))
def loop_function(self):
name_list = ['Luke', 'Han', 'Leia', 'Chewie']
for n in name_list:
self.button_function(n)
force_user = self.fu.get()
side = self.sd.get()
print(n, force_user, side)
def button_function(self, n):
self.fu = IntVar(value=1)
self.sd = StringVar(value='rebel')
self.fu_e = Checkbutton(self.f, variable=self.fu)
self.sd_e = Entry(self.f, textvariable=self.sd)
col = 0
lbl_list = ['Name', 'Force User?', 'Side']
for l in lbl_list:
(Label(self.f, text=l, width=11, anchor=W)
.grid(column=col, row=0, padx=10, pady=10))
col += 1
(Label(self.f, text=n, width=11, anchor=W)
.grid(column=0, row=1, padx=5))
self.fu_e.grid(column=1, row=1)
self.sd_e.grid(column=2, row=1)
(Button(self.f, text='Save',
command = lambda: print('WAIT HERE!!'))
.grid(column=1, row=2, padx=10, pady=10))
if __name__ == '__main__':
root=Tk()
ui = TestClass(root)
ui.pack()
root.mainloop()
I think the following code does what you want to do.
After clicking on the button Start the user gets a dialog where she can enter properties of the first user Luke. By clicking on the button Save the entered data is stored in some way. Then the properties of the next user (Han) can be edited.
A for loop is not the correct approach here. Instead we want to listen for the click events of the start and save buttons. In my solution, when the user clicks Start, the event handler pick_next_player is being called. This method always picks the next element from an iterator that I wrapped around name_list. Then the GUI elements are being rendered with your button_function.
The event handler save_action listens to the click event of the Save button. It collects the values that the user entered, stores it into self.results and displays the next player by calling pick_next_player.
When the last player has been saved, this script just prints a line 'finished ...' to the console. I assume you are going to stop the script or close the dialog there. But this is of course up to you.
from tkinter import *
class TestClass(Frame):
def __init__(self, parent=None):
self.parent = parent
Frame.__init__(self)
self.main = self.master
self.f = Frame(self.parent)
self.f.pack()
(
Button(self.f, text='Start', command=self.pick_next_player)
.grid(column=0, row=0, padx=10, pady=10)
)
self.name_list = ['Luke', 'Han', 'Leia', 'Chewie']
self.name_iter = iter(self.name_list)
self.results = []
self.current_name = None
def pick_next_player(self):
try:
self.current_name = next(self.name_iter)
except StopIteration:
print(f"finished: {self.results}")
return
self.button_function()
def button_function(self):
self.fu = IntVar(value=1)
self.sd = StringVar(value='rebel')
self.fu_e = Checkbutton(self.f, variable=self.fu)
self.sd_e = Entry(self.f, textvariable=self.sd)
col = 0
lbl_list = ['Name', 'Force User?', 'Side']
for l in lbl_list:
(Label(self.f, text=l, width=11, anchor=W)
.grid(column=col, row=0, padx=10, pady=10))
col += 1
(
Label(self.f, text=self.current_name, width=11, anchor=W)
.grid(column=0, row=1, padx=5)
)
self.fu_e.grid(column=1, row=1)
self.sd_e.grid(column=2, row=1)
(
Button(self.f, text='Save', command=self.save_action)
.grid(column=1, row=2, padx=10, pady=10)
)
def save_action(self):
force_user = self.fu.get()
side = self.sd.get()
print(f"saving {self.current_name}, {force_user}, {side}")
self.results.append({'name': self.current_name, 'force': force_user, 'faction': side})
self.pick_next_player()
if __name__ == '__main__':
root = Tk()
ui = TestClass(root)
ui.pack()
root.mainloop()

How to restrict number of signs in Entry widgets with Tkinter

Hello i tried to do that in loop, but can't understand why only last created one is the only one restricted? I'd like to limit the 12 created widgets in loop to 4 signs. Can someone help me? :3
PS. Sorry if something uncleared, i ask question here for the first time.
from tkinter import *
import trace
import random
class Plansza:
def __init__(self, master):
self.frame = Frame(master, bg="brown")
self.frame.pack()
self.tab = [random.randint(1, 6),random.randint(1, 6),random.randint(1, 6),random.randint(1, 6)]
print(self.tab)
print(len(self.tab))
self.max_len = 4
self.wynik = StringVar()
self.wynik.set(self.tab)
self.goal = Entry(self.frame, width=6, font=50, fg="purple", justify=CENTER, textvariable=self.wynik, show="*")
self.goal.grid(row=0, column=1, padx=30, pady=30)
self.pokaz = Button(self.frame, text = "Pokaż", command=self.show)
self.pokaz.grid(row=0, column=2)
self.wiersz=1
print(self.wynik.get())
self.var = [1,1,1,1,1,1,1,1,1,1,1,1]
self.iter = 0
self.map()
self.sprawdz = Button(self.frame, text = "Sprawdź")
self.sprawdz.grid(row=self.wiersz+1, column=1, padx=50, pady=50)
def on_write(self, *arg):
s = self.var[self.iter].get()
if len(s) > self.max_len:
self.var[self.iter].set(s[:self.max_len])
def show(self):
self.goal.config(show="")
def map(self):
self.var[self.iter]=StringVar()
self.var[self.iter].trace_variable("w", self.on_write)
self.pole_na_odp = Entry(self.frame, width=6, font=50, fg="purple", justify=CENTER, textvariable=self.var[self.iter])
self.pole_na_odp.grid(row=self.wiersz, column=1, padx=20, pady=20)
self.wiersz+=1
self.var.append([])
self.iter+=1
if(self.wiersz<12):
self.map()
root = Tk()
b = Plansza(root)
root.mainloop()
[EDIT] I did a list but now I've got another error:
Whenever i wanna type something in my Entry widgets i got en error like this:
s = self.var[self.iter].get()
AttributeError: 'int' object has no attribute 'get'
And there are no more restrict number of sings even in last Entry widget.
(Answer to the question in your edit.)
In this line
s = self.var[self.iter].get()
it looks like you are trying to use get() to return an element of self.var. That is not how lists work. I'm pretty sure what you want is
s = self.var[self.iter]

Python - pass value back to main() based on when a button is pressed

Forgive me if this is a badly mangled way of doing things, but I'm new to development in general.
I am trying to create a window with a number of buttons using tkinter, each button having the name of a player on it using a class called from main().
I then want to be able to use the name on the button that is pressed later in the app, so I want to pass that back to main(). So, if I click on the Annie button, I want to open up a new window later called 'Options for Annie' and I'm assuming that the value 'Annie' needs to be passed back to the main function.
My main code:
<imports appear here>
def main():
players = ['Annie','Benny','Carrie','Donny']
winHome = playerWindow(root,players)
print("In main() : " + winHome.selected)
root.mainloop()
if __name__ == "__main__":
main()
My class code:
<imports appear here>
root=Tk()
class playerWindow():
def selectPlayer(self,selname):
self.selected = selname
print("In class: " + self.selected)
def __init__(self, master, players=[]):
colours = ['red','green','orange','white','yellow','blue']
self.selected = ""
r = 0
for p in players:
randcol = random.randint(0,len(colours))-1
if colours[randcol] in ('blue','green'):
fgcol='white'
else:
fgcol='black'
playername = delplayer = p
playername = Button(root, text=playername, bg=colours[randcol], fg=fgcol, width=15, command=lambda name = playername:self.selectPlayer(name)).grid(row=r,column=0)
s = ttk.Separator(root, orient=VERTICAL)
delplayer = Button(root, text='Del', bg='grey', fg='red', width=5, command=lambda name = delplayer: print("Delete Player %s" % name)).grid(row=r,column=1)
r = r + 1
Button(root, text="New Player", fg="black", command=lambda: print("New Player Functionality"), width=15).grid(row = len(players)+3,column=0)
Button(root, text="QUIT", fg="red", command=root.destroy, width=15).grid(row = len(players)+3,column=1)
What is happening is that the window gets created, the next line in main() is run (my added print statement) which is empty, obviously as main is continuing. When I press the button, the sleectPlayer function is called and works.
Somehow I need to get the value back to main() to go on to the next task using that value, however I don't seem to be able to frame the question correctly to get the answers I need.
Any help would be greatly appreciated.
I am using Python 3.5.1
You are already accessing it. I personally don't like returning to the main function, instead I suggest creating a top-level class to link back to. This should help make things flow better.
import tkinter as tk
from tkinter import ttk
import random
class PlayerWindow():
def __init__(self, master, parent, players=[]):
self._parent = parent
colours = ['red','green','orange','white','yellow','blue']
self.selected = ""
r = 0
for p in players:
randcol = random.randint(0,len(colours))-1
if colours[randcol] in ('blue','green'):
fgcol='white'
else:
fgcol='black'
playername = delplayer = p
playername = tk.Button(master, text=playername, bg=colours[randcol], \
fg=fgcol, width=15, command=lambda name = \
playername:self.selectPlayer(name)).grid(row=r,column=0)
s = ttk.Separator(master, orient=tk.VERTICAL)
delplayer = tk.Button(master, text='Del', bg='grey', fg='red', \
width=5, command=lambda name = delplayer: \
print("Delete Player %s" % name)).grid(row=r,column=1)
r = r + 1
tk.Button(master, text="New Player", fg="black", command=lambda: \
print("New Player Functionality"), width=15).\
grid(row = len(players)+3,column=0)
tk.Button(master, text="QUIT", fg="red", command=self._parent.close,
width=15).grid(row = len(players)+3,column=1)
def selectPlayer(self, selname):
self.selected = selname
print("In class: " + self.selected)
self._parent.hello() # call hello function of top-level, links back
class MyApplication(object):
def __init__(self, master):
self._master = master
players = ['Annie','Benny','Carrie','Donny']
self._player_window = PlayerWindow(master, self, players)
print("In main() : " + self._player_window.selected)
def hello(self):
name = self._player_window.selected
print("Hello, %s" % name)
def close(self):
# any other clean-up
self._master.destroy()
def main():
root = tk.Tk()
app = MyApplication(root)
root.mainloop()
if __name__ == "__main__":
main()

Python Tkinter: How do I avoid global variables in a changing class?

I'm doing a large project at the moment to help me learn my first programming language (Python) and I've run into some unknown territory. I am aware that it's generally bad to use global variables and there are better solutions, but I can't figure it out for my situation.
I've made the code below as a simple example of what I want to achieve. What's the best way to do this instead of using the global variable?
Also, are there any general errors I've made in my code below?
Thanks in advance
from tkinter import *
root = Tk()
display_number = 5
class NumberBox():
def __init__(self):
global display_number
self.number_label = Label(root, text=display_number)
self.number_label.pack()
self.engine()
def engine(self):
self.number_label.config(text=display_number)
root.after(10, self.engine)
def change_number(operation):
global display_number
if operation == "add":
display_number += 1
if operation == "subtract":
display_number -= 1
Button(root, text="Add Class", command=lambda: NumberBox()).pack()
Button(root, text="Number UP", command=lambda: change_number("add")).pack()
Button(root, text="Number DOWN", command=lambda: change_number("subtract")).pack()
for _ in range(5):
NumberBox()
root.mainloop()
I made several changes:
Create a new class NumberBoxesList to avoid global variables and make program's logic more visible
Remove root.after method: this method should not be used for updates immediately following user's actions
Use import tkinter as tk : import * is bad practice
Result:
import tkinter as tk
class NumberBox():
def __init__(self, root, display_number):
self.number_label = tk.Label(root, text=display_number)
self.number_label.pack()
def changeNumber(self, display_number):
self.number_label.config(text=display_number)
class NumberBoxesList():
def __init__(self, root, start_number = 5):
self.number = start_number
self.root = root
self.boxes = []
tk.Button(root, text="Add Class", command= self.addBox).pack()
tk.Button(root, text="Number UP", command=lambda: self.change_number("add")).pack()
tk.Button(root, text="Number DOWN", command=lambda: self.change_number("subtract")).pack()
def addBox(self):
self.boxes.append(NumberBox(self.root, self.number))
def change_number(self, operation):
if operation == "add":
self.number += 1
if operation == "subtract":
self.number -= 1
for box in self.boxes:
box.changeNumber(self.number)
root = tk.Tk()
boxList = NumberBoxesList(root)
for _ in range(5):
boxList.addBox()
root.mainloop()
like this:
from tkinter import *
root = Tk()
class NumberBox():
display_number = 5
def __init__(self):
self.number_label = Label(root, text=self.display_number)
self.number_label.pack()
self.engine()
def engine(self):
self.number_label.config(text=self.display_number)
root.after(10, self.engine)
def change_number(operation):
if operation == "add":
NumberBox.display_number += 1
if operation == "subtract":
NumberBox.display_number -= 1
Button(root, text="Add Class", command=lambda: NumberBox()).pack()
Button(root, text="Number UP", command=lambda: change_number("add")).pack()
Button(root, text="Number DOWN", command=lambda: change_number("subtract")).pack()
for _ in range(5):
NumberBox()
root.mainloop()
by defining the variable in the class (but outside the __init__ it becomes owned by all instances of the class as a single variable, so changing it in one affects all instances

Categories