I am building a gui in tkinter with a list task_list = [].
Tasks are appended to/deleted from the list in the gui.
I want a window with checkboxes for every item in the list.
So if there's 10 items in the list, there should also be 10 checkboxes.
If there's 5 items in the list, there should be 5 corresponding checkboxes.
Can this be done?
I can't find anything on it
Thanks!
Here.
from tkinter import *
task_list=["Call","Work","Help"]
root=Tk()
Label(root,text="My Tasks").place(x=5,y=0)
placement=20
for tasks in task_list:
Checkbutton(root,text=str(tasks)).place(x=5,y=placement)
placement+=20
root.mainloop()
Using grid.
from tkinter import *
task_list=["Call","Work","Help"]
root=Tk()
Label(root,text="My Tasks").grid(row=0,column=0)
placement=3
for tasks in task_list:
Checkbutton(root,text=str(tasks)).grid(row=placement,column=0,sticky="w")
placement+=3
root.mainloop()
Here is my code for this issue:
from tkinter import Tk, Checkbutton, IntVar, Frame, Label
from functools import partial
task_list = ['Task 1', 'Task 2', 'Task 3', 'Work', 'Study']
def choose(index, task):
print(f'Selected task: {task}' if var_list[index].get() == 1 else f'Unselected task: {task}')
root = Tk()
Label(root, text='Tasks').grid(column=0, row=0)
frame = Frame(root)
frame.grid(column=0, row=1)
var_list = []
for index, task in enumerate(task_list):
var_list.append(IntVar(value=0))
Checkbutton(frame, variable=var_list[index],
text=task, command=partial(choose, index, task)).pack()
root.mainloop()
First I would like to mention that it is possible to mix layout manager methods like in this example. The main window uses grid as layout management method and I have gridded a frame to the window, but notice that Checkbuttons are getting packed, that is because frame is a different container so it is possible to use a different layout manager, which in this case makes it easier because pack just puts those checkbuttons one after another.
The other stuff:
There is the task list which would contain the tasks.
Then I have defined a function choose() this function prints out something. It depends on a variable. The comparison happens like this: print out this if value is this else print out this. It is just an if/else statement in one line and all it checks is if the IntVar in that list in that specific index is value 1 so "on". And there are two argument this function takes in: index and task. The index is meant to get the according IntVar from the var_list and the task is meant to display what tasks was chosen or unchosen.
Then the simple root = Tk() and root.mainloop() at the end.
Then is the label that just explains it.
Then the frame and here You can see that both label and frame were gridded using .grid()
Then the var_list = [] just creates an empty list.
Then comes the loop:
It loops over each item in the task_list and extracts the index of that item in the list and the value itself. This is done by using enumerate() function.
Each iteration appends a IntVar(value=0) to the var_list and since this appending happens at the same time as the items are read from the task_list the index of that IntVar in the list is the same as the current item in the task_list so that same index can be used for access.
Then a Checkbutton is created, its master is the frame (so that .pack() can be used) and the text=task so it corresponds to task name, the variable is set as a specific item in the var_list by index and this all has to be done so that a reference to that IntVar is kept. Then comes command=partial(choose, index, task) which may seem confusing but all partial does is basically this function will now execute always with the variables just given so those variables will always be the same for this function for this Checkbutton. And the first argument of partial is the function to be executed and next are arguments this function takes in. Then the Checkbutton gets packed.
If You have any questions ask.
Useful sources:
About partial() (though there are other sources too)
About Checkbutton (other sources about this too)
One line if/else statements
Related
I am having some issues wrapping my head around something I encountered in python recently.
So, basically, I want to allow for a user to load several json files, all listed in a python list. These files contain parameters used to create buttons with, namely, the color the button should have, the text that should be displayed in it and the command that it needs to execute once clicked.
def createTags(self):
for items in self.LoadedInstallProfiles:
with open(items, "r") as jsonfiles:
self.loadeddata = json.load(jsonfiles)
self.tag = Button(self.tagmenu, text=self.loadeddata.get("profilename"), background=
self.loadeddata.get("profilecolor"), command=print(self.loadeddata.get("profilename")))
self.tag.pack(side="top",fill="x")
The problem is: the buttons show up with their individual color and text, but all seem to print out the same profilename when clicked, which is that in the last json file in the list.
I common way is to store the created button widgets in a list. I have modified your method. See below.
def createTags(self):
# First create the widget and append to list variable
self.tags = [] #List to store button widgets
for items in self.LoadedInstallProfiles:
with open(items, "r") as jsonfiles:
loadeddata = json.load(jsonfiles)
text = loadeddata.get("profilename")
bg = loadeddata.get("profilecolor")
tag = Button( self.tagmenu, text=text, background=bg, command=print(text) )
self.tag.append( tag )
# Then display the widgets
for tag in self.tags:
tag.pack(side="top",fill="x")
I imagine the problem with command=print(self.loadeddata.get("profilename")) is similar to the problem with lambda statements (that said I am surprised your buttons work at all They should print once at init and then never work after that because you are calling print at button creation instead of saving a reference to print).
Due to the nature of how lambda works here in a loop like this you end up only printing the last value in the loop for all commands. Instead you need to use a lambda statement and also define the value in the lambda for each loop to accurately record the correct data for the print statement.\
I created 3 test files for this:
test.json:
{"profilename":"test", "profilecolor": "green"}
test2.json:
{"profilename":"test2", "profilecolor": "blue"}
test3.json:
{"profilename":"test3", "profilecolor": "orange"}
Example code:
import tkinter as tk
import json
class Window(tk.Tk):
def __init__(self):
super().__init__()
self.btn_list = []
for file in ['test.json', 'test2.json', 'test3.json']:
with open(file, 'r') as f:
self.btn_list.append(json.load(f))
self.create_tags()
def create_tags(self):
for item in self.btn_list:
tk.Button(self, text=item.get("profilename"), background=item.get("profilecolor"),
command=lambda x=item.get("profilename"): print(x)).pack(side="top", fill="x")
if __name__ == '__main__':
Window().mainloop()
Results:
What I am trying to do track when any values in a list of StringVar change, even when the list is expanding. Any additions to the list before the trace statement will result in the callback. But any additions afterward, such as when pressing a button, will not cause any callback.
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root)
frame.grid(row=0)
L = []
def add_entry(event):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
add = tk.Button(frame,text='add Entry',command='buttonpressed')
add.grid(row=0)
add.bind('<Button-1>',add_entry)
for i in range(2):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
for i in L:
i.trace('w',lambda *arg:print('Modified'))
root.mainloop()
Modifying the first two Entry's prints out Modified, but any Entry's after the trace is run, such as the ones produced when a button is pressed, will not.
How do I make it so that trace method will run the callback for the entire list of variables even if the list is expanded?
Simple suggestion, change your add_entry function to something like this:
def add_entry(event):
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[len(L)-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
L[len(L)-1].trace('w',lambda *arg:print('Modified'))
Extra suggestions:
This add = tk.Button(frame,text='add Entry',command='buttonpressed') is assigning a string to command option, means it will try to execute that string when button is clicked(which will do nothing). Instead, you can assign your function add_entry to command option and it will call that function when button is clicked and you can avoid binding Mouse Button1 click to your Button(Note: No need to use argument event in function when using like this). Read more here
Python supports negative indexing of List, so you can call L[-1] to retrieve the last element in the list instead of calling L[len(L)-1]).
Once you change your add_entry function as suggested, you can reduce your code to
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root)
frame.grid(row=0)
L = []
def add_entry():
global L
L.append(tk.StringVar())
tk.Entry(frame,textvariable=L[-1]).grid(row=len(L),padx=(10,10),pady=(5,5))
L[-1].trace('w',lambda *arg:print('Modified'))
add = tk.Button(frame,text='add Entry',command=add_entry)
add.grid(row=0)
for i in range(2):
add_entry()
root.mainloop()
What I am attempting to create is a program that will first create a pandas dataframe. Then, it will make a tkinter window with an input entry frame, a button, and a text box. For the code I have below, when the button is pressed, I get an output that shows the header of the dataframe and the row that was "searched".
import pandas
from tkinter import *
#creates the dataframe
summer17=pandas.read_excel("summer17.xlsx","list")
window = Tk() #start of the main window
#function that will search the dataframe column "company" for any matches
def search_df():
search_result=summer17[summer17['Company'].str.contains("CAPS")]
t1.insert(END,search_result)
#Creates the entry box
e1_value=StringVar()
e1=Entry(window)
e1.grid(row=0,column=0)
#Creates a button
b1=Button(window,width=10,text='search',command=search_df)
b1.grid(row=0,column=1)
#Creates a text box
t1=Text(window,height=5,width=80)
t1.grid(row=0,column=2)
window.mainloop() #end of the main window
That works all and well, however, I want the user to be able to input a value into the entry box and press the button and search for that entry. So I change the function to be
def search_df():
search_result=summer17[summer17['Company'].str.contains(e1_value.get())]
t1.insert(END,search_result)
If I leave this blank, it returns the entire data frame (as I may or may not expect). However, if I put CAPS in the entry box and press the button, It still returns the entire dataframe.
My guess is that when I am getting the value from the entry box, there is a variable miss match, but I'm not sure how I would correct that.
i used one of my files to create the dataframe.
you need to add the e1_value to the entry by using the textvariable parameter.
I added a bind between the enter key and your function therefore you don't have to press the button, you can press the enter key instead. To do that, i used the bind function. This function binds a widget and a tkinter event. it executes the chosen function and passes a parameter (which is the event).
However the command paramter of the widget button does not pass any parameter when it executes the chosen function (the event is always a left clic). That's why your function takes *event as a parameter, event can be None. (i used *event but event=None works too, and i don't know which way is the most pythonic way, sorry)
PS : You should use import tkinter as tk because you may have some conflict with variable and function names if you use from tkinter import *
import pandas
import tkinter as tk
#creates the dataframe
summer17=pandas.read_csv("User_AD_Commun01_2017-07-26_15-01.csv",
sep=";",
encoding="latin1")
window = tk.Tk() #start of the main window
#function that will search the dataframe column "company" for any matches
def search_df(*event):
search_result=summer17.loc[summer17['company'].str.contains(e1_value.get(),
na=False, #ignore the cell's value is Nan
case=False)] #case insensitive
t1.insert(tk.END,search_result)
#Creates the entry box and link the e1_value to the variable
e1_value=tk.StringVar()
e1=tk.Entry(window, textvariable=e1_value)
e1.grid(row=0,column=0)
#execute the search_df function when you hit the "enter" key and put an event
#parameter
e1.bind("<Return>", search_df)
#Creates a button
b1=tk.Button(window,
width=10,
text='search',
command=search_df)
b1.grid(row=0,column=1)
#Creates a text box
t1=tk.Text(window,height=5,width=80)
t1.grid(row=0,column=2)
window.mainloop() #end of the main window
{Edit: the answer by Bryan Oakley in the suggested duplicate question enter link description here a) fires a response on change to the array variable (arrayvar.trace mode="w"), and I need it triggered on FocusOut, as described in my original question; b) works for Python 2, but I'm having trouble converting it to work in Python 3.5. I'm currently using his and pyfunc's answers as leads and trying to figure out a similar solution using a FocusOut event.}
I am working on a tkinter GUI that lets a user select a particular type of calculation, using a pair of radio button lists. Based on the selections, a tool bar is populated with multiple modular entry widgets, one for each variable the calculation requires. The goal is to have the numerical entry values passed to the model, which will return data to be graphed on a canvas or matplotlib widget.
My question is: what typical strategy is used for gathering and continually refreshing values from multiple widgets, in order to update displays and to pass them on to the model? The trick here is that there will be a large number of possible calculation types, each with their own toolbar. I'd like the active toolbar to be "aware" of its contents, and ping the model on every change to a widget entry.
I think the widgets and the toolbar would have to be classes, where the toolbar can query each widget for a fresh copy of its entry values when a change is detected, and store them as some collection that is passed to the model. I'm not entirely sure how it can track changes to the widgets. Using a "validate='focusout' " validation on the entry widget (e.g. as in
this validation reference )
suggests itself, but I already use "validate='key' " to limit all entries to numbers. I don't want to use "validate=all" and piggyback onto it because I don't want to continually ask the model to do a lengthy calculation on every keypress.
I'm new to GUI programming, however, so I may be barking up the wrong tree. I'm sure there must be a standard design pattern to address this, but I haven't found it.
Below is a screenshot of a mockup to illustrate what I want the GUI to do. The Task radiobutton controls which secondary button menu appears below. The selection in the second menu populates the top toolbar with the necessary entry widgets.
The following code does (mostly) what I want. The ToolBar frame objects will store the values from its contained widgets, and call the appropriate model as needed. The VarBox objects are Entry widgets with extra functionality. Hitting Tab or Return refreshes the data stored in the ToolBar dictionary, tells the ToolBar to send data to the model, and shifts focus to the next VarBox widget.
from tkinter import *
# Actual model would be imported. "Dummy" model for testing below.
def dummy_model(dic):
"""
A "dummy" model for testing the ability for a toolbar to ping the model.
Argument:
-dic: a dictionary whose values are numbers.
Result:
-prints the sum of dic's values.
"""
total = 0
for value in dic.values():
total += value
print('The total of the entries is: ', total)
class ToolBar(Frame):
"""
A frame object that contains entry widgets, a dictionary of
their current contents, and a function to call the appropriate model.
"""
def __init__(self, parent=None, **options):
Frame.__init__(self, parent, **options)
self.vars = {}
def call_model(self):
print('Sending to dummy_model: ', self.vars)
dummy_model(self.vars)
class VarBox(Frame):
"""
A customized Frame containing a numerical entry box
Arguments:
-name: Name of the variable; appears above the entry box
-default: default value in entry
"""
def __init__(self, parent=None, name='', default=0.00, **options):
Frame.__init__(self, parent, relief=RIDGE, borderwidth=1, **options)
Label(self, text=name).pack(side=TOP)
self.widgetName = name # will be key in dictionary
# Entries will be limited to numerical
ent = Entry(self, validate='key') # check for number on keypress
ent.pack(side=TOP, fill=X)
self.value = StringVar()
ent.config(textvariable=self.value)
self.value.set(str(default))
ent.bind('<Return>', lambda event: self.to_dict(event))
ent.bind('<FocusOut>', lambda event: self.to_dict(event))
# check on each keypress if new result will be a number
ent['validatecommand'] = (self.register(self.is_number), '%P')
# sound 'bell' if bad keypress
ent['invalidcommand'] = 'bell'
#staticmethod
def is_number(entry):
"""
tests to see if entry is acceptable (either empty, or able to be
converted to a float.)
"""
if not entry:
return True # Empty string: OK if entire entry deleted
try:
float(entry)
return True
except ValueError:
return False
def to_dict(self, event):
"""
On event: Records widget's status to the container's dictionary of
values, fills the entry with 0.00 if it was empty, tells the container
to send data to the model, and shifts focus to the next entry box (after
Return or Tab).
"""
if not self.value.get(): # if entry left blank,
self.value.set(0.00) # fill it with zero
# Add the widget's status to the container's dictionary
self.master.vars[self.widgetName] = float(self.value.get())
self.master.call_model()
event.widget.tk_focusNext().focus()
root = Tk() # create app window
BarParentFrame = ToolBar(root) # holds individual toolbar frames
BarParentFrame.pack(side=TOP)
BarParentFrame.widgetName = 'BarParentFrame'
# Pad out rest of window for visual effect
SpaceFiller = Canvas(root, width=800, height=600, bg='beige')
SpaceFiller.pack(expand=YES, fill=BOTH)
Label(BarParentFrame, text='placeholder').pack(expand=NO, fill=X)
A = VarBox(BarParentFrame, name='A', default=5.00)
A.pack(side=LEFT)
B = VarBox(BarParentFrame, name='B', default=3.00)
B.pack(side=LEFT)
root.mainloop()
Ive made a sample program on how generally it looks like.. my goal is to have the Data Entry write permanently to the button so dat if I run the program again its update the current price.
from tkinter import*
import tkinter as tk
import tkinter.simpledialog
def changeP1(event):
btnT4=tk.Button(root,text='Updating...',width=10,bg='green')
btnT4.grid(in_=root,row=1,column=2)
btnT4.bind('<1>',changeP1)
askC1=tk.simpledialog.askfloat('Updating...','What is the current price?')
btnT4=tk.Button(root,text=('RM {:,.2f}'.format(askC1)),width=10)
btnT4.grid(in_=root,row=1,column=2)
btnT4.bind('<1>',changeP1)
def changeP2(event):
btnT4=tk.Button(root,text='Updating...',width=10,bg='green')
btnT4.grid(in_=root,row=2,column=2)
btnT4.bind('<1>',changeP2)
askC2=tk.simpledialog.askfloat('Updating...','What is the current price?')
btnT4=tk.Button(root,text=('RM {:,.2f}'.format(askC2)),width=10)
btnT4.grid(in_=root,row=2,column=2)
btnT4.bind('<1>',changeP2)
def changeP3(event):
btnT4=tk.Button(root,text='Updating...',width=10,bg='green')
btnT4.grid(in_=root,row=3,column=2)
btnT4.bind('<1>',changeP3)
askC3=tk.simpledialog.askfloat('Updating...','What is the current price?')
btnT4=tk.Button(root,text=('RM {:,.2f}'.format(askC3)),width=10)
btnT4.grid(in_=root,row=3,column=2)
btnT4.bind('<1>',changeP3)
root=Tk()
Title=['Item','Unit','Price']
Item=['Kopi O','Teh O','Teh Tarik']
Unit= '1 cup'
Price=[1,0.9,1.2]
cl=[0,1,2]
rw=[1,2,3]
for i in range(3):
btnT1=tk.Button(root,text=Title[i],width=10,bg='yellow')
btnT1.grid(in_=root,row=0,column=cl[i])
for x in range(3):
btnT2=tk.Button(root,text=Item[x],width=10)
btnT2.grid(in_=root,row=rw[x],column=0)
for y in range(3):
btnT3=tk.Button(root,text=Unit,width=10)
btnT3.grid(in_=root,row=rw[y],column=1)
for z in range(3):
btnT4=tk.Button(root,text=('RM {:,.2f}'.format(Price[z])),width=10)
btnT4.grid(in_=root,row=rw[z],column=2)
if z in range(0,1):
btnT4.bind('<1>',changeP1)
if z in range(1,2):
btnT4.bind('<1>',changeP2)
if z in range(2,3):
btnT4.bind('<1>',changeP3)
root.mainloop()
and if theres anyway to make this simpler..
You have 2 options (well that I know of) since you're dynamically creating your buttons. Both options only require one function.
If you wish to use bind then you can get the selected widget using event.widget
def onChange(event):
ans = tk.simpledialog.askfloat('Updating...','What is the current price?')
if ans: # checks if None is returned when clicking cancel
event.widget.config(text='RM {:,.2f}'.format(ans))
And so in your loop you'd only have the one bind btnT4.bind('<1>', onChange).
Alternatively use the command attribute for the button to assign the function to be called when the button is pressed. Using command for the button is generally more pythonic than binding.
This requires you to also create a list to store the buttons, to allow the function to know which widget to change.
btn_list = [] # create an empty list for the buttons
# Your for loop will look like this the command parameter instead
# and append the button to the list
for z in range(3):
btnT4=tk.Button(root,text=('RM {:,.2f}'.format(Price[z])),width=10,\
command=lambda i=z: onChange(i))
btnT4.grid(in_=root,row=rw[z],column=2)
btn_list.append(btnT4)
lambda will pass the value of z into the onChange function to create a unique call for that button. The value of 'z' is relative to the index position of the button in the list.
The onChange function when called will ask for the new input, and if valid will update the button object stored in the list using the index.
# Your change function will look like this
def onChange(i):
ans = tk.simpledialog.askfloat('Updating...','What is the current price?')
if ans:
btn_list[i].config(text='RM {:,.2f}'.format(ans))