Updating the Tkinter button error with code structure Explanation required - python

So I'm currently teaching myself python, and I am developing a simple GUI restaurant menu. But I came across an error I'm hoping someone could explain, I've solved it by doing self.total_button by option, rather than putting the options in parentheses (like I did for the button above it), but the only thing I have changed is that and the program runs with no errors.
The line in the total method self.total_button["text"] = "Total £"+ str(self.cash_total) only works when I declare self.total_button the way I do, if I declare each option in parentheses it states a Nonetype' object does not support item assignment.
Does layout matter with buttons in tkinter when updating them later in a program?
#Order Up!
#A Simple GUI program, that presents a simple restaurant menu.
#It lists items, and prices.
#Let the user select different items and then show the user the total bill.
from tkinter import *
class Application(Frame):
"""Create a GUI application."""
def __init__(self, master):
"""Initialises the frame"""
super(Application, self).__init__(master)
self.grid()
self.cash_total = 0
self.total_list = []
self.create_widgets()
def create_widgets(self):
"""Creates the widgets that creates the menu ordering systems"""
#Create a instruction label
Label(self,
text = "Click the desired buttons to order something"
).grid(row = 0, column = 0, columnspan = 4, sticky = N)
#Create Burger button
Button(self,
text = "Hamburger no bun",
command = self.hamburger_no_bun
).grid(row = 1, column = 0, sticky = W)
#Creates a total button
#Super weird bug have to set it out like this so i can use the total method later.
self.total_button = Button(self)
self.total_button["text"] = "Total: £"
self.total_button["command"] = self.total
self.total_button.grid(row = 5, column = 5, sticky = W)
#Create Text Box to show current order
self.order_txt = Text (self, width = 100, height = 8, wrap = WORD)
self.order_txt.grid(row = 1, column = 5, sticky = W)
def hamburger_no_bun(self):
"""Creates a hamburger tuple to be added to the list."""
self.hamburger_no_bun = ("Hamburger no bun, £", 2.95)
self.total_list.append(self.hamburger_no_bun)
self.order_txt.insert(0.2, str(self.hamburger_no_bun))
def total(self):
#The affected method
"""Adds the total amount due taken from the total_list"""
for i in self.total_list:
self.cash_total += i[1]
print(self.cash_total)
self.total_button["text"] = "Total £"+ str(self.cash_total)
self.cash_total = 0
#main
root = Tk()
root.title("Order Up! - A Restaurant Menu GUI")
app = Application(root)
root.mainloop()

You probably wrote code like this
self.total_button = Button(self, self, text="Total: £", command=self.total).grid(row = 5, column = 5, sticky = W)
You may be suprised to learn that self.total_button now holds the value None. This is because it is holding the return value of the grid method of the Button not the button reference itself.
Later when you try to use self.total_button it will throw an exception, because the value is None and None has no attribute "Text".
To resolve the issue you must correctly capture the reference to the button and to do this split the line creating and setting up the button into two lines.
self.total_button = Button(self, text="Total: £", command=self.total)
self.total_button.grid(row = 5, column = 5, sticky = W)
Now you have a correct reference to the button, that will be usable later in the total method.

Related

How to make python - tkinter dropdown command update after event

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()
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.
There are 2 problems:
The first one is that you mentioned and to fix it:
rename def add(self) to def add(self, e) and rename add() to add(None). Then change lambda event: self.add to self.add
The second one is:
AttributeError: 'frame' object has no attribute 'frame_camera'
but is not question related
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))

Tkinter entry getting text entered by user

