How do I create multiple similar frames with tkinter using classes? - python

How would I create multiple frames that have the same widgets in Tkinter? Basically what I want to do is create 15 copies of a set of multiple frames that all contain the same widgets as shown in the image, the purpose of this program is to assist the user in sorting photographs into groups based on a specific ID supplied by the user. The radio buttons are there for the user to classify each photo, i.e front, back ,top etc..
Its not very efficient to copy the code 15 times and I want to know if it's possible to use a class to define the frame once and reuse the code for each new frame. I need to keep track of what the user does on each frame and save their selections on the radio buttons and check boxes for each frame. After all the photos have been classified by the user, a button is clicked that should then save all the photos with a new ID and also saves the info from the radio buttons into a csv file. Then the next batch of photos is loaded and the process repeats.
I have included an example of the code I used to create one of the frames, this is the code that I want to make reusable. I do not want to have to repeat it 15 times.
############################################################################
#FRAME 3
Photo_2 = Frame(master, bg = "white",relief = RIDGE, bd = 2)
Photo_2.grid(column = 2, row = 1, padx=5, pady=5)
Lbl2 = Label(Photo_2,text = 'Frame 3')
Lbl2.grid(row = 0, column = 0, columnspan = 4, pady = 5)
# Check box
varc2 = StringVar()
varc2.set(0)
Check_2 = Checkbutton(Photo_2, variable = varc2, text="Relevant?", command = lambda:Chk_Val(varc2))
Check_2.grid(row = 1,column = 0,columnspan = 4)
# Photo 1
P2 = "Photo_2.jpg"
P2 = Image.open(P2).resize((200, 200), Image.ANTIALIAS)
phot2 = ImageTk.PhotoImage(P2)
panel = Label(Photo_2, image = phot2)
panel.grid(columnspan = 3, column=1)
# Create Multiple Radio Buttons
Rad_Cont = Frame(Photo_2)
Rad_Cont.grid(column = 0, row = 2)
v2 = StringVar()
v2.set("Address")
for text,mode in RADIO:
b = Radiobutton(Rad_Cont, text=text, variable=v2,
value=mode, command = lambda:Rad_Val(v2))
b.pack()
################################################################################

Of course it is possible to create a class to represent similar objects.
Here is how I might implement what you're trying to accomplish:
import tkinter as tk
class PhotoFrame(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, bg='white', relief='ridge', bd=2)
self.label_widget()
self.checkbox_widget()
self.photo_widget()
self.radio_widgets()
def label_widget(self):
self.title_label = tk.Frame(self, text='Frame 3') # Or Frame 1, 2 etc.
self.title_label.grid(row=0, column=0, columnspan=4, pady=5)
def checkbox_widget(self):
self.varc = tk.StringVar()
self.varc.set(0)
self.checkbox = tk.Checkbutton(self, variable=self.varc,
text='Relevant?', command=self.Chk_Val)
self.checkbox.grid(row=1, column=0, columnspan=4)
def photo_widget(self):
# ... Your code here
def radio_widgets(self):
# ... Your code here
def Chk_Val(self):
# ... Your code here
Now I tried not to give you the entire solution so you can learn and figure the rest out by yourself, but I'm sure you can see what I'm getting at in terms of using a class. Now this class PhotoFrame could be used as many times as you'd like, although please understand you'll have to configure each frame appropriately, e.g. I would omit the the text attribute assignment in the label_widget section (You don't want all of your frames titled 'Frame 3'), so in your main program logic, you'd configure each instance of a PhotoFrame object like so:
frame1 = PhotoFrame(master)
frame1.title_label.configure(text='Frame 1') # Along with any other configuration
I hope this all helps - if you are not familiar with classes check out the provided documentation, and here is a great tkinter reference: NMT tkinter

Related

Clearing objects from a frame, without deleting the Frame

I'm attempting to create a simple point of sale system where when a button is pressed, its quantity and price are added up and eventually a total is shown (haven't gotten to this bit yet)
I've decided to also incorporate a clear button which will clear the frame in which the clicked items and their prices + quantities are shown however I'm having some problems with clearing the frame and still being able to click buttons afterwards.
This is the code I have for the Item button:
def AddButton():
global item_num #calls global variable
item_num += 1
item_text = "Chips 2.00"+" "+str(item_num) #concatonates text & variable
item1.config(text=item_text) #updates label text - doesn't add multiple
item1.pack()
addButton = Button(itemFrame, text="Chips", width=10, height=10, command=AddButton)
addButton.grid(row=1, column=1)
item1 = Label(receiptFrame)
and I began by trying to use .destroy like this:
def clearClick(): #blank function for clear button
receiptFrame.destroy()
however, since this completely deletes the frame, I'm then not able to re-input more items after it's been cleared
I also tried re-creating the frame:
def clearClick(): #blank function for clear button
receiptFrame.destroy()
receiptFrame = Frame(root, width=600, height=500, bd=5, relief="ridge")
receiptFrame.grid(row=1, column=3, columnspan=2)
but this still doesn't work
Is there a way to clear the contents of a frame without deleting the frame itself or does .destroy have to be used?
fr.winfo_children() returns the list of widgets inside the frame:
root = tk.Tk()
fr = tk.Frame()
lb = tk.Label(fr)
lb.grid()
print(fr.winfo_children())
for child in fr.winfo_children():
child.destroy()
print(fr.winfo_children()) # Now empty

