I tried to create a multiple checkboxes and get the information whether they are checked or not. I tried to use tkinter for this purpose. The number of checkboxes would be variable. Up to now, I found a way to create the checkboxes with the following code. With this, 10 checkboxes are created at which people can tick any of them
class Example(tk.Frame):
def __init__(self, root, *args, **kwargs):
tk.Frame.__init__(self, root, *args, **kwargs)
self.root = root
self.vsb = tk.Scrollbar(self, orient="vertical")
self.text = tk.Text(self, width=40, height=20,
yscrollcommand=self.vsb.set)
self.vsb.config(command=self.text.yview)
self.vsb.pack(side="right", fill="y")
self.text.pack(side="left", fill="both", expand=True)
n=10
for i in range(n):
cb = tk.Checkbutton(self, text="Modul %s" % i)
self.text.window_create("end", window=cb)
self.text.insert("end", "\n")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()
However, the information are not saved in any variable. If I add variable to be dumped in the cb, the code will check every checkboxes. The edited code section is as follow (sorry that I couldn't highlight the addition):
class Example(tk.Frame):
def __init__(self, root, *args, **kwargs):
tk.Frame.__init__(self, root, *args, **kwargs)
self.root = root
self.vsb = tk.Scrollbar(self, orient="vertical")
self.text = tk.Text(self, width=40, height=20,
yscrollcommand=self.vsb.set)
self.vsb.config(command=self.text.yview)
self.vsb.pack(side="right", fill="y")
self.text.pack(side="left", fill="both", expand=True)
n=10
var1 = IntVar()
val =[]
for i in range(n):
cb = tk.Checkbutton(self, text="Modul %s" % i, variable=var1)
self.text.window_create("end", window=cb)
self.text.insert("end", "\n") # to force one checkbox per line
val.append(var1.get())
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()
Could you help what can I add to the code in order to be able to get the checked Modules by the user? for instance I will get in "val" variable a list with [1, 0, 1, 0, 0, 0, 1, 0, 0, 0] if somebody tick module 0, 2, and 6
I look forward for your feedback.
Are you looking for something like this?
import tkinter as tk
def done():
result = []
for var in variables:
result.append(var.get())
print(result)
root = tk.Tk()
variables = []
check_buttons = []
for i in range(10):
var = tk.IntVar(root)
check_button = tk.Checkbutton(root, text="Modul %i" % i, variable=var)
check_button.pack()
variables.append(var)
check_buttons.append(check_button)
done_button = tk.Button(root, text="Done", command=done)
done_button.pack()
root.mainloop()
I created 10 variables in a list called variables and connected them to each Checkbutton
This is quite simple, the mistake is you are creating one IntVar for all the checkbuttons, what you should be having is different IntVar for different buttons. Then what I want to add is, instead of val being the values from IntVar let it be the IntVar themselves, so later it can be reused. Here I am using bind to print the list, you can create a button and add command option to it.
self.val = [] # self.val if you want to access it outside __init__, or just val
for i in range(n):
var1 = IntVar()
cb = tk.Checkbutton(self, text="Modul %s" % i, variable=var1)
self.text.window_create("end", window=cb)
self.text.insert("end", "\n") # to force one checkbox per line
self.val.append(var1)
self.text.bind('<Return>',lambda e:print([x.get() for x in self.val])) # Dummy function to just print a new list
What is [x.get() for x in self.val] ? It is simple List Comprehension which means:
temp = [] # Any empty list
for x in self.val:
temp.append(x.get())
Here is another way of doing (tho probably other answers will better suit You depending on what You know):
from tkinter import Tk, Checkbutton, IntVar
from functools import partial
def func(n):
print(f'Selected {n + 1}') if variables[n].get() == 1 else print(f'Deselected {n + 1}')
variables = []
root = Tk()
for i in range(10):
variables.append(IntVar())
Checkbutton(root, text=f'Option {i + 1}', variable=variables[i], command=partial(func, i)).pack()
root.mainloop()
(By suggestion #CoolCloud You can replace the shown command with this: command=lambda i=i: func(i), that should also work, also then You don't have to import functools)
However this is a pretty compact setup.
Explanation:
First there is an empty list, where all the Checkbox variables will be stored.
Then in range 10 which means that 10 Checkboxes will be created we do the following:
First we append a variable to the list that will get assigned to each Checkbutton
Then it creates a Checkbutton and adds to it the variable by using indexing and immediately packs it.
About that command:
The partial basically makes it so that the function given in its argument will always get executed with that variable. More about that here
func() function:
print number of option when selected, otherwise print the number when deselected (adding the correct 'selected: no' and 'deselected: no'). It can also be written as:
if all_checkboxes[n][0].get() == 1:
print(f'Selected {n + 1}')
else:
print(f'Deselected {n + 1}')
Hope this helps, if You have any questions ask them.
Source for information about Checkbox and its attributes and arguments.
Related
I am popping up a custom dialog box using Tkinter.
I am opening it from another Tkinter window.
root = Tk()
class ListDialog:
def __init__(self, names, prompt):
self.names = names
self.sub_root = Tk()
self.sub_root.title("Intovex")
self.sub_root.iconbitmap("Icon.ico")
self.myfont = Font(root=self.sub_root, family="Arial", size=8)
self.sub_root.maxsize(320, 240)
self.sub_root.wm_attributes("-topmost", True)
self.sub_root.wm_attributes("-toolwindow", True)
self.var = IntVar()
label = Label(self.sub_root, text=prompt)
label.pack(fill=X)
c=1
print(names)
for i in names:
print(i)
r = Radiobutton(self.sub_root, text=i, variable=self.var, value=c, command=self.end)
r.pack(anchor=W)
c+=1
self.var.set(1)
button = Button(self.sub_root, command=self.endit, text="OK", bg = "#448DE0", fg="White", bd=0, width=12, pady=4, padx=4, height=1,font=self.myfont, highlightcolor="#A3C7F0")
button.pack(side=BOTTOM)
self.choice = names[0]
def end(self):
ch = self.var.get()
print(str(ch))
self.choice = self.names[ch - 1]
def endit(self):
self.sub_root.destroy()
def ask(self):
self.sub_root.mainloop()
var2 = StringVar()
def get_choice():
list = ListDialog(["Test1", "Test2"], "Testing")
list.ask()
var2.set(str(list.choice))
label = Label(root, text="", textvariable=var2)
button = Button(root, text="Test", command=get_choice)
label.pack()
button.pack()
root.mainloop()
However, it works when it is run alone by directly instantiating the class and invoking ask() method.
You may have seen that I have print statements everywhere in the code(it is for debugging) and I found where isn't it working
The print statement to print the names list parameter is printing the whole list correctly.
Inside the for-loop also it prints the elements in the names list correctly
When I click on a radio button it invokes the end() method. There it always prints the default value.
root = Tk()
class ListDialog:
def __init__(self, names, prompt):
self.names = names
self.sub_root = Tk()
self.sub_root.title("Intovex")
self.sub_root.iconbitmap("Icon.ico")
self.myfont = Font(root=self.sub_root, family="Arial", size=8)
self.sub_root.maxsize(320, 240)
self.sub_root.wm_attributes("-topmost", True)
self.sub_root.wm_attributes("-toolwindow", True)
self.var = IntVar()
label = Label(self.sub_root, text=prompt)
label.pack(fill=X)
c=1
print(names)
for i in names:
print(i)
r = Radiobutton(self.sub_root, text=i, variable=self.var, value=c, command=self.end)
r.pack(anchor=W)
c+=1
self.var.set(1)
button = Button(self.sub_root, command=self.endit, text="OK", bg = "#448DE0", fg="White", bd=0, width=12, pady=4, padx=4, height=1,font=self.myfont, highlightcolor="#A3C7F0")
button.pack(side=BOTTOM)
self.choice = names[0]
def end(self):
ch = self.var.get()
print(str(ch))
self.choice = self.names[ch - 1]
def endit(self):
self.sub_root.destroy()
def ask(self):
self.sub_root.mainloop()
list = ListDialog(["Test1", "Test2"], "Testing")
list.ask()
print(list.choice)
But it works if I open it as a TopLevel widget. But then the main window doesn't wait till the popup windows returns the value(choice).
The problem with the code in the first snippet is because you're calling Tk() more that once within the tkinter application — it confuses the interface code and can cause a variety of problems, as you're finding out.
If you replace the call inside the __init__() method of the ListDialog class, with one to tk.Toplevel() instead, then your code will start working.
I also streamlined the for loop that creates the Radiobuttons by changing it so to use the built-in enumerate() function to automatically keep a count of the names. In conjunction with that, I made the initial value of the IntVar zero which is not one of the values that selecting one the radiobuttons will assign it. This is so when the ListDialog is first displayed, none of them will be selected. It also ensures that the end() callback function will get called whenever the user presses one of them, so your app will always be informed when that happens.
Note that, although I didn't change it, you shouldn't name a variable list because that hides the name of the built-in class by that name. In general you should avoid naming anything something that conflicts with an existing standard Python name.
from tkinter import *
from tkinter.font import Font
root = Tk()
class ListDialog:
def __init__(self, names, prompt):
self.names = names
# self.sub_root = Tk() # Wrong - don't call Tk() more than once.
root.withdraw() # Hide root window.
self.sub_root = Toplevel() # Create another top-level window.
self.sub_root.title("Intovex")
# self.sub_root.iconbitmap("Icon.ico") # I don't have this file...
self.myfont = Font(root=self.sub_root, family="Arial", size=8)
self.sub_root.maxsize(320, 240)
self.sub_root.wm_attributes("-topmost", True)
self.sub_root.wm_attributes("-toolwindow", True)
self.var = IntVar(value=0) # Define and init value to one *not* produced by btns.
label = Label(self.sub_root, text=prompt)
label.pack(fill=X)
print(names)
for c, name in enumerate(names, start=1):
print(c)
r = Radiobutton(self.sub_root, text=c, variable=self.var, value=c,
command=self.end)
r.pack(anchor=W)
button = Button(self.sub_root, command=self.endit, text="OK", bg = "#448DE0",
fg="White", bd=0, width=12, pady=4, padx=4, height=1,
font=self.myfont, highlightcolor="#A3C7F0")
button.pack(side=BOTTOM)
self.choice = names[0]
def end(self):
ch = self.var.get()
print(str(ch))
self.choice = self.names[ch - 1]
def endit(self):
self.sub_root.destroy()
root.deiconify() # Reshow root window.
def ask(self):
self.sub_root.mainloop()
var2 = StringVar()
def get_choice():
list = ListDialog(["Test1", "Test2"], "Testing")
list.ask()
var2.set(str(list.choice))
label = Label(root, text="", textvariable=var2)
button = Button(root, text="Test", command=get_choice)
label.pack()
button.pack()
root.mainloop()
I'm creating a GUI where I need to create certain number of entries and buttons in Tkinter. I'd like to create all these in a for loop. As actions, when I press any of the button, it should transfer the value of the Entry to the callback of the button next to it.
This is what I've done so far but it's not working yet.
n=0
self.button = []
self.entFreq = []
for calVal in calibration:
lbl = Label(self.calFrame)
lbl.configure(text = "Set amplitud to " + calVal)
lbl.configure(background=self.bg_App, fg = "white")
lbl.grid(row=n, column=0)
self.entFreq.append(Entry(self.calFrame, width=10))
self.entFreq[n].grid(row=n, column=1, padx = 10)
#Construction Button send frequency
self.button.append(Button(self.calFrame, text="Cal", borderwidth=0, relief="groove", command = lambda n=self.entFreq[n].get(): self.get_val(n)))
self.button[n].configure(bg="#FFF3E0")
self.button[n].grid(row=n, column=2)
n+=1
def get_val(self, var):
print "Got this:", str(var)
I'm just getting blank in the var function. How to link those two?
You're putting too much code into your lambdas. You only need to pass in n, and get_val can do the rest of the work:
self.button.append(Button(..., command=lambda n=n: self.get_val(n)))
...
def get_val(self, n):
value = self.entFreq[n].get()
print "Got this:", value
You might want to consider defining a class for this set of label, entry and button, since they are designed to work together and you're making several sets.
You could, for example,pass in a label and a function to call when the user clicks the button. For example:
class LabelEntry(object):
def __init__(self, parent, text, command):
self.command = command
self.label = Label(parent, text=text)
self.entry = Entry(parent)
self.button = Button(parent, text="Cal", command=self.call_command)
def call_command(self):
value = self.entry.get()
self.command(value)
You would use it something like this:
def some_function(self, value):
print "the value is", value
...
for calVal in calibration:
le = LabelEntry(frame,
text="Set aplitud to " + calVal,
command=self.some_function)
le.label.grid(...)
le.entry.grid(...)
le.button.grid(...)
I am trying to assign values to buttons which return their value when clicked (more precisely, they print it.) The only caveat is that the buttons are created dynamically, using a for loop.
How can I assign id's (and other variables) to buttons which have been created with a for loop?
Example Code:
#Example program to illustrate my issue with dynamic buttons.
from Tkinter import *
class my_app(Frame):
"""Basic Frame"""
def __init__(self, master):
"""Init the Frame"""
Frame.__init__(self,master)
self.grid()
self.Create_Widgets()
def Create_Widgets(self):
for i in range(1, 11): #Start creating buttons
self.button_id = i #This is meant to be the ID. How can I "attach" or "bind" it to the button?
print self.button_id
self.newmessage = Button(self, #I want to bind the self.button_id to each button, so that it prints its number when clicked.
text = "Button ID: %d" % (self.button_id),
anchor = W, command = lambda: self.access(self.button_id))#Run the method
#Placing
self.newmessage.config(height = 3, width = 100)
self.newmessage.grid(column = 0, row = i, sticky = NW)
def access(self, b_id): #This is one of the areas where I need help. I want this to return the number of the button clicked.
self.b_id = b_id
print self.b_id #Print Button ID
#Root Stuff
root = Tk()
root.title("Tkinter Dynamics")
root.geometry("500x500")
app = my_app(root)
root.mainloop()
The problem is that you are using the last value of self.button_id when you call the command once the buttons are created. You have to bind the current value of the local variable for each lambda with lambda i=i: do_something_with(i):
def Create_Widgets(self):
for i in range(1, 11):
self.newmessage = Button(self, text= "Button ID: %d" % i, anchor=W,
command = lambda i=i: self.access(i))
self.newmessage.config(height = 3, width = 100)
self.newmessage.grid(column = 0, row = i, sticky = NW)
In the app I'm working on I have a class setup to handle all my GUI. One method in this class creates a frame and populates a group of radiobutton's from a loop. This method will be called multiple times from outside the class to redraw this frame. The problem I have is that when the frame is redrawn it is actually just overwriting the existing frame (old frame persists). So if there are fewer options in a later call, the earlier options are still visible. I have tried doing a grid_remove on the frame first but can't get that to work. So my question is, why is the grid_remove() not working in the initMech() method below?
#!/usr/bin/env python
from Tkinter import *
class MWindow(Frame):
def __init__(self, parent) :
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
topframe = Frame(self.parent, bd=2, relief=GROOVE, padx=5, pady=5, width=300, height=50)
topframe.grid(row=0, column=0)
desc = Button(topframe, text="list1", command=lambda:set_list(1))
desc.grid(row=0,column=1)
desc2 = Button(topframe, text="list2", command=lambda:set_list(2))
desc2.grid(row=0,column=2)
def initMech(self):
try:
radio_frame.grid_remove()
except:
print "can't remove"
radio_frame = Frame(self.parent, bd=2, relief=GROOVE, padx=5, pady=5, width=300, height=50)
radio_frame.grid(row=1, column=0)
variant=StringVar()
c = 0
for x in chas.v_list:
Radiobutton(radio_frame, text=x,variable=variant, value=x, command=lambda x = x:chas.set_vari(x)).grid(row=0, column=c)
c = c+1
def onExit(self):
self.parent.destroy()
class Mech():
def set_chass(self,chass):
try:
if self.chassis == chass:
pass
else:
self.chassis = chass
del self.vari
except AttributeError:
self.chassis = chass
def load_vari(self):
if self.chassis == 1:
self.v_list = ["a","b","c"]
else:
self.v_list = ["w", "x", "y", "z"]
win.initMech()
def set_vari(self, vari):
self.vari = vari
def set_list(num):
chas.set_chass(num)
chas.load_vari()
root = Tk()
win = MWindow(root)
chas = Mech()
root.mainloop()
It is not working because you're using a local variable named radio_frame, and that variable isn't defined yet. Most likely, the error message you are ignoring is telling you exactly that. Why are you trying to remove a frame that you haven't yet created?
Also, are you aware that grid_remove only removes the widget from view, it doesn't destroy it? Thus, if you call this function several times you'll have several invisible versions of this frame, all using up memory.
This is the same problem I posed earlier today and which a couple of you tried to help me with, but I can't get it to work. All I want to do is to populate "ichose" with the multiple selections I make when I click on the listbox.
import Tkinter as tk
from Tkinter import *
global ichose
class App(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self,master)
self.master=master
self.grid()
self.ichose = ()
self.l = Listbox(self, height=10, selectmode=EXTENDED)
# Selectmode can be SINGLE, BROWSE, MULTIPLE or EXTENDED. Default BROWSE
self.l.grid(column=0, row=0, sticky=(N,W,E,S))
self.l.bind("Double-Button-1", self.entered)
s = Scrollbar(self, orient=VERTICAL, command=self.l.yview)
s.grid(column=0, row=0, sticky=(N,S,E))
self.l['yscrollcommand'] = s.set
for i in range(1,101):
self.l.insert('end', 'Line %d of 100' % i)
def entered(self, event):
self.ichose = self.selection_get()
self.ichose = ('hello')
root=tk.Tk()
root.title('Listbox Problem')
root.geometry('200x200')
app=App(root)
root.mainloop()
print app.ichose
Whatever I do, "ichose" comes out as an empty tuple ().
It's clear that the function "entered" is never called because I never see the test string 'hello'.
I also don't know what the various options are as in "Double-Button-", "<>" etc. Where can I find a list and explanation of each one?
If somebody could please just modify my program so the "print ichose" works, I'd be really grateful. You can see from my program that I don't really know what I'm doing but am keen to learn. Thank you.
I've finally found the answer to my own question. This is REALLY useful if you want to capture multiple responses from a listbox. I've commented a lot. Hope it helps!
import Tkinter as tk
from Tkinter import *
class App(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self,master)
self.master=master
self.grid()
self.ichose = []
self.l = Listbox(self, height=10, selectmode=MULTIPLE)
# Selectmode can be SINGLE, BROWSE, MULTIPLE or EXTENDED. Default BROWSE
self.l.grid(column=0, row=0, sticky=(N,W,E,S))
s = Scrollbar(self, orient=VERTICAL, command=self.l.yview)
s.grid(column=0, row=0, sticky=(N,S,E))
self.l['yscrollcommand'] = s.set
for i in range(1,101):
self.l.insert('end', 'Line %d of 100' % i)
# Create Textbox that will display selected items from list
self.selected_list = Text(self,width=20,height=10,wrap=WORD)
self.selected_list.grid(row=12, column=0, sticky=W)
# Now execute the poll() function to capture selected list items
self.ichose = self.poll()
def poll(self):
items =[]
self.ichose = []
# Set up an automatically recurring event that repeats after 200 millisecs
self.selected_list.after(200, self.poll)
# curselection retrieves the selected items as a tuple of strings. These
# strings are the list indexes ('0' to whatever) of the items selected.
# map applies the function specified in the 1st parameter to every item
# from the 2nd parameter and returns a list of the results. So "items"
# is now a list of integers
items = map(int,self.l.curselection())
# For however many values there are in "items":
for i in range(len(items)):
# Use each number as an index and get from the listbox the actual
# text strings corresponding to each index, and append each to
# the list "ichose".
self.ichose.append(self.l.get(items[i]))
# Write ichose to the textbox to display it.
self.update_list()
return self.ichose
def update_list(self):
self.selected_list.delete(0.0, END)
self.selected_list.insert(0.0, self.ichose)
root=tk.Tk()
root.title('Listbox Multi-Capture')
root.geometry('200x340')
app=App(root)
root.mainloop()
print app.ichose
# ----------------[ Listbox EXAMPLE ]----------------
self.sarcCountries = (
"Bangladesh",
"India",
"Pakistan",
"Nepal",
"Bhutan",
"Sri Lanka",
"Afghanistan"
)
self.listData = StringVar(value = self.sarcCountries)
self.listbox = Listbox(
master = self,
height = 10,
listvariable = self.listData,
selectmode = MULTIPLE,
selectbackground = "#BC80CC"
)
self.listbox.bind("<<ListboxSelect>>", self.OnListboxSelectionChanged)
self.listbox.pack(fill = BOTH, expand = 0, padx = 10, pady = 10)
# ----------------[ Listbox EXAMPLE ]----------------
def OnListboxSelectionChanged(self, val):
# NOTE: If your listbox's select mode is MULTIPLE, then you may use this portion of code
selections = val.widget.curselection()
print("---------------------------")
if (selections != ()):
for index in selections:
print(self.sarcCountries[int(index)])
print("---------------------------")