I am very new to Tkinter ( I find it very difficult to learn). I have a python script working based on user input. I would like to wrap a GUI around it and eventually put it on web. In any case for user input I would like to get this from the GUI with a combination of Entry widgets and some buttons. First thing is I was reading and some people mentioned to use a class so I have the following. I have a few questions
I would like to check to see if indeed the users entered a value before he hits the GO button. How do I do this?
I would like the value entered to be made accessible by the rest of the program in the main body. How do I do this?
Thanks,
from Tkinter import *
class MainWindow():
def get_fc(self):
a = self.fc_gui.get()
return a
def __init__(self, master):
self.master = master
self.master.title('TEST')
self.fc_gui = DoubleVar(self.master, value = 500.00)
self.fclabel1 = Label(self.master, text = 'Please Enter a value', fg = 'black', bg = 'yellow')
self.fclabel1.grid(row = 0, column = 0)
self.fcedit1 = Entry(self.master, textvariable = self.fc_gui, bd = 5 )
self.fcedit1.grid(row = 1, column = 0)
fcbutton1 = Button(self.master, text='GO', command = self.get_fc)
fcbutton1.grid(row = 1, column = 1)
master = Tk()
MainWindow(master)
master.mainloop()
It doesn't make sense to return to a Button. The Button can't do anything with the value. Instead, save the value as an instance variable.
You don't have a mainloop().
You can't really check if the user entered a value before they hit "Go" - at the start of the program, of course they haven't entered anything yet. If you needed to track the contents of this field, there are ways to do that, but it's not necessary for a simple validation. Just check the value when they hit the button.
from Tkinter import *
class MainWindow():
def get_fc(self):
a = self.fc_gui.get()
if a: # this block will execute if a has content
self.a = a # save it for future use
def __init__(self, master):
self.master = master
self.master.title('TEST')
self.fc_gui = DoubleVar(self.master, value = 500.00)
self.fclabel1 = Label(self.master, text='Please Enter a value',
fg = 'black', bg = 'yellow')
self.fclabel1.grid(row = 0, column = 0)
self.fcedit1 = Entry(self.master, textvariable = self.fc_gui, bd = 5 )
self.fcedit1.grid(row = 1, column = 0)
fcbutton1 = Button(self.master, text='GO', command = self.get_fc)
fcbutton1.grid(row = 1, column = 1)
master = Tk()
MainWindow(master)
master.mainloop() # don't forget mainloop()

Why is this code working I took from a textbook for GUI Python?

Why isn't this working. This is straight from the text book. I'm getting an Attribute error saying self._area does not exist.
from Tkinter import *
import math
class CircleArea(Frame):
def __init__(self):
"""Sets up a window and widgets."""
Frame.__init__(self)
self.master.title("Circle Area")
self.grid()
#Label and field for radius
self._radiusLabel = Label(self, text = "Radius")
self._radiusLabel.grid(row = 0, column = 0)
self._radiusVar = DoubleVar()
self._radiusEntry = Entry(self, textvariable = self._radiusVar)
self._radiusEntry.grid(row = 0, column = 1)
#Label and field for the area
self._areaLabel = Label(self, text = "Area")
self._areaLabel.grid(row = 1, column = 0)
self._areaVar = DoubleVar()
self._areaEntry = Entry(self, textvariable = self._areaVar)
self._areaEntry.grid(row = 1, column = 1)
# The command button
self._button = Button(self, text = "Compute", command = self._area)
self._button.grid(row = 2, column = 0, columnspan = 2)
def _area(self):
"""Event handler for button."""
radius = self._radiusVar.get()
area = radius ** 2 * math.pi
self._areaVar.set(area)
def main():
CircleArea(). mainloop()
run = CircleArea()
run.main()
Is it because the _area method is declared after it is called? That doesn't make sense why it wouldn't work using a down up programming technique. I'm really new to GUI just started learning. First chapter on GUI for class.
edit*: I'm expecting a window to pop up and have one Entry field for input for the radius of the circle. With a label Radius. And an output entry field for the results of the area of the circle based on the radius. and a compute button at the bottom which computes it.
And I just wanted to get used to typing the different commands and such. I haven't even been in the lecture for this yet. I was just seeing what this code would do and what it would look like. I typed it all out by hand if that makes you feel better.:P Instead of copy and pasting.
The problem is that your indenting is wrong. _area and main are defined within __init__, which you don't want. Correct indenting is below (you don't need a main function).
from Tkinter import *
import math
class CircleArea(Frame):
def __init__(self):
"""Sets up a window and widgets."""
Frame.__init__(self)
self.master.title("Circle Area")
self.grid()
#Label and field for radius
self._radiusLabel = Label(self, text = "Radius")
self._radiusLabel.grid(row = 0, column = 0)
self._radiusVar = DoubleVar()
self._radiusEntry = Entry(self, textvariable = self._radiusVar)
self._radiusEntry.grid(row = 0, column = 1)
#Label and field for the area
self._areaLabel = Label(self, text = "Area")
self._areaLabel.grid(row = 1, column = 0)
self._areaVar = DoubleVar()
self._areaEntry = Entry(self, textvariable = self._areaVar)
self._areaEntry.grid(row = 1, column = 1)
# The command button
self._button = Button(self, text = "Compute", command = self._area)
self._button.grid(row = 2, column = 0, columnspan = 2)
def _area(self):
"""Event handler for button."""
radius = self._radiusVar.get()
area = radius ** 2 * math.pi
self._areaVar.set(area)
run = CircleArea()
run.mainloop()
Actually I think you miss an argument in your main method,you define a class CircleArea , but in python you know that, each method defined in class must have an default argument named 'self',so just try this
def main(self):
CircleArea(). mainloop()
I think it will work as you wish :)