Tkinter Generating new rows of Entry boxes not responding to Button call

I am trying to create an SQLite database interface using python and Tkinter to store the results of tests. Basically, the user first selects an option from a drop-down menu (Tkinter menubar) which generates a form with 6 predefined tests for collecting input. The input from these entry boxes is then stored in SQLite, where each row/test is stored in a different table. Different materials will be tested but the tests are the same for each. I want to compare the same test results for multiple materials at a later stage to model trends etc.
So, my idea is that the menu-bar calls a function (which determines the test category, board_strength for example) to generate a form with the date and project number and which does the layout. A class is used to generate the entry boxes, get the input, calculate the average, and store the values to SQLite after each row is filled out. If the user requires an additional row for the same test, a button should add it as needed. Then, if the user moves onto the next test (maybe via a button..), they again have the option to add another row for this test, and so on. Here I've only shown 1 test, ECT, but there are 5 more.
I've tried making a button to generate a new row but it doesn't seem to work properly. I've read a few posts on this site and googled, and although I'm quite new to working with classes, it seemed the easiest way to do what I want.
from tkinter import *
import tkinter as tk
date = datetime.date.today()
class create_widgets:
'''create widgets for input data, collect input, determine average, and store'''
def __init__(self, window, test_name, units, row):
self.window = root
self.test_name = ''
self.units = ''
self.row = 0
self.add_entry(window, test_name, units, row)
def add_entry(self, window, test_name, units, row):
self.entries = []
lbl_of_test = Label(window, text = f'{test_name} ({units}):', font = ('Arial', 10))
lbl_of_test.grid(row = row, column = 0)
for i in range(3):
self.entry = Entry(window, width=8)
self.entry.grid(row=row, column=i+1)
self.entries.append(self.entry)
def board_strength():
''' Add project details and date and generate test labels'''
lbl1 = Label(top_frame, text = 'Board Strength Properties', font = ('Arial Bold', 12))
lbl1.pack(side = 'top')
display_date = Label(top_frame, text = f'Date: {date}', font = ('Arial', 10))
display_date.pack(anchor = 'w')
sample_lable = Label(top_frame, text = 'Project Number:', font = ('Arial Bold', 10))
sample_lable.pack(anchor = 'sw', side = 'left')
sample_entry = Entry(top_frame, width = 15)
sample_entry.pack(anchor = 'sw', side = 'left')
for i in range(3):
lbl = Label(btm_frame2, text = f'Test: {i+1}', font = ('Arial', 10))
lbl.grid(row=0, column=i+1)
#First test
ect_widgets = create_widgets(btm_frame2, test_name = 'ECT', units = 'kN/m', row = 1)
ect_button = Button(btm_frame2,text='Add ECT', command = ect_widgets.add_entry)
ect_button.grid(row=12,column= 1, pady = 20)
# Next test
# fct_widgets = create_widgets(btm_frame2, test_name = 'FCT', units = 'kPa', row = 1)
# fct_button = Button(btm_frame2,text="FCT", command = fct_widgets.add_entry)
# fct_button.grid(row=12,column= 2, pady = 20)
# Next: Add FCT
menubar = Menu(root)
filemenu = Menu(menubar, tearoff=0)
menubar.add_cascade(label="File", menu=filemenu)
filemenu.add_separator()
filemenu.add_command(label="Exit", command=root.quit)
papermenu = Menu(menubar, tearoff=0)
menubar.add_cascade(label="Paper", menu=papermenu)
boardmenu = Menu(menubar, tearoff=0)
menubar.add_cascade(label="Board", menu=boardmenu)
boardmenu.add_command(label="Strength Properties", command = board_strength)
boardmenu.add_command(label="Structural Properties", command= 'board_structure')
root.config(menu=menubar)
root.mainloop()
When I run this code, I get the following error:
TypeError: add_entry() missing 4 required positional arguments: 'window', 'test_name', 'units', and 'row'
Why does it still want these arguments if I've already passed them creating the instance ect_widget? And how do I increase the row number accordingly when new rows are added? The row numbers are going to be a headache.
Any help or input would greatly be appreciated. Thanks!
This snippet of the code you have offered isn't enough to actually run it so I'm sorry for minor errors, but I think I know what you are missing here.
You probably want the add_entry method to not have all those arguments required. While it needs those things to do what it does; you can save that data in the instance of the class when you create it.
def __init__(self, window, test_name, units, row):
self.window = window
self.test_name = test_name
self.units = units
self.row = row
self.add_entry()
def add_entry(self):
self.entries = []
lbl_of_test = Label(self.window,
text = f'{self.test_name} ({self.units}):',
font = ('Arial', 10))
lbl_of_test.grid(row = self.row, column = 0)
for i in range(3):
self.entry = Entry(self.window, width=8)
self.entry.grid(row=self.row, column=i+1)
self.entries.append(self.entry)
Something like this is probably more appropriate. We store attributes of an instance using that "self." syntax. The "self.window = window" etc saves these things so you can use them whenever without passing them. Then, in the add_entry method, it can use the saved values of those things by calling up the 'self.' version of them.
(Technically speaking, there is nothing magical about using the word 'self' before those variables; it works that way because 'self' is the first argument passed into non-static methods--including __init__--in a class. Whatever is first refers to the instance of that class, ergo the typical name of 'self.' If you ever needed to refer to the instance of a class within its own methods you would just use 'self.')
I hope that helps, let us know if you have more questions.
P.S. I split up that really long line into multiple...generally, you don't want lines longer than 80 characters, and Python lets you cut lines freely between items inside of parentheses.

