I am remaking a GUI calculator app in Tkinter, in order to learn about classes, methods, attributes, and also to shorten my original code. In order to shorten the code, I made a frame class that generates frames, entries, labels and dropdown menus, so I don't have to create them individually. Everything went well until I got to the dropdown menu part. When the user selects a different option from the Filters - dropdown menu like V, or B or L etc. the value in frame 1 -> entry[1] doesn't update. The method that updates the value in that entry is called add(self) and it's a part of calculator class. Here is the simple version
import numpy as np
import tkinter as tk
window = tk.Tk()
window.geometry("920x500")
window.resizable(0,0)
window.title('Exposure Time Calculator')
class Calculator:
def __init__(self, window):
self.create_test_frame1()
self.create_test_frame2()
self.add(None)
def create_test_frame1(self):
labelvalues=['val 1','val 2']
entryvalues=['203','1333']
self.frame_1 = frame('Test Frame 1',labelvalues,entryvalues,6, 2, 0, 0, "no",0,0,0,0,0,30,40)
def create_test_frame2(self):
labelvalues = ['val 3','val 4']
entryvalues = ['10','24.5']
option_menu_values = ['B','V','R','I','Luminance','Hydrogen 3nm']
self.frame_2 = frame('Frame 2', labelvalues, entryvalues, 14, 2, 0, 2,
"option_menu1_yes", option_menu_values,'Filters',
0,0,0,
5,20)
def add(self, e):
qe = self.frame_1.entry[1]
bandOption = self.frame_2.clicked.get()
if bandOption == "B":
qe.delete(0,tk.END)
qe.insert(0,22)
elif bandOption == "V":
qe.delete(0,tk.END)
qe.insert(0,33)
class frame:
# Creates a frame class for automatic frame generation
# with entries, labels and/or option menus
# 1. name : frame name
# 2. label_default_values: name of labels
# 3. entry_default_values: default values in entries
# 4. entry_width: the entries dimensions
# 5. I: number of labels and entries
# 6. grid_row: frame grid row placement
# 7. grid_column: frame grid column placement
# 8. option_menu: true or false if user wants a option list or not
# 9. option_list_values: list for option menu
# 10. option_label: name for option menu label
# 11. ipax, ipady: padding
# 12. comand: comand for option list
def __init__(self, name, label_default_values, entry_default_values, entry_width, I, grid_row, grid_column,
option_menu1, option_list_values, option_label,
option_menu2, option2_list_values,option_label2,
ipad_x, ipad_y
):
self.name = name
self.label_default_values = label_default_values
self.entry_default_values = entry_default_values
self.I = I
self.grid_row = grid_row
self.grid_column = grid_column
self.dropMenu_options = option_list_values
self.label = option_label
self.entry_width = entry_width
self.dropMenu_options2 = option2_list_values
self.option_label2 = option_label2
self.ipad_x = ipad_x
self.ipad_y = ipad_y
frame = tk.LabelFrame(window, text = name, highlightbackground='grey', highlightthickness=1)
frame.grid(row=self.grid_row, column=self.grid_column, padx=5, pady=5, ipadx=ipad_x, ipady=ipad_y)
if option_menu1 == "option_menu1_yes":
self.clicked = tk.StringVar()
self.clicked.set(self.dropMenu_options[0])
self.drop = tk.OptionMenu(frame, self.clicked, *self.dropMenu_options, command = self.add)
self.drop.grid(row=5, column=1, sticky="ew")
label = tk.Label(frame, text = option_label, highlightbackground='grey', highlightthickness=1)
label.grid(row = 5, column = 0, sticky = "w")
if option_menu2 == "option_menu2_yes":
self.clicked2 = tk.StringVar()
self.clicked2.set(self.dropMenu_options2[0])
self.drop2 = tk.OptionMenu(frame, self.clicked2, *self.dropMenu_options2)
self.drop2.grid(row=6, column=1, sticky="ew")
label = tk.Label(frame, text = option_label2, highlightbackground='grey', highlightthickness=1)
label.grid(row = 6, column = 0, sticky = "w")
self.entry ={}
for i in range(0, self.I):
label = tk.Label(frame, text = self.label_default_values[i], justify = "left")
label.grid(row=i, column=0, sticky = "w")
self.entry[i] = tk.Entry(frame, textvariable = float(self.entry_default_values[i]), width=self.entry_width)
self.entry[i].grid(row=i, column=1, sticky = "e")
self.entry[i].delete(0, tk.END)
self.entry[i].insert(0, self.entry_default_values[i])
c=Calculator(window)
window.mainloop()
There are 2 problems:
def add(self)
to def add(self, e)
and rename add()
to add(None)
. Then change lambda event: self.add
to self.add
AttributeError: 'frame' object has no attribute 'frame_camera'
but is not question related The method add
is in the Calculator
class, so instead of self.add
you need to call add
on the calculator. Since the frame doesn't know what the calculator is, you need to pass it in when constructing the frame.
Something like the following, where the calculator instance is passed as the first option:
self.frame_1 = frame(self, 'Test Frame 1', ...)
Next, you need to define your class to accept and save the reference to the calculator and then use it in the command
of the OptionMenu
:
class frame:
def __init__(self, calculator, name, ...):
self.calculator = calculator
...
self.drop = tk.OptionMenu(..., command = self.calculator.add)
Also, you define add
like this:
def add(self, e):
I assume that means you think the second parameter is an event object. It is not. It is the value that was picked from the optionmenu.
Arguably, a better way to define this would be to actually use this new value if provided, and fall back to calling get
if a value isn't provided. Also, you can reduce the wall of if statements into a single dictionary lookup to make the code shorter and more robust.
def add(self, new_value=None):
qe = self.frame_1.entry[1]
bandOption = self.frame_2.clicked.get() if new_value is None else new_value
band = {"B": 22, "V": 33}
qe.delete(0, "end")
qe.insert(0, band[bandOption])
This solution is 2/3 the size of your original, and more flexible and easier to maintain.
It works if I define add(event) outside classes.
def add(event):
qe = c.frame_1.entry[1]
bandOption = c.frame_2.clicked.get()
if bandOption == "B":
qe.delete(0,tk.END)
qe.insert(0,22)
elif bandOption == "V":
qe.delete(0,tk.END)
qe.insert(0,33)
And this in the frame class:
self.drop = tk.OptionMenu(frame, self.clicked, *self.dropMenu_options, command = lambda event:add(event))
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.