I'm trying to create a tkinter widget that takes nested dictionaries as input and generates OptionMenus that continue to narrow down choices until the choice dictionaries culminate in a value.
So far, the widgets generate correctly, but are not destroyed correctly. I've currently removed widget regeneration based off changing choices as it would cause more and more widgets to appear each time a change was made.
Here is the code, as it wasn't pasting here nicely.
import tkinter as tk
labelwidth = 20
inputwidth = 25
class DropDownWidget(tk.Frame):
def __init__(self, parent, labeltext, optionType, options, command=""):
if not isinstance(options, dict):
print(options)
raise TypeError("DropDownWidgets take dictionaries of the choices and the values they represent as keys and values")
tk.Frame.__init__(self, parent)
self.optionType = optionType
self.options = options
self.optionKeys = list(options.keys())
self.optionValues = list(options.values())
self.dropDownDisplayed = tk.StringVar(self, self.optionKeys[0])
self.label = tk.Label(self, text=labeltext, width=labelwidth, justify="left", relief="groove", anchor="w")
self.input = tk.OptionMenu(self, self.dropDownDisplayed, *list(options.keys()), command=command) # , command=self.updateValue)
self.input.config(width=inputwidth-7)
self.label.grid(row=0, column=0)
self.input.grid(row=0, column=1)
def updateValue(self, something):
self.inputVar.set(self.options[self.dropDownDisplayed.get()])
def replaceOptionMenu(self, options, command, *args):
self.options = options
self.input.grid_forget()
self.dropDownDisplayed.set(list(options.keys())[0])
self.input = tk.OptionMenu(self, self.dropDownDisplayed, *list(options.keys()), command=command)
self.input.config(width=inputwidth - 7)
self.input.grid(row=0, column=2)
def replaceOptionMenuNoCmd(self, options):
self.options = options
self.input.grid_forget()
self.dropDownDisplayed.set(list(options.keys())[0])
self.input = tk.OptionMenu(self, self.dropDownDisplayed, *list(options.keys()))
self.input.config(width=inputwidth - 7)
self.input.grid(row=0, column=2)
def value(self):
return self.optionType(self.options[self.dropDownDisplayed.get()])
def setValue(self, value):
self.dropDownDisplayed.set(value)
class MultiDropDownWidget(tk.Frame):
def __init__(self, parent, options, isChild=False):
tk.Frame.__init__(self, parent, relief="raised", bg="blue")
self.originalOptions = options
if isChild is False:
self.root = self
else:
self.root = parent.root
if isinstance(options, dict):
if isinstance(list(options.values())[0], dict):
self.options = {key: key for key in options.keys()}
self.hasChild = True
else:
self.options = {key: value for key, value in options.items()}
self.hasChild = False
#
else:
self.options = options
if self.hasChild:
self.dropDown = DropDownWidget(self.root, "Choice", str, self.options,
command=lambda x: self.destroyChildren()) # self.updateChild)
else:
self.dropDown = DropDownWidget(self.root, "Choice", str, self.options)
self.dropDown.pack()
if self.hasChild:
self.child = MultiDropDownWidget(self.root, options[self.dropDown.value()], isChild=True)
else:
pass
def updateChild(self, *args):
if self.hasChild:
self.child.destroyChildren()
self.child = MultiDropDownWidget(self, self.originalOptions[self.dropDown.value()], isChild=True)
def destroyChildren(self, *args):
if self.hasChild:
self.child.destroyChildren()
self.child.destroy()
if not self.hasChild:
self.destroy()
options = {'a': {
'aa': {'aaa': 'aaa'},
'ab': {'aba': 'aba'},
},
'b': {
'bb': {'bbb': 'bbb'},
'bc': {'bcc': 'bcc'},
},
'c':
{'c1': {'c2': 'c2'}}}
root = tk.Tk()
multiWidget = MultiDropDownWidget(root, options)
multiWidget.pack()
root.mainloop()
The problem seems to lie within destroyChildren()
I can see that the destroy()s are being called on the correct objects when using the debugger, but they are lingering around for some reason. I have also noticed that if I destroy the root widget they will all be destroyed, as expected.
I am also able to destroy their inner DropDownWidget without issue, but their empty frame and the MultiDropDownWidget still exist.
I tried making the MultiDropDown Frame.init make the frame show a blue background, but I'm not seeing the blue show up anywhere, which is probably where the issue lies. I'm still not sure why a component is resisting being destroyed.
If you replace the dropDown command with when hasChild is True with updateChildren you'll see the new children be generated without the old children being destroyed.
I have restructured the widgets not to be their own MultiDropDownWidget objects but DropDownWidget objects that are stored in a list of the MultiDropDownWidget. Two issues that it has which should be okay for my use are that if a top-level key leads directly to a value, it crashes, and sub-options can't have the same value as an option preceding it when it is changed or it will reconfigure improperly.
class MultiDropDownWidget(tk.Frame):
def __init__(self, parent, options, isChild=False):
tk.Frame.__init__(self, parent, relief="raised", bg="blue")
self.originalOptions = options
if isChild is False:
self.root = self
else:
self.root = self
if isinstance(options, dict):
if isinstance(list(options.values())[0], dict):
self.options = {key: key for key in options.keys()}
self.hasChild = True
else:
self.options = {key: value for key, value in options.items()}
self.hasChild = False
else:
raise TypeError("DropDownWidgets take dictionaries of the choices and the values they represent as keys and values")
self.widgets = []
self.widgetValues = []
self.valueLabel = FreeInputWidget(self, "Value", str)
self.baseChoice = self.dropDown = DropDownWidget(self.root, "Choice", str, self.options,
command=lambda x: self.reconstructChildren(x))
self.widgets.append(self.baseChoice)
self.baseChoice.pack()
self.constructChildren(oOptions=self.originalOptions[self.baseChoice.value()])
self.valueLabel.pack()
#disableInput(self.valueLabel)
def constructChildren(self, *args, oOptions="", widgetList=""):
options = oOptions
while True:
b = False
if isinstance(options, dict):
if isinstance(list(options.values())[0], dict):
childoptions = {key: key for key in options.keys()}
command = self.reconstructChildren
child = DropDownWidget(self.root, "Choice", str, childoptions, command=lambda x: self.reconstructChildren(x))
self.root.widgets.append(child)
child.pack()
options = options[child.value()]
else:
b = True
childoptions = {key: value for key, value in options.items()}
child = DropDownWidget(self.root, "Choice", str, childoptions, command=self.updateValueLabel)
self.root.widgets.append(child)
child.pack()
else:
pass
# child = DropDownWidget(self.root, "Choice", str, childoptions, command=command)
# self.root.widgets.append(child)
# child.pack()
if b is True:
break
else:
pass
# options = options[child.value()]
self.updateValueLabel()
def updateValueLabel(self, *args):
self.valueLabel.setValue(self.widgets[len(self.widgets)-1].value())
self.valueLabel.pack_forget()
self.valueLabel.pack()
def destroyChildren(self, index, *args):
for child in self.widgets[index:]:
# print(f"{child.value()} destroyed")
child.pack_forget()
self.widgets.pop(self.widgets.index(child))
child.destroy()
def reconstructChildren(self, x, *args):
# print(self.widgets)
i = 0
for widget in self.widgets:
# print(x, widget.value())
if x == widget.value():
break
i += 1
# print(x)
# print(i)
self.destroyChildren(i+1)
newOptions = self.getDictLayer(i)#self.originalOptions, i, keyy)[keyy] #[keyy]
self.constructChildren(oOptions=newOptions) #self.widgets[i].originalOptions)
def getDictLayer(self, index, *args):
options = self.originalOptions
if index == 0:
return options[self.baseChoice.value()]
for i in range(0, index+1):
# print(self.widgets[i].value())
options = options[self.widgets[i].value()]
return options
Related
I can't get the values that are selected in the checkbutton.
I want to add text and value to checkbutton.
I have a list variable defined and must be released after I select it.
from tkinter import Tk, Checkbutton, Frame, IntVar
class Options:
def __init__(self, parent, name, selection=None, select_all=False):
self.parent = parent
self.name = name
self.selection = selection
self.variable = IntVar()
self.checkbutton = Checkbutton(self.parent, text=self.name,
variable=self.variable, command=self.check)
if selection is None:
self.checkbutton.pack(side='top')
elif select_all:
self.checkbutton.config(command=self.select_all)
self.checkbutton.pack()
for item in self.selection:
item.checkbutton.pack(side='top')
def check(self):
state = self.variable.get()
if state == 1:
print(f'Selected: {self.name}')
if all([True if item.variable.get() == 1 else False for item in self.selection[:-1]]):
self.selection[-1].checkbutton.select()
elif state == 0:
print(f'Deselected: {self.name}')
if self.selection[-1].variable.get() == 1:
self.selection[-1].checkbutton.deselect()
def select_all(self):
state = self.variable.get()
if state == 1:
for item in self.selection[:-1]:
item.checkbutton.deselect()
item.checkbutton.invoke()
elif state == 0:
for item in self.selection[:-1]:
item.checkbutton.select()
item.checkbutton.invoke()
selection = []
enable = [['name1', 'position1'], ['name2', 'position2'], ['name3', 'position3']]
root = Tk()
option_frame = Frame(root)
option_frame.pack(side='left', fill='y')
for i in range(len(enable)):
selection.append(Options(option_frame,enable[i][0], selection))
selection.append(Options(option_frame, 'Select All', selection, True))
root.mainloop()
I have a question. I have this code:
import tkinter as tk
class new_f:
def __init__(self,root,num):
self.new_frame=tk.Frame(root,width=100,height=100,bg='white',bd=3,relief=tk.GROOVE)
self.new_frame.pack(side=tk.LEFT,fill=tk.X,expand=True)
self.num=num
def add_label(self,t):
self.l1=tk.Label(self.new_frame,bg='white',text=t)
self.l1.pack()
def return_instance(self):
return self.num
class Main_win:
def __init__(self,root):
self.root=root
self.bind_number=0
self.current_index=0
self.instance_list=[]
self.b1=tk.Button(self.root,text='Add Frame',command=self.add_frame_win)
self.b1.pack(side=tk.BOTTOM)
self.b2=tk.Button(self.root,text='Add text',command=self.add_text_frame)
self.b2.pack(side=tk.BOTTOM)
def return_instance_num(self,num,*args):
self.current_index=num
def add_frame_win(self):
new_in=new_f(self.root,self.bind_number)
self.instance_list.append(new_in)
new_in.new_frame.bind('<Button-1>',lambda evnt: self.return_instance_num(new_in.return_instance()))
#self.current_index=new_in.return_instance()
self.bind_number+=1
def add_text_frame(self):
instance=self.instance_list[self.current_index]
instance.add_label('Hello World')
root=tk.Tk()
ob=Main_win(root)
root.mainloop()
What I a trying to achieve is that I want to detect on which frame was the left mouse-button clicked so as to make that Frame active and add the labels to that particular Frame. However, I am stuck on how would I go about writing the code. I need a new class Because I don't know how many frames will the user need.
This is a short example of the code I will be implementing later. So my question is:
How will I go to detect which frame was picked so as to make it active to add the labels?
In this approach I have label l1 bound to Button-1
This was achieved by passing self to new_f instead of root
and binding self.l1 to Button-1
import tkinter as tk
class new_f:
def __init__(self, prog, num):
self.prog = prog
self.new_frame = tk.Frame(prog.root, width = 100, height = 100, bg = 'white', bd = 3, relief = tk.GROOVE)
self.new_frame.pack(side = tk.LEFT, fill = tk.X, expand = True)
self.num = num
def add_label(self, t):
self.l1 = tk.Label(self.new_frame, bg = 'white', text = t)
self.l1.pack()
# binding button-1 press to label
self.l1.bind("<Button-1>", lambda evnt: self.prog.return_instance_num(self.return_instance()))
def return_instance(self):
return self.num
class Main_win:
def __init__(self, root):
self.root = root
self.bind_number = 0
self.current_index = 0
self.instance_list = []
self.b1 = tk.Button(self.root, text = 'Add Frame', command = self.add_frame_win)
self.b1.pack(side = tk.BOTTOM)
self.b2 = tk.Button(self.root, text = 'Add text', command = self.add_text_frame)
self.b2.pack(side = tk.BOTTOM)
def return_instance_num(self, num, *args):
self.current_index = num
def add_frame_win(self):
# note passing self not root
new_in = new_f(self, self.bind_number)
self.instance_list.append(new_in)
new_in.new_frame.bind('<Button-1>', lambda evnt: self.return_instance_num(new_in.return_instance()))
#self.current_index = new_in.return_instance()
self.bind_number = self.bind_number + 1
def add_text_frame(self):
instance = self.instance_list[self.current_index]
instance.add_label('Hello World')
root = tk.Tk()
ob = Main_win(root)
# This necessary to prevent error if user hits 'Add text' before 'Add Frame'
ob.add_frame_win()
root.mainloop()
Here is an alternative method that uses dictionaries to store l1 and new_frame objects as keys and new_f instances as values.
This method can be used for other tkinter objects (Entry, Listbox, Text, Canvas)
import tkinter as tk
class new_f:
def __init__(self, parent):
self.parent = parent
self.frame = tk.Frame(
parent.root, width = 100, height = 100,
bg = "white", bd = 3, relief = tk.GROOVE)
self.frame.pack(
side = tk.LEFT, fill = tk.X, expand = True)
self.frame.bind("<Button-1>", parent.get_current_frame)
def add_label(self, t):
self.label = tk.Label(self.frame, bg = "white", text = t)
self.label.pack(fill = tk.BOTH, expand = True)
# bind button-1 to label, set instance_label and current to self
self.label.bind("<Button-1>", self.parent.get_current_label)
self.parent.instance_label[self.label] = self.parent.current = self
class Main_win:
instance_label = dict() # This method can be expanded for other objects
instance_frame = dict() # that you may want to create in frames
def __init__(self, root):
self.root = root
self.b1 = tk.Button(
self.root, text = "Add Frame", command = self.add_frame_win)
self.b1.pack(side = tk.BOTTOM)
self.b2 = tk.Button(
self.root, text = "Add text", command = self.add_text_frame)
self.b2.pack(side = tk.BOTTOM)
def get_current_label(self, ev):
self.current = self.instance_label[ev.widget]
def get_current_frame(self, ev):
self.current = self.instance_frame[ev.widget]
def add_frame_win(self):
# note passing self not root
self.new_in = new_f(self)
self.instance_frame[self.new_in.frame] = self.current = self.new_in
def add_text_frame(self):
# Change message with entry tool?
self.current.add_label("Hello World")
root = tk.Tk()
ob = Main_win(root)
# This necessary to prevent error if user hits 'Add text' before 'Add Frame'
ob.add_frame_win()
root.mainloop()
I am making a cube timer, and I want to make a table where you can see all of your times, including its scramble, date and other information using the text widget. If you insert a couple of times, it works fine, but when you do more than 50, you can't see some of the times. So, i'm using a scrollbar on a Canvas. I'm trying to make the canvas scroll down when you use the Scrollbar, but it doesn't seem to work. Here's my code.
class TimeTable(tk.Canvas):
"""Creates a time table using a tk.Canvas"""
def __init__(self, times, *args, **kwargs):
"""
:param times: list[CubeUtils.Time]
"""
# Initialize super class and define attributes
super().__init__(*args, **kwargs)
self.TIME_ATTRS = ["Time", "Scramble", "Date", "DNF"]
self.ScrollbarY = None
self.fullscreen = False
self.times = times
self.frame = self.nametowidget(self.winfo_parent())
self.parent = self.frame.nametowidget(self.frame)
self.config(height=len(self.times)*15)
self.frame.config(height=len(self.times)*15)
# Insert time attributes in entries
for column, attr in enumerate(self.TIME_ATTRS):
text = tk.Text(self, font=("Arial", 15, "bold"), width=50, height=1)
text.insert("0.0", attr)
text.config(state=tk.DISABLED)
text.grid(row=0, column=column+1, sticky=tk.E)
# Insert times
for time_count, time in enumerate(self.times):
time_info = [time_count+1, time.time, time.scramble, time.date, time.DNF]
if time_info[0] > 49 and self.ScrollbarY is None:
self.add_scrollbar()
time_info_font = ("Arial", 15, "bold")
for column, info in enumerate(time_info):
if isinstance(info, int) and not isinstance(info, bool):
text = tk.Text(self, font=time_info_font, width=2 if len(str(info)) <= 2 else len(str(info)), height=1, fg="#ff5000")
else:
text = tk.Text(self, font=time_info_font, width=50, height=1, fg="#ff5000")
text.insert("0.0", str(info))
text.grid(row=time_count + 1, column=column, sticky=tk.E)
text.config(state=tk.DISABLED)
# Bindings
self.parent.bind("<F11>", lambda key: self.toggle_fullscreen())
self.parent.bind("<Escape>", lambda key: self.exit_fullscreen())
self.parent.bind_all("<MouseWheel>", self.on_mousewheel)
Scrolling
def on_mousewheel(self, event):
"""Scrolls the canvas using the mouse wheel"""
self.yview_scroll(int(-1 * (event.delta / 120)), "units")
def add_scrollbar(self):
"""Adds a scrollbar to the frame"""
self.config(scrollregion=self.bbox("all"))
self.ScrollbarY = tk.Scrollbar(self.frame)
self.config(yscrollcommand=self.ScrollbarY.set)
self.ScrollbarY.config(command=self.yview)
self.ScrollbarY.pack(side=tk.RIGHT, expand=True, fill=tk.BOTH)
Fullscreening
def toggle_fullscreen(self):
"""Toggles fullscreen on and off"""
self.fullscreen = not self.fullscreen
self.parent.attributes("-fullscreen", self.fullscreen)
def exit_fullscreen(self):
"""Exists fullscreen"""
self.fullscreen = False
self.parent.attributes("-fullscreen", self.fullscreen)
By the way, the times argument must be a list with elements of type Time, here is the code for that class.
class Time:
"""Creates a time object that stores its time, scramble, date and whether or not it is a DNF"""
def __init__(self, time, scramble, date, DNF=False):
"""
:param time: float
:param scramble: str
:param date: datetime.datetime
:param DNF: bool
"""
if not isinstance(date, datetime.datetime):
raise TypeError("date parameter must be of type datetime.datetime")
if not isinstance(time, float):
raise TypeError("time parameter must be of type float")
if not isinstance(scramble, str):
raise TypeError("scramble parameter must be of type str")
if not isinstance(DNF, bool):
raise TypeError("DNF parameter must be of type bool")
self.time = time
self.scramble = scramble
self.date = datetime.datetime.strftime(date, "%Y-%m-%d-%I:%M %p")
self.DNF = DNF
I have tried reading web pages on effbot.com and other websites about putting a scrollbar on a canvas, but none of them are working for me.
Edit: Thanks to acw1668 I have finished my TimeTable class, here is the finished code for that
class TimeTable(tk.Frame):
"""Creates a time table using a tk.Canvas"""
def __init__(self, parent, times, *args, **kwargs):
"""
:param parent: tk.Tk()
:param times: list[CubeUtils.Time]
"""
# Initialize super class and define attributes
super().__init__(*args, **kwargs)
self.TIME_ATTRS = ["Time", "Scramble", "Date", "DNF"]
self.fullscreen = False
self.times = times
self.canvas = tk.Canvas(self)
self.frame = tk.Frame(self.canvas)
self.parent = parent
self.canvas.pack(fill=tk.BOTH, expand=True)
self.canvas.create_window(2, 2, window=self.frame, anchor="nw")
self.frame.bind("<Configure>", self.on_config)
self.parent.bind("<MouseWheel>", self.on_mousewheel)
self.parent.bind("<F11>", lambda event: self.toggle_fullscreen())
self.parent.bind("<Escape>", lambda event: self.exit_fullscreen())
self.populate()
def populate(self):
"""Populates the frame with the times"""
# Insert time attributes in text widgets
for column, attr in enumerate(self.TIME_ATTRS):
text = tk.Text(self.frame, font=("Arial", 15, "bold"), width=50, height=1)
text.insert("0.0", attr)
text.config(state=tk.DISABLED)
column += 4
text.grid(row=0, column=column)
# Insert times in text widgets
for time_count, time in enumerate(self.times):
time_info = [time_count + 1, time.time, time.scramble, time.date, time.DNF]
time_info_font = ("Arial", 15, "bold")
for column, info in enumerate(time_info):
if isinstance(info, int) and not isinstance(info, bool):
text = tk.Text(self.frame, font=time_info_font, width=2 if len(str(info)) <= 2 else len(str(info)),
height=1, fg="#ff5000")
else:
text = tk.Text(self.frame, font=time_info_font, width=50, height=1, fg="#ff5000")
text.insert("0.0", str(info))
row = time_count + 1
column += 3
text.grid(row=row, column=column, sticky=tk.E)
text.config(state=tk.DISABLED)
def on_config(self, event):
"""Reset the scroll region to encompass the inner frame"""
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def on_mousewheel(self, event):
"""Scrolls the canvas using the mouse wheel"""
self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
def toggle_fullscreen(self):
"""Toggles fullscreen on and off"""
self.fullscreen = not self.fullscreen
self.parent.attributes("-fullscreen", self.fullscreen)
def exit_fullscreen(self):
"""Exists fullscreen"""
self.fullscreen = False
self.parent.attributes("-fullscreen", self.fullscreen)
I tried to incorporate this piece into my script and add the calculation into column 7, however, it appears to be failing to return correct multiple of inputs, any idea why? Here is the code:
def autofill(self, event):
row = int(event.widget.grid_info()['row'])
auto_list = self.in_list(self.LookUpList, self._entry[row, 0].get())
if auto_list is not None:
self._entry[row,1].delete(0, 'end')
self._entry[row,1].insert(0, auto_list[1])
self._entry[row,2].delete(0, 'end')
self._entry[row,2].insert(0, auto_list[2])
self._entry[row,4].delete(0, 'end')
self._entry[row,4].insert(0, auto_list[3])
self._entry[row,6].delete(0,'end')
if self._entry[row,3].get() != '':
a = self._entry[row,3].get()
else: a = 0
b = int(self._entry[row,4].get())
c = int(a * b)
self._entry[row,6].insert(0, c)
I have just found the error, had to convert one variable into int, then it worked:
self._entry[row,6].delete(0,'end')
if self._entry[row,3].get() != '':
a = int(self._entry[row,3].get())
else: a = 0
b = int(self._entry[row,4].get())
c = int(a * b)
self._entry[row,6].insert(0, c)
Here is a fix that does what you're asking for. The main thing to note is that, I added an autofill method, and a binding to the Return key that calls the autofill method. You need to hit the Return/Enter key after typing for the cells to be populated, you can change this to any other event that you prefer.
There are few other changes just to make the code work in its current state. I did not make any change regarding how efficient/elegant your implementation is, I leave that to you.
from tkinter import *
class SimpleTableInput(Frame):
def __init__(self, parent, rows, columns):
Frame.__init__(self, parent)
self._entry = {}
self.rows = rows
self.columns = columns
# register a command to use for validation
vcmd = (self.register(self._validate), "%P")
# create the table of widgets
for row in range(self.rows):
for column in range(self.columns):
index = (row, column)
if column == 3:
e = Entry(self, validate="key", validatecommand=vcmd)
else:
e = Entry(self)
e.grid(row=row, column=column, stick="nsew")
self._entry[index] = e
# adjust column weights so they all expand equally
for column in range(self.columns):
self.grid_columnconfigure(column, weight=1)
## Lookup table:
self.LookUpList=[
['a','Black skirt','PP','2000'],
['b','Pink T-shirt','PP','1000'],
['c','Yellow skirt','Marela','1500'],
['d','White trousers','PP','2000']]
## Bind the Return/Enter key to populate the entries
for row in range(self.rows):
self._entry[row, 0].bind("<Return>", self.autofill)
def in_list(self, list_of_lists, item):
if not list_of_lists:
return None
if item in list_of_lists[0]:
return list_of_lists[0]
return self.in_list(list_of_lists[1:], item)
## The method that will be called to populate the entries
def autofill(self, event):
row = int(event.widget.grid_info()['row'])
auto_list = self.in_list(self.LookUpList, self._entry[row, 0].get())
self._entry[row,1].delete(0, 'end')
self._entry[row,1].insert(0, auto_list[1])
def get(self):
'''Return a list of lists, containing the data in the table'''
result = []
for row in range(self.rows):
current_row = []
for column in range(self.columns):
index = (row, column)
current_row.append(self._entry[index].get())
result.append(current_row)
return result
def _validate(self, P):
if P.strip() == "":
return True
try:
f = float(P)
except ValueError:
self.bell()
return False
return True
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
names = ["Cislo produktu",
"Popis produktu",
"Znacka",
"Mnozstvi",
"Jednotkova cena",
"Prodejna",
"Celkova cena"]
frame = Frame(self)
frame.pack(side="top", fill="both")
for i, title in enumerate(names):
l = Label(frame, text=title)
l.grid(row=0, column=i)
frame.grid_columnconfigure(i, weight=1)
self.EmptySpace = Label(self)
self.table = SimpleTableInput(self, 30, 7)
self.table.pack(side="top", fill="both")
self.EmptySpace.pack(side="top",fill="both")
## frame1 = Frame(self)
## frame1.pack(side="left",fill="both")
## self.SubButton = Button(self, text="Ulozit a zavrit", command=self.on_ulozit)
## self.StornoButton = Button(self, text="Stornovat nakup", command=self.on_storno)
## self.SubButton.pack(side="left", fill="both", expand=True)
## self.StornoButton.pack(side="left", fill="both", expand=True)
def on_ulozit(self):
data = self.table.get()
data1 = [list(filter(None, lst)) for lst in data]
data2 = list(filter(None, data1))
for item in data2:
item.append(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
## look up property
with open('C:\\Users\\chroustovskyj\\Desktop\\Dev_py\\App\\Data_Storage\\Data_Storage.csv', 'a', newline='') as csvfile:
writer = csv.writer(csvfile)
writer.writerows(data2)
root.destroy()
def on_storno(self):
print("This is storno.")
if __name__ == '__main__':
root = Tk()
root.wm_title("Formular")
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry("%dx%d+0+0" % (w, h))
Example(root).pack(side="top", fill="both", expand=False)
root.mainloop()
Using this list I want to be able to get two inputs. That will be stored in variables. Any suggestions?? Please help I am new to tkinter.
import tkinter as tk
class App():
def __init__(self, master):
self.master = master
self.type_integration = None
self.typeChoice = tk.StringVar()
self.typeChoice.set(None) # This fixes the grayness of the radio buttons!
self.typeFrame = tk.Frame(master)
OPTIONS = [('Arsenal','Arsenal'),('Aston Villa','Aston Villa'),('Burnley','Burnley'),('Chelsea','Chelsea'),('Crystal Palace','Crystal Palace'),('Everton','Everton'),('Hull','Hull'),('Leicester','Leicester',),('Liverpool','Liverpool'),('Manchester City','Manchester City'),('Manchester United','Manchester United'),('Newcastle United','Newcastle United'),('Queens Park Rangers','Queens Park Rangers'),('Southampton','Southampton'),('Stoke','Stoke'),('Sunderland','Sunderland'),('Swansea','Swansea'),('Tottenham','Tottenham'),('West Bromwich Albion','West Bromwich Albion'), ('West Ham','West Ham')]
for text, value in OPTIONS:
tk.Radiobutton(self.typeFrame, text=text, variable=self.typeChoice, value=value).pack(anchor = 'w')
tk.Button(self.typeFrame, text="Confirm Home Team", command=self.exit).pack(anchor = 'w')
self.typeFrame.pack()
def exit(self):
self.type_integration = self.typeChoice.get()
self.master.destroy()
def getinput(self):
return self.type_integration
master = tk.Tk()
app = App(master)
tk.mainloop()
home = app.getinput()
print(home)
Do I need to make another class?? Or can I reuse the code?? Help would be much appreciated. Am willing to listen to anything as I am not very good.
RadioButton is a Tkinter widget used to implement one-of-many selections.
You need to use CheckButton widget instead to do what you want.
def __init__(self, master):
...
self.variables = variables = []
for text, value in OPTIONS:
var = tk.StringVar()
variables.append(var)
tk.Checkbutton(self.typeFrame, text=text, variable=var,
onvalue=text, offvalue='').pack(anchor = 'w')
...
def exit(self):
self.type_integration = ','.join(v.get() for v in self.variables if v.get())
self.master.destroy()
UPDATE
To limit at most 2 selection:
def __init__(self, master):
...
self.variables = []
self.checkboxs = []
for text, value in OPTIONS:
var = tk.StringVar()
cb = tk.Checkbutton(self.typeFrame, text=text, variable=var,
onvalue=text, offvalue='', command=self.limit2)
cb.pack(anchor = 'w')
self.variables.append(var)
self.checkboxs.append(cb)
...
def limit2(self):
if sum(bool(v.get()) for v in self.variables) >= 2:
# Disable non-selected items to limit selection
for cb, v in zip(self.checkboxs, self.variables):
if not v.get():
cb['state'] = 'disabled'
else:
for cb in self.checkboxs:
cb['state'] = 'normal'