how to restart a program in tkinter

I am programming a simple game using tkinter and want an option at the end to be able to replay the game, but I am not sure how I would go about doing it. I would like to be able to go back to the beginning where i define init. Also is there a way I could do this and not have to quit it so I could save scores as the player keeps playing.
from tkinter import *
import random
class App:
def __init__(self, master):
player_dice = []
for i in range(1,6):
x = random.randint(1,6)
player_dice.append(x)
self.label = Label(master, text = x , fg = "red").grid(row =0, column =i+1)
self.label = Label(master, text = "Dice:" , fg = "red").grid(row =0, column =1)
self.hi_one = Button(master, text="one", command= lambda: say_one(player_dice)).grid(row = 1, column = 1)
self.hi_two = Button(master, text="two", command= lambda: say_two(player_dice)).grid(row = 1, column = 2)
Here is where I would like to add a button to loop back to init.
def num(var, die, dlist):
new_window = Toplevel(root)
if die == 1:
if guess == total:
result = Message(new_window, text = "You Win").grid(row = 1, column = 1)
else:
result = Message(new_window, text = "You Lose").grid(row = 1, column = 1)
restart = Button(new_window, text = "Play Again", command = ?????).grid(row=1, column = 2)
Easiest way:
Just add a function resetBoard that resets your game.
Obviously there are various parts of the UI that don't need to be re-set, so those can go into __init__, the rest can go into resetBoard() and you can (possibly) call resetBoard() from within __init__.
Correct way:
Implement an MVC or MVP pattern: Separate your data and logic from your UI. Your view (UI) should reflect whatever is in your model, then reseting the game is just a question of reseting the model and firing the correct events so the view is updated (highly simplistic, but the very useful model-view-XXX patterns cannot be properly explained in just a few words.)

Tkinter Radiobutton spawns frames

I'm new to this GUI business with python2.7 and Tkinter. I'm trying to create a new frame depending on which Radiobutton the user choose, like a menu. When I click on a radiobutton it creats a new frame just like I want, but if I continue to click on the same radiobutton, it will create another frame, and another frame, etc. Can't seem to figure out on how to check if the Radiobutton is already marked (clicked on just once).
Hope I made myself clear, thankful for help!
class Books:
""" Books() is the main class for creating the whole interface """
def __init__(self):
""" Initialize the first function in class Books() """
self.library = "library.txt"
self.filepath = os.getcwd() + "/" + self.library
self.window = Tk()
self.window.title("Personal library")
self.window.wm_iconbitmap(default="myicon.ico")
userChoice = Frame(self.window, height = 1, bd = 1, relief = RIDGE)
userChoice.pack(side = TOP, pady = 10, padx = 5)
self.menuChoice = IntVar()
btAddBooks = Radiobutton(userChoice, text = "Add a new book to the library", value = 1, variable = self.menuChoice, command = self.processChoice)
btAddBooks.grid(row = 1, sticky = W)
btFindBooks = Radiobutton(userChoice, text = "Print info about a book", value = 2, variable = self.menuChoice, command = self.processChoice)
btFindBooks.grid(row = 2, sticky = W)
btPrintBooks = Radiobutton(userChoice, text = "Print all book titles in library", value = 3, variable = self.menuChoice, command = self.processChoice)
btPrintBooks.grid(row = 3, sticky = W
def processChoice(self):
""" Used to handle user choice of Radiobuttons """
if self.menuChoice.get() == 1:
self.processAddBooks()
elif self.menuChoice.get() == 2:
self.processFindBook()
elif self.menuChoice.get() == 3:
self.processShowBooks(self.filepath)
def processAddBooks(self):
""" Add a new book to the library. """
# Create a new frame
questions = Frame(self.window, height = 1, bd = 1, relief = SUNKEN)
questions.pack(fill = X, pady = 10, padx = 5)
# Do stuff with frame here...
Well, if you only need one frame to be open at a time, you can call frame.destroy() on the previous frame before you instantiate the new frame. However, this approach will require that there be something initialized for Tkinter to destroy the first time one of the buttons is selected, otherwise you'll get an error. For my purpose, I just created a throwaway class with a destroy method that did nothing, then used an instance of that class as a placeholder bound to that variable until my Toplevel widget was created for the first time. If you want multiple frames open at the same time, just not duplicates of the same option, try using a different variable name for each frame and only creating the frame if not frame.winfo_exists()--though I'm not 100% sure this wouldn't be susceptible to the same issue of needing a placeholder assigned to that variable until the frame is created the first time. If such is needed, the placeholder class would need a winfo_exists() method that would return False.

Categories

Resources