I am doing a project for school, but I am stuck at the point of the creation of multiple checkboxes. I copy/pasted some code and messed around with it, trying to get two checkboxes, but I was unable to do so. The checkboxes use the same variable to tick/untick (I know this). I am unable to find a way to get a second variable to allow the second checkbox to be ticked or unticked depending on whether the first is unticked or ticked.
from Tkinter import *
root = Tk()
root.title("Payroll")
root.resizable(width=FALSE, height=FALSE)
class App:
def __init__(self,master):
root.minsize(width=500, height=500)
root.maxsize(width=500, height=500)
self.var = IntVar()
frame = Frame(master)
frame.grid()
f2 = Frame(master,width=200,height=100)
f2.grid(row=0,column=1)
btnSalary = Checkbutton(frame,text='Salary',variable=self.var,command=self.fx)
btnWage = Checkbutton(frame,text='Wage',variable=self.var,command= self.fx)
btnSalary.grid(row=0,column=0)
btnWage.grid(row=2,column=0)
msg1="Wage"
self.v= Message(f2,text=msg1)
msg2="Salary"
self.v= Message(f2,text=msg2)
def fx(self):
if self.var.get():
self.v.grid(column=1,row=0,sticky=N)
else:
self.v.grid_remove()
app = App(root)
root.mainloop()
Checkbuttons and Radiobuttons have very specific use cases:
You would use one or more Checkbuttons when the user can check none, any, or all of the options. A checkbutton always has two values, an onvalue and an offvalue (typically True and False, answering the question "is it checked").
You would use one or more Radiobuttons when the user can pick exactly one choice from a set of choices.
If your case it looks like you're wanting the user to choose either Salary or Wage. If that is true, you need to use Radiobuttons, not Checkbuttons. When you use Radiobuttons, all of the choices that act as one must use the same textvariable. This is how Tkinter knows they work as a set. Also, you must give each Radiobutton a distinct value, so that when you get the value you will know which one is selected.
In your case, the widgets might look something like this:
btnSalary = Radiobutton(..., variable=self.var,value="salary", ...)
btnWage = Radiobutton(...,variable=self.var, value="wage", ...)
Related
from tkinter import *
from tkinter.ttk import Combobox
v1=[]
root = Tk()
root.geometry('500x500')
frame1=Frame(root,bg='#80c1ff',bd=5)
frame1.place(relx=0.5,rely=0.1,relwidth=0.75,relheight=0.1,anchor='n')
lower_frame=Frame(root,bg='#80c1ff',bd=10)
lower_frame.place(relx=0.5,rely=0.25,relwidth=0.75,relheight=0.6,anchor='n')
v=[]
def maincombo():
Types=["MA","MM","MI","SYS","IN"]
combo1=Combobox(frame1,values=Types)
combo1.place(relx=0.05,rely=0.25)
combo2=Combobox(frame1,values=v)
combo2.bind('<<ComboboxSelected>>', combofill)
combo2.place(relx=0.45,rely=0.25)
def combofill():
if combo1.get()=="MA":
v=[1,2,3,45]
combo2=Combobox(frame1,values=v)
combo2.place(relx=0.45,rely=0.25)
if combo1.get()=="MM":
v=[5,6,7,8,9]
combo2=Combobox(frame1,values=v)
combo2.place(relx=0.45,rely=0.25)
maincombo()
root.mainloop()
I want to populate the one combobox based on selection of other combobox I,e types.But failed to do so with simple functions.
Looking at you code, most of what you need is already there. The changes I have made are as follows:
Bound to combo1 rather than combo2 (as combo1 is the one you want to monitor)
Set combo1 and combo2 as global variables (so they can be used in the combofill method)
Set the combofill method to accept the event arg (it would raise a TypeError otherwise)
Use the .config method on combo2 rather than creating a new one each time
Set combo2 to be empty when neither "MA" or "MM" are selected
Here is my implementation of that:
from tkinter import *
from tkinter.ttk import Combobox
v1=[]
root = Tk()
root.geometry('500x500')
frame1=Frame(root,bg='#80c1ff',bd=5)
frame1.place(relx=0.5,rely=0.1,relwidth=0.75,relheight=0.1,anchor='n')
lower_frame=Frame(root,bg='#80c1ff',bd=10)
lower_frame.place(relx=0.5,rely=0.25,relwidth=0.75,relheight=0.6,anchor='n')
v=[]
def maincombo():
global combo1, combo2
Types=["MA","MM","MI","SYS","IN"]
combo1=Combobox(frame1,values=Types)
combo1.place(relx=0.05,rely=0.25)
combo1.bind('<<ComboboxSelected>>', combofill)
combo2=Combobox(frame1,values=v)
combo2.place(relx=0.45,rely=0.25)
def combofill(event):
if combo1.get()=="MA":
v=[1,2,3,45]
elif combo1.get()=="MM":
v=[5,6,7,8,9]
else:
v=[]
combo2.config(values=v)
maincombo()
root.mainloop()
A couple other ideas for potential future consideration:
I would recommend using the grid manager rather than the place manager as it will stop widgets overlapping, etc. (on my system, combo2 slightly covers combo1)
Use a dictionary rather than if ... v=... elif ... v= ... and then use the get method so you can give the default argument. For example:
v={"MA": [1,2,3,45],
"MM": [5,6,7,8,9]}. \
get(combo1.get(), [])
EDIT:
Responding to the question in the comments, the following is my implementation of how to make a "toggle combobox" using comma-separated values as requested.
As the combobox has already overwritten the value of the text area when our <<ComboboxSelected>> binding is called, I had to add a text variable trace so we could keep track of the previous value of the text area (and therefore append the new value, etc.). I am pretty sure that explanation is completely inadequate so: if in doubt, look at the code!
from tkinter import *
from tkinter.ttk import Combobox
root = Tk()
def log_last():
global last, cur
last = cur
cur = tx.get()
def append_tx(event):
if last:
v = last.split(",")
else:
v = []
v = list(filter(None, v))
if cur in v:
v.remove(cur)
else:
v.append(cur)
tx.set(",".join(v))
combo.selection_clear()
combo.icursor("end")
last, cur = "", ""
tx = StringVar()
combo = Combobox(root, textvariable=tx, values=list(range(10)))
combo.pack()
combo.bind("<<ComboboxSelected>>", append_tx)
tx.trace("w", lambda a, b, c: log_last())
root.mainloop()
I want to make a unit converter in tkinter. I made two drop-down menus; the first one allows the user to select the unit they want to convert from, and the second one allows them to choose the unit they want to convert to. I want to disable all the options that do not make sense in the second menu after they have selected an option in the first one (if they want to convert kilograms it would not make sense to choose centimeters in the second menu)
I have tried to use a StringVar() to change the state of the menu, but it is not working. I have no idea of what to do next. I have been using the documentation of Tutorialspoint, but I cannot find anything that works (first time using tkinter).
import tkinter as tk
root = tk.Tk()
root.geometry('600x600')
my_var = tk.StringVar()
my_var.set('active')
unit_1 = tk.Menubutton(root,text='This is the first menu button',bg='white',activebackground='#2E64FE',activeforeground='#FFFFFF')
menu_1 = tk.Menu(unit_1)
unit_1.config(menu=menu_1)
menu_1.add_command(label='Inches',command= lambda: my_var.set('disabled') )
menu_1.add_command(label='Kilograms')
unit_2 = tk.Menubutton(root,text='This is the second menu button',bg='white',activebackground='#2E64FE',activeforeground='#FFFFFF')
menu_2 = tk.Menu(unit_2)
unit_2.config(menu=menu_2)
menu_2.add_command(label='Centimeters')
menu_2.add_command(label='Pounds',state= my_var.get())
unit_1.place(relx=0.03,rely=0.08,relheight=0.04,relwidth=0.45)
unit_2.place(relx=0.52,rely=0.08,relheight=0.04,relwidth=0.45)
root.mainloop()
Here I am trying to make the button 'Inches' in the first menu to disable the button 'Pounds' in the second menu, but when I click on 'Inches' nothing happens to 'Pounds'.
tk.StringVar() is used to change text on something, for example if you want to have a button with dynamic text, you may want to use a tk.StringVar() for that.
What you want to do is something different; you want to change the configuration of a label. So you need to find the element and adjust its state:
import tkinter as tk
root = tk.Tk()
root.geometry('600x600')
my_var = tk.StringVar()
my_var.set('active')
unit_1 = tk.Menubutton(root,text='This is the first menu button',bg='white',activebackground='#2E64FE',activeforeground='#FFFFFF')
menu_1 = tk.Menu(unit_1)
unit_1.config(menu=menu_1)
menu_1.add_command(label='Inches', command=lambda: disable_pounds())
menu_1.add_command(label='Kilograms', command=lambda: disable_centimeters())
unit_2 = tk.Menubutton(root,text='This is the second menu button',bg='white',activebackground='#2E64FE',activeforeground='#FFFFFF')
menu_2 = tk.Menu(unit_2)
unit_2.config(menu=menu_2)
menu_2.add_command(label='Centimeters')
menu_2.add_command(label='Pounds',state= my_var.get())
unit_1.place(relx=0.03,rely=0.08,relheight=0.04,relwidth=0.45)
unit_2.place(relx=0.52,rely=0.08,relheight=0.04,relwidth=0.45)
def disable_pounds():
menu_2.entryconfig("Pounds", state="disabled")
menu_2.entryconfig("Centimeters", state="active")
def disable_centimeters():
menu_2.entryconfig("Pounds", state="active")
menu_2.entryconfig("Centimeters", state="disabled")
root.mainloop()
I'm making a revision system for school, and I want it to be able to use a modular amount of subjects just in case a subject is added to the system, so I need a way to make a number of buttons with different subject names, and be able to differentiate between those buttons using tkinter. So for example, if they were to click the Mathematics button, it would take them to another bit of code specially suited for mathematics(Although, it can't be solely for Mathematics, since then I would need definitions for subjects that haven't even been added yet)
First I simply tried setting the command to "print(subjectnames[subcount-1])", thinking it would print the name of the button, but that just printed both names out straight away without even pressing a button. Then I tried changing the variable name subject to the name of the button, which I didn't expect to work, I was just stumped and desperate
Here I started setting up the definition
def chooseQuiz():
clearWindow()
subjectnames=[]
button=[]
This was probably unimportant, just labels for the title and spacing
Label(mainWindow, text="Which quizzes would you like to take?", bg='purple3', font=('constantia',25,"bold")).grid(row=0, column=0, padx=100, pady=0)
Label(mainWindow, bg='purple3').grid(row=1, column=0, padx=0, pady=15)
Here I extract data from an SQL table to get all subject names from all topics, again probably unimportant but here is where most of the variables are made
c.execute("SELECT Subject_name FROM topics")
for row in c.fetchall():
if row[0] in subjectnames:
pass
elif row[0] not in subjectnames:
subjectnames.append(row[0])
else:
messagebox.showerror("Error", "subjectnames are not appending")
chooseQuiz()
This is the main part of this question, where I tried to form a fluid number of buttons all with different commands, but to no avail
for subcount in range(len(subjectnames)):
button.append(Button(mainWindow, text=str(subjectnames[subcount-1]), bg='grey', fg='black', font=('cambria',15), width=25, command=(subject==subjectnames[subcount-1])))
button[-1].grid(row=subcount+2,column=0, padx=0, pady=15)
I expected the subject variable to be the same as the button I pressed, but it remained at 0(original value). I think this is due to wrong use of the command function in tkinter on my part. The buttons still showed up fine(only 2 subjects currently, Mathematics and Physics, and both showed up fine).
Yes, it is possible.
The following example creates a window with a reset button; upon clicking reset, a frame is populated with buttons corresponding to a random number of buttons chosen randomly from possible subjects. Each button has a command that calls a display function that redirects the call to the proper topic, which, in turn prints the name of its topic to the console, for simplicity of the example. You could easily create functions/classes corresponding to each topic, to encapsulate more sophisticated behavior.
Adding subjects, is as simple as adding a key-value pair in SUBJECTS
Pressing reset again, removes the current button and replaces them with a new set chosen randomly.
import random
import tkinter as tk
from _tkinter import TclError
SUBJECTS = {'Maths': lambda:print('Maths'),
'Physics': lambda:print('Physics'),
'Chemistry': lambda:print('Chemistry'),
'Biology': lambda:print('Biology'),
'Astronomy': lambda:print('Astronomy'),
'Petrology': lambda:print('Petrology'),}
topics = []
def topic_not_implemented():
print('this topic does not exist')
def get_topics():
"""randomly creates a list of topics for this example
"""
global topics
topics = []
for _ in range(random.randrange(1, len(SUBJECTS))):
topics.append(random.choice(list(SUBJECTS.keys())))
return topics
def reset_topics():
global topics_frame
try:
for widget in topics_frame.winfo_children():
widget.destroy()
topics_frame.forget()
topics_frame.destroy()
except UnboundLocalError:
print('error')
finally:
topics_frame = tk.Frame(root)
topics_frame.pack()
for topic in get_topics():
tk.Button(topics_frame, text=topic, command=lambda topic=topic: display(topic)).pack()
def display(topic):
"""redirects the call to the proper topic
"""
SUBJECTS.get(topic, topic_not_implemented)()
root = tk.Tk()
reset = tk.Button(root, text='reset', command=reset_topics)
reset.pack()
topics_frame = tk.Frame(root)
topics_frame.pack()
root.mainloop()
I am writing a program in Python 3.6 using Tkinter where a customer has multiple(11) entry fields. I want these entry fields to only accept integers and also be able to define the maximum amount of characters.
I already have a function that does this. But this function only works for one entry field. I have tried entering variables with calling the function so it changes another entry field for example. I was not able to do this.
This is the function I have that works with 1 entry field.
def limitMuntgeld(self, *args):
value = self.invoerM.get()
if len(value) > 5:
self.invoerM.set(value[:5])
if value.lower() in "abcdefghijklmnopqrstuvwxyz-=[];/":
self.invoerM.set(value[:0])
This is the example entry field code that works with the function
self.invoerMuntgeld = Entry(self, font=('Arial', 14), textvariable=self.invoerM)
This is combined with a trace on the entry field posted below.
self.invoerM = StringVar()
self.invoerM.trace('w', self.limitMuntgeld)
I have also tried it with vcmd and validatecommand. However, no good results.
My endresult would be one function working with all entry fields. If anyone has any suggestions, I am all ears!
The proper way to do entry validation is with the validatecommand option rather than using trace. With the validation feature built into the widget you don't need a reference to the widget itself (though you can use it if you want).
When the validatecommand is run, you can have it pass in what the new value will be if the input is valid. You only need to check this value and then return True or False, without having to know which widget it applies to.
For example:
import tkinter as tk
def validate_input(new_value):
valid = new_value .isdigit() and len(new_value) <= 5
return valid
root = tk.Tk()
validate = root.register(validate_input)
for i in range(10):
entry = tk.Entry(root, validate="key", validatecommand=(validate, "%P"))
entry.pack(side="top", fill="x")
root.mainloop()
For information about what %P represents, and what else can be used as arguments to the command, see this question: Interactively validating Entry widget content in tkinter
{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()