Tkinter button throwing "unknown option -XXXXX" when setting options. Python 2.7

Here is the include file
import tkinter as tk
from tkinter import ttk
class CollapsiblePane(ttk.Frame):
"""
-----USAGE-----
collapsiblePane = CollapsiblePane(parent,
expanded_text =[string],
collapsed_text =[string])
collapsiblePane.pack()
button = Button(collapsiblePane.frame).pack()
"""
def __init__(self, parent, expanded_text ="Collapse <<",
collapsed_text ="Expand >>"):
ttk.Frame.__init__(self, parent)
# These are the class variable
# see a underscore in expanded_text and _collapsed_text
# this means these are private to class
self.parent = parent
self._expanded_text = expanded_text
self._collapsed_text = collapsed_text
# Here weight implies that it can grow it's
# size if extra space is available
# default weight is 0
self.columnconfigure(1, weight = 1)
# Tkinter variable storing integer value
self._variable = tk.IntVar()
# Checkbutton is created but will behave as Button
# cause in style, Button is passed
# main reason to do this is Button do not support
# variable option but checkbutton do
self._button.config( width=1, height=1, borderwidth = 0)
self._button.grid(row = 0, column = 0)
# This wil create a seperator
# A separator is a line, we can also set thickness
self._separator = ttk.Separator(self, orient ="horizontal")
self._separator.grid(row = 0, column = 1, sticky ="we")
self.frame = ttk.Frame(self)
# This will call activate function of class
self._activate()
def toggle(self):
"""Switches the label frame to the opposite state."""
self._variable = not self._variable
def _activate(self):
if not self._variable:
# As soon as button is pressed it removes this widget
# but is not destroyed means can be displayed again
self.frame.grid_forget()
# This will change the text of the checkbutton
self.toggle()
elif self._variable:
# increasing the frame area so new widgets
# could reside in this container
self.frame.grid(row = 1, column = 0, columnspan = 1)
self.toggle()
Here is the program that utilizes it.
# Importing tkinter and ttk modules
from tkinter import *
from tkinter.ttk import *
# Importing Collapsible Pane class that we have
# created in separate file
from collapsiblepane import CollapsiblePane as cp
# Making root window or parent window
root = Tk()
root.geometry('200x200')
# Creating Object of Collapsible Pane Container
# If we do not pass these strings in
# parameter the the defalt strings will appear
# on button that were, expand >>, collapse <<
cpane = cp(root, 'Expanded', 'Collapsed')
cpane.grid(row = 0, column = 0)
# Button and checkbutton, these will
# appear in collapsible pane container
b1 = Button(cpane.frame, text ="GFG").grid(
row = 1, column = 2, pady = 10)
cb1 = Checkbutton(cpane.frame, text ="GFG").grid(
row = 2, column = 3, pady = 10)
mainloop()
And here's my error.
TclError: unknown option "-borderwidth"
The error comes from this line
self._button.config( width=1, height=1, borderwidth = 0)
Which I've also tried as
self._button = ttk.Button(self,command = self._activate, width=1, height=1, borderwidth = 0)
With the same error.
I've removed the borderwidth, and then it throws the same error with width/height. I've tested the width/height/borderwidth in other instances and it works, I'm just not sure why it won't work here.
When using ttk widgets, you have to keep in mind that many of the parameters you would set separately for each tkinter widget are mostly managed by styles when it comes to ttk widgets.
If you look up the ttk.Button widget options (here), you can easily find out which parameters can and can not be set.
In your case, you'd have to create a style for the button.
s = ttk.Style()
s.configure("TButton", borderwidth=0)
If you use this code, you won't have to separately declare that your button is using that style, however every other (ttk) button will automatically use this style too. If you want to set a style for just one button, you'd have to do something like this:
s = ttk.Style()
s.configure("b1.TButton", borderwidth=0) #you can rename b1 to anything you want
and then
b1 = Button(cpane.frame, text ="GFG", style="b1.TButton").grid(
row = 1, column = 2, pady = 10)
You can also read up on styles by yourself here.

