i'm trying to create a 2D grid of radiobuttons with default values. The grid may be as have as many as 256 rows but always 3 columns. I have researched much and tried many options and this is the best code I have for now. Obviously I am new to Python and Tkinter so any additional suggestions for solving my dilema (and code writing) are greatly appreciated. Also keep in mind that I will need to read back each row of radio buttons to determine which one was checked.
import Tkinter as tk
from Tkinter import *
class GUI(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.pack()
self.initUI()
def initUI(self):
# the 5Hz radio button should be selected as default
self.freqs=['1Hz','5Hz','10Hz']
self.numFreqs = len(self.freqs)
#val and var are set up to try to establish the defaults
#every 3 vals = 1 var therfore the middle val should = var
# val 0,1,2 = var 1 , val 3,4,5 = var 4 , val 6,7,8 = var 7
# this should allow the 5hx radiobuttonto be default selected
self.val = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14]
self.var=[1,4,7,10,13]
self.var[0] = IntVar()
self.var[1] = IntVar()
self.var[2] = IntVar()
self.var[3] = IntVar()
self.var[4] = IntVar()
self.cButtons = []
self.rButtons1 = []
self.rButtons2 = []
self.rButtons3 = []
self.textLbls = ['Choice 1', 'Choice 2', 'Choice 3','Choice 4','Choice 5']
#build enough values for 256 rows of 3 radiobuttons
#For var = [1,4,7,10... val = [0,1,2, 3,4,5, 6,7,8... so vars match vals on the 5hz radiobutton
#Can't do this since it creates the'many already selected bug'
#See http://stackoverflow.com/questions/5071559/tkinter-radio-button-initialization-bug
#for i in range(len(self.var)):
#self.var[i] = IntVar()
for i in range(len(self.textLbls)):
temp = self.val[(self.numFreqs*i)+1]
temp1 = self.var[i]
self.cButtons.append(Checkbutton(self, text=self.textLbls[i], onvalue=1, offvalue=0))
self.cButtons[i].grid(row=i, column=2, sticky = W)
Label(self, text=' Frequency:').grid(row=i, column=6)
#variable connects group of radios for slection/clear of group
#if value = variable this is default
self.rButtons1.append(Radiobutton(self, text=self.freqs[0], variable=self.var[i], value=self.val[(self.numFreqs*i)+0]))
self.rButtons1[i].grid(row=i, column=7, padx=5)
self.rButtons2.append(Radiobutton(self, text=self.freqs[1], variable=self.var[i], value=self.val[(self.numFreqs*i)+1]))
self.rButtons2[i].grid(row=i, column=8, padx=5)
self.rButtons3.append(Radiobutton(self, text=self.freqs[2], variable=self.var[i], value=self.val[(self.numFreqs*i)+2]))
self.rButtons3[i].grid(row=i, column=9, padx=5)
def main():
root = Tk()
app = GUI(root)
root.mainloop()
if __name__ == '__main__':
main()
Not entirely sure what problem you are looking for help with on this code, but I made a couple quick changes that should make it a bit more functional:
import tkinter as tk
from tkinter import *
class GUI(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.pack()
self.initUI()
def initUI(self):
# the 5Hz radio button should be selected as default
self.freqs=['1Hz','5Hz','10Hz']
self.numFreqs = len(self.freqs)
#val and var are set up to try to establish the defaults
#every 3 vals = 1 var therfore the middle val should = var
# val 0,1,2 = var 1 , val 3,4,5 = var 4 , val 6,7,8 = var 7
# this should allow the 5hz radiobutton to be default selected
self.val = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14]
#You only really need to hold on to a reference to the IntVars, not the
# entire widgets. Declaring each "var" individually is also unneccesary
self.checkButtonVars = []
self.radioButtonsVars = []
self.textLbls = ['Choice 1', 'Choice 2', 'Choice 3','Choice 4','Choice 5']
#(your comment block)
valueIter = iter(self.val) #This will let us read the values 3 by 3
for i,lbl in enumerate(self.textLbls): #enumerate is a nice way to avoid range(len(x))
tempVals = [next(valueIter) for x in range(3)] #get the next 3 values
#make a variable for the new CheckBox (and append it to our list)
self.checkButtonVars.append(IntVar(value=0))
#make a variable for the new RadioButton group (and append it to our list)
self.radioButtonsVars.append(IntVar(value=tempVals[1]))
#Make the checkboxes, radiobuttons and label for the line.
Checkbutton(self, text=lbl, variable=self.checkButtonVars[i]).grid(row=i, column=2, sticky = W)
Label(self, text=' Frequency:').grid(row=i, column=6)
Radiobutton(self, text=self.freqs[0], variable=self.radioButtonsVars[i], value=tempVals[0]).grid(row=i, column=7, padx=5)
Radiobutton(self, text=self.freqs[1], variable=self.radioButtonsVars[i], value=tempVals[1]).grid(row=i, column=8, padx=5)
Radiobutton(self, text=self.freqs[2], variable=self.radioButtonsVars[i], value=tempVals[2]).grid(row=i, column=9, padx=5)
#I added a button to retrieve the values, this is just as a demo.
self.printButton = Button(self,text='Return values',command=self.printVals)
self.printButton.grid(row=i+1,column=8)
def getRadioVals(self,*args):
#returns the values in the radio buttons as a list
return [x.get() for x in self.radioButtonsVars]
def getCheckVals(self,*args):
#returns the values in the checkboxes as a list
return [x.get() for x in self.checkButtonVars]
def printVals(self,*args):
print('Radio Buttons: %s' %(self.getRadioVals()))
print('Check Buttons: %s' %(self.getCheckVals()))
def main():
root = Tk()
app = GUI(root)
root.mainloop()
if __name__ == '__main__':
main()
If you could give a better description of specific problems you are having, it might be possible to address them.
Also, just as a note: trying to display 256 radiobuttons vertically is likely to cause you some problems. As far as I know, there is no way to create a scrolled Frame in standard tkinter, so your window will end up much taller than your screen.
Related
i am working on a GUI in Tkinter, and i have encountered a wierd issue that i have been struggling to solve.
The goal is to remove and add users from the interface. When a user is added, it is displayed, and when deleted the user should disappear from the GUI.
This is where my issue begins. When only adding and removing one user, everything works fine.
However when two or more users are added, the removal process fails. The removal works as expected on the first try, but when trying to remove any more, the interface does not update. The users get removed from the list as should, but the GUI never updates. Here is my code
#anden slet klik virker ikke
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.entries = -1
self.people = []
self.names = []
self.id = []
self.tlf = []
self.frames = []
self.master = master
self.pack()
self.create_widgets()
#Create the first two rows of the application
def create_widgets(self):
# Row 0: Input boxes
self.inputName = tk.Entry(self, bd=4)
self.inputName.grid(row=0, column=0)
self.inputID = tk.Entry(self, bd=4)
self.inputID.grid(row=0, column=1)
self.inputTLF = tk.Entry(self, bd=4)
self.inputTLF.grid(row=0, column=2)
# Row 0: "Add" button
self.addButton = tk.Button(self, text="Add", command=self.AddMember)
self.addButton.grid(row=0, column=3)
# Row 1: Labels
tk.Label(self, text = "Navn", borderwidth = 4).grid(row=1, column=0)
tk.Label(self, text="ID", borderwidth=4).grid(row=1, column=1)
tk.Label(self, text="Tlf", borderwidth=4).grid(row=1, column=2, ipadx=30)
tk.Label(self, text=" ", borderwidth=4).grid(row=1, column=3)
# What the "add" button does
def AddMember(self):
self.people.append([self.inputName.get(), self.inputID.get(), self.inputTLF.get()]) #Add textbox-text to list
self.entries += 1
self.updateMembers()
def updateMembers(self): # Display new member
# This is declared to make sure that self.entries is assigned by value, and not by index
entry = self.entries
# Add the new name from 'people' to the list of name entries, and display
self.names.append(tk.Label(self, text=self.people[entry][0], borderwidth=4))
self.names[entry].grid(row=entry + 2, column=0)
# -//- but with ids
self.id.append(tk.Label(self, text=self.people[entry][1], borderwidth=4))
self.id[entry].grid(row=entry + 2, column=1)
# -//- but with phone numbers
self.tlf.append(tk.Label(self, text=self.people[entry][2], borderwidth=4))
self.tlf[entry].grid(row=entry + 2, column=2)
# Create a frame to but multiple buttons in one grid-cell
self.frames.append(tk.Frame(self))
self.frames[entry].grid(row=entry + 2, column=3)
#Create such buttons
removeButton = tk.Button(self.frames[entry], text="X", command=lambda: self.remove(entry))
msgButton = tk.Button(self.frames[entry], text="SMS", command=lambda: self.sendSMS(entry))
callButton = tk.Button(self.frames[entry], text="Ring", command=lambda: self.makeCall(entry))
#Display such buttons
removeButton.pack(side='top')
callButton.pack(side = 'right')
msgButton.pack(side='left')
def sendSMS(self, sender_id):
print("SMSMSMSM")
def makeCall(self, sender_id):
print("RINGRINGRING")
def remove(self, sender_id):
print("")
print(self.entries)
self.people.pop(sender_id) # Remove from the "People" list
if self.entries >= 0:
# Un-display the lowest entry
self.tlf[self.entries].destroy()
self.frames[self.entries].destroy()
self.id[self.entries].destroy()
self.names[self.entries].destroy()
for i in range(self.entries): # RE-display all current entries (deleted one excluded)
tk.Label(self, text=self.people[i][0], borderwidth=4).grid(row=i + 2, column=0)
tk.Label(self, text=self.people[i][1], borderwidth=4).grid(row=i + 2, column=1)
tk.Label(self, text=self.people[i][2], borderwidth=4).grid(row=i + 2, column=2)
# Remove deleted user's info in the display lists.
self.names.pop(sender_id)
self.id.pop(sender_id)
self.tlf.pop(sender_id)
self.frames.pop(sender_id)
self.entries -= 1 # Decrement size of people
print(self.entries)
#Actually start the program
root = tk.Tk()
app = Application(master=root)
app.mainloop()
I have been troubleshooting this for hours on end, and have not managed to solve this, so any help is appreciated :D
thanks a lot for your time. I'm currently stuck at the following point: I have developed a GUI with Tkinter with about 200 entries. (For simplification I have only included a small section below). But these 200 entries are seldom filled in at once. Normally 50 entries are filled in every start of the program. When the program is closed, these filled in values are deleted and have to be filled in again after the program is started again. Is there a way to prevent this?
I do not want to lose the values entered in jobNameA_entry and jobNameB_entry when closing the program.
Many thanks in any case.
import tkinter as tk
class Win1:
def __init__(self, master):
self.master = master
self.master.title("Gap Assessment")
self.topFrame = tk.Frame(self.master)
self.topFrame.grid(row=0, column=0, sticky='news', ipady = 5)
self.A_GapFrame = tk.Frame(self.master)
self.B_GapFrame = tk.Frame(self.master)
self.subframe_AGap()
self.subframe_BGap()
# Create a Tkinter variable
self.gapType = tk.StringVar(self.master)
# Dictionary with optionsverschwinden
self.choiceGap = ['AFrame','BFrame']
# self.choiceGap = sorted(self.choiceGap)
self.gapType.set('') # set the default option
self.ctngMenu = tk.OptionMenu(self.topFrame, self.gapType, *self.choiceGap, command=self.chioseGap_handle)
self.ctngMenu.grid(row = 1, column =2)
def chioseGap_handle(self, selected):
if selected == 'AFrame':
self.A_GapFrame.tkraise()
# self.subframe_AGap()
self.A_GapFrame.place(x=20, y=30, width = 210)
self.B_GapFrame.place_forget()
if selected == 'BFrame':
self.B_GapFrame.tkraise()
# self.subframe_BGap()
self.B_GapFrame.place(x=30, y=70, width = 210)
self.A_GapFrame.place_forget()
def subframe_AGap(self):
self.jobNameA_text = tk.StringVar()
self.jobNameA_entry = tk.Entry(self.A_GapFrame, textvariable = self.jobNameA_text)
self.jobNameA_entry.grid(row=1, column=0, sticky='news')
self.jobNameA_text = tk.StringVar()
self.jobNameA_entry = tk.Entry(self.A_GapFrame, textvariable = self.jobNameA_text)
def subframe_BGap(self):
self.jobNameB_text = tk.StringVar()
self.jobNameB_entry = tk.Entry(self.B_GapFrame, textvariable = self.jobNameB_text)
self.jobNameB_entry.grid(row=2, column=0, sticky='news')
self.jobNameB_text = tk.StringVar()
self.jobNameB_entry = tk.Entry(self.B_GapFrame, textvariable = self.jobNameB_text)
root = tk.Tk()
root.geometry("200x300+50+50")
app = Win1(root)
root.mainloop()
Running python 3.8 in PyCharm
I'm trying to create a class to allow creation of a tkinter checkbutton (and eventually other widgets) that will hold all the formatting and definition for the widget. I would like to be able to test the state of the checkbutton and act on it in a callback. But like many others, my checkbutton variable seems to always be 0. I'm fairly sure its a garbage collection issue, but I can't figure a way around it. Note the second set of parameters in the class definition specify the location of the label to display the checkbutton variable state. At the moment, the callback is just to display the variable state in a label.
Acknowledgement:
I modified code from Burhard Meier's book "Python GUI Programming Cookbook" for this. The code related to the class is mine, the rest is from Burkhard Meier.
'''
This is a modification of code by Burhard A. Meier
in "Python GUI Programming Cookbook"
Created on Apr 30, 2019
#author: Burkhard A. Meier
'''
#======================
# imports
#======================
import tkinter as tk
from tkinter import ttk
# Create instance
win = tk.Tk()
# Add a title
win.title("GUI Test Box")
# Modify adding a Label
a_label = ttk.Label(win, text="Testing TK Widgets")
a_label.grid(column=0, row=0)
# Modified Button Click Function
def click_me():
action.configure(text='Hello ' + name.get() + ' ' +
number_chosen.get())
#Define the Checkbutton Class
class ChkBut(): #lable, xloc, yloc, xlab, ylab
def __init__(self, lable, xloc, yloc, xlab, ylab): # , xloc, yloc, text="default", variable = v, onvalue='Set', offvalue='NotSet'):
v = tk.IntVar()
self.v = v
self.lable = lable
self.xloc = xloc
self.yloc = yloc
self.xlab = xlab
self.ylab = ylab
c = tk.Checkbutton(
win,
text= self.lable,
variable=self.v,
command=self.cb(self.v, xlab,ylab))
c.grid(column=xloc, row=yloc, sticky = tk.W)
c.deselect()
def cb (self, c, xlab, ylab):
if c.get()==1:
c_label = ttk.Label(win, text="Set")
c_label.grid(column=xlab, row=ylab)
elif c.get()==0:
c_label = ttk.Label(win, text="NotSet")
c_label.grid(column=xlab, row=ylab)
else:
c_label = ttk.Label(win, text=c.v.get())
c_label.grid(column=xlab, row=ylab)
# Changing the Label
ttk.Label(win, text="Enter a name:").grid(column=0, row=0)
# Adding a Textbox Entry widget
name = tk.StringVar()
name_entered = ttk.Entry(win, width=12, textvariable=name)
name_entered.grid(column=0, row=1)
# Adding a Button
action = ttk.Button(win, text="Click Me!", command=click_me)
action.grid(column=2, row=1)
# Creating three checkbuttons
ttk.Label(win, text="Choose a number:").grid(column=1, row=0)
number = tk.StringVar()
number_chosen = ttk.Combobox(win, width=12, textvariable=number, state='readonly')
number_chosen['values'] = (1, 2, 4, 42, 100)
number_chosen.grid(column=1, row=1)
number_chosen.current(0)
check1 = ChkBut("Button 1", 0, 4, 0, 5)
chVarUn = tk.IntVar()
check2 = tk.Checkbutton(win, text="UnChecked", variable=chVarUn)
check2.deselect()
check2.chVarUn = 0
check2.grid(column=1, row=4, sticky=tk.W)
chVarEn = tk.IntVar()
check3 = tk.Checkbutton(win, text="Enabled", variable=chVarEn)
check3.select()
check3.chVarEn = 0
check3.grid(column=2, row=4, sticky=tk.W)
name_entered.focus() # Place cursor into name Entry
#======================
# Start GUI
#======================
win.mainloop()
I am using tkinter to create a small GUI for some Python scripts. For this GUI I need a Combobox named combo with three options:
"none" (the user wants to hand in no data)
"constant" (the user wants to hand in only one value)
"adjustable" (the user wants to hand in more than one value)
Depending on the choice done in combo, a different number of entrys should
appear. The first option should show no entry (and delete all "leftover"
entrys), the second one should show only one entry and the third one should show
two entrys. For me it's no problem to create these entrys but I don't know how
to make them disappear. Once they were created they stay until the GUI is
closed.
I tried something like this:
import tkinter as tk
master = tk.Tk()
var1 = tk.StringVar()
var2 = tk.StringVar()
def submit():
if choice.get() == "none": # all entry boxes schould disappear
entry1.destroy()
entry2.destroy()
if choice.get() == "constant": # only 1 entry box schould be visible
entry1 = tk.Entry(master, textvariable = var1)
entry1.grid(column = 0, row = 1)
entry2.destroy()
if choice.get() == "adjustable": # all entry boxes should be visible
entry1 = tk.Entry(master, textvariable = var1)
entry1.grid(column = 0, row = 1)
entry2 = tk.Entry(master, textvariable = var1)
entry2.grid(column = 0, row = 2)
choice = tk.StringVar(value = "none")
combo = ttk.Combobox(master, textvariable = choice, state = "readonly")
combo["values"] = ("none", "constant", "adjustable")
combo.grid(column = 0, row = 0)
action = tk.Button(master, text = "confirm", command = submit)
action.grid(column=1, row=0)
master.mainloop()
But as I said before, once a entry was created it did not disappear anymore. I
also tried entry.grid_forget() insted of entry.destroy() but this also
didn't work.
I also tryed to declare entry1 and entry1 outside of submit() but then I
don't know how to recreate these entrys once they were destroyed.
Thanks to the help of jasonharper I found a solution while working at another script. The code should look something like this:
import tkinter as tk
from tkinter import ttk
master = tk.Tk()
var1 = tk.StringVar()
var2 = tk.StringVar()
def submit():
if choice.get() == "none": # all entry boxes schould disappear
entry1.grid_remove()
entry2.grid_remove()
if choice.get() == "constant": # only 1 entry box schould be visible
entry1.grid(column = 0, row = 1)
entry2.grid_remove()
if choice.get() == "adjustable": # all entry boxes should be visible
entry1.grid(column = 0, row = 1)
entry2.grid(column = 0, row = 2)
choice = tk.StringVar(value = "none")
combo = ttk.Combobox(master, textvariable = choice, state = "readonly")
combo["values"] = ("none", "constant", "adjustable")
combo.grid(column = 0, row = 0)
entry1 = tk.Entry(master, textvariable = var1)
entry2 = tk.Entry(master, textvariable = var2)
action = tk.Button(master, text = "confirm", command = submit)
action.grid(column=1, row=0)
master.mainloop()
Now all entrys are deleted or created when wanted. To delete the text at the entrys You only have to add entry.delete(0,"end").
I hope this will also help others.
I am trying to create a little GUI that takes a various number of inputs from entries. The number of entries should be flexible between zero and 32, and the user can add entries via a button (simplified version of the program).
The problem is that with 32 entries die GUI gets quite long and I'd like to add a scrollbar so to minimize the size of the GUI window and the user can scroll through the entries.
I have developed an example based on code I found on this website but I cannot get the scrollbar to work.
I simplified version of my code is below.
Thanks for your help!
from tkinter import *
class SlowCH_Manager(Canvas):
"""
Manages a variable number of slow channel massages
"""
def __init__(self,master=None,**kwargs):
Canvas.__init__(self,master,**kwargs)
self.frame = Frame(self)
self.create_window(0,0,anchor=N+W,window=self.frame)
self.row = 0
self.widgets = []
self.max = 32
self._init_entries()
def _init_entries(self):
"""
initialize the input area with labels and perhaps default values
"""
label_id = Label(self.frame, text='message ID').grid(row = self.row, column = 1)
label_msg = Label(self.frame, text='message data').grid(row = self.row, column = 2)
self.row += 1
def add_entry(self):
"""
Dynamically add entry to GUI until max number of entries is arrived.
By SENT specification max 32 slow channel messages are allowed.
"""
if len(self.widgets) >= self.max:
print('Im full')
else:
label = Label(self.frame, text=str(len(self.widgets))).grid(row = self.row, column = 0)
entry_id = Entry(self.frame)
entry_id.grid(row = self.row, column = 1)
entry_data = Entry(self.frame)
entry_data.grid(row = self.row, column = 2)
self.row += 1
self.widgets.append(entry_id)
def _ypos(self):
return sum(x.winfo_reqheight() for x in self.widgets)
if __name__ == "__main__":
root = Tk()
manager = SlowCH_Manager(root)
manager.grid(row=0,column=0)
scroll = Scrollbar(root)
scroll.grid(row=0,column=1,sticky=N+S)
manager.config(yscrollcommand = scroll.set)
scroll.config(command=manager.yview)
manager.configure(scrollregion = manager.bbox("all"))
b = Button(root, text = "add entry", command = manager.add_entry)
b.grid(row=1,column=0)
root.mainloop()
You need to update the scrollregion of the canvas each time you add an entry:
if __name__ == "__main__":
root = Tk()
manager = SlowCH_Manager(root)
manager.grid(row=0,column=0)
scroll = Scrollbar(root)
scroll.grid(row=0,column=1,sticky=N+S)
manager.config(yscrollcommand = scroll.set)
scroll.config(command=manager.yview)
manager.configure(scrollregion = manager.bbox("all"))
def command():
manager.add_entry()
# update scrollregion
manager.configure(scrollregion = manager.bbox("all"))
b = Button(root, text = "add entry", command = command)
b.grid(row=1,column=0)
root.mainloop()