My Tkinter GUI wont open

list_ = [0,0]
menu = ""
subpage = ""
def main_menu(root):
print list_
menu = Frame(root)
button0 = Button(menu, text="Go To Subpage",
command=lambda: switch_page("sub"))
button0.grid(row = 0, column = 0)
button1 = Button(menu, text="Save Values",
command=lambda: save_values())
button1.grid(row = 0, column = 1)
global entry0
global entry1
entry0 = Entry(menu)
entry1 = Entry(menu)
entry0.pack()
entry1.pack()
return menu
def sub_menu(root):
subpage = Frame(root)
label0 = Label(text = list_[0])
label1 = Label(text = list_[1])
label0.grid(row = 0, column = 0)
label1.grid(row = 1, column = 0)
button2 = Button(subpage, text="Return To Main Page",
command = lambda: switch_page("main"))
button2.grid(row = 0, column = 0)
return subpage
def save_values():
list_ = []
e0 = entry0.get()
list_.append(e0)
e1 = entry1.get()
list_.append(e1)
print list_
def switch_page(page_name):
slaves = root.pack_slaves()
if slaves:
slaves[0].pack_forget()
pages[page_name].pack(fill="both", expand=True)
root = Tk()
pages = {
"main": main_menu(root),
"sub": sub_menu(root),
}
switch_page("main")
root.mainloop()
So my problem is that the GUI won't open I have tried putting it into another new python file and have tried to re-write it but slightly differently, which now won't open as soon as I use a .grid() function, once this .grid() function is removed it will show the GUI. Anyway for this code I am not sure in the slightest what is preventing it from opening because the research I have done has lead to me finding out I need to use root.mainloop() ,which I was already using and the other solution was to define the frame length and width which I thought was unnecessary because I had previously had the GUI working and not only that I have a frame created with widgets inside.
Any help is appreciated.
Also on side note in the switch page function I use the .pack_forget() function and was looking to see if there was a function which closed the page fully or would allow me to change a label while the page is open like a sort of refresh function(update idle task didn't work for what I needed it for)
You cannot use both grid and pack with windows that share the same parent. You are doing that here:
...
button0.grid(row = 0, column = 0)
...
entry0.pack()
entry1.pack()
You can mix and match grid and pack all you want within an application, but you can't use them both like you do because those widgets all have menu as a parent.
Also, you can change the attributes of any widget with the config method (eg: some_widget.configure(background="red")). You need to have a reference (variable) to that widget.

Tkinter creating a widget without storing it in a some kind of container like a variable?

Good day. I was confused when creating a radiobutton or any kind of widget like the label widget inside a class method because it was not stored in a some kind of container like a variable. It's my first time seeing this kind of code: here it is
class Application(Frame):
""" GUI Application for favorite movie type. """
def __init__(self, master):
""" Initiale Frame. """
super(Application, self).__init__(master)
self.grid()
self.create_widgets()
def create_widgets(self):
""" Create widgets for movie type choices. """
# Create description label
Label(self,
text = "Choose your favorite type of movie"
).grid(row=0, column=0, sticky= W)
# create instruction label
Label(self,
text="Select one:"
).grid(row=1,column=0, sticky=W)
Radiobutton(self,
text="Comedy",
variable=self.favorite,
value = "comedy.",
command = self.update_text
).grid(row = 2, column = 0, sticky=W)
# create Drama radio button
Radiobutton(self,
text = "Drama",
variable = self.favorite,
value = "drama.",
command = self.update_text
).grid(row = 3, column = 0, sticky = W)
# create Romance button
Radiobutton(self,
text = "Romance",
variable = self.favorite,
value = "romance.",
command = self.update_text
).grid(row = 4, column = 0, sticky = W)
I usually see codes like this:
radio = Radiobutton(root)
radio.grid()
Can you explain me what happen with the first code? How did it create a widget without storing it in a some kind of a variable like in a second code
Tkinter is just a thin wrapper around an embedded Tcl interpreter which itself has the tk toolkit loaded. When you create a widget, the actual widget is represented as an object inside the tcl interpreter.
Tkinter will create a python object that holds a reference to this tcl object, but the existence of the python object isn't required for the tcl/tk object to exist. If you create a widget without saving a reference, the widget is still created and still exists in the tcl/tk interpreter.

Categories