I am using a dropdown menu to create several widgets. I would like to check whether the widget exists (from a previous selection on the dropdown menu) before deleting it. I am using the following code:
self.ndim_options, self.ndim_options_var = self.DropdownMenuCommand(("1","2","3"),'-',"Number of indirect dimensions","-")
def DropdownMenuCommand(self,options,status,name,row):
if row == "-":
row = self.row
optionLabel = tk.Label(self.frame, bg='turquoise')
optionLabel["text"] = name
optionLabel.grid(row=row, column=0, sticky='w')
var = tk.StringVar(self)
var.set(status)
w = tk.OptionMenu(self.frame, var, *options, command = self.setdimensionproperties)
w.config(bg = 'paleturquoise')
w["menu"].config(bg = 'paleturquoise')
w.grid(row=row, column=1)
self.row += 1
return w, var
def setdimensionproperties(self,val):
row = self.RowEnd
if val == "3": #Set parameters for a 4D (3 indirect dimensions)
#Remove any existing weighting functions
if self.weightingFuncNameDim2.winfo_exists():
self.weightingFuncNameDim2.grid_remove()
self.weightingFuncNameDim2, self.weightingFuncNameDim2_var = self.DropdownMenu(("sinebell","gaussian", "sinebell2"),'-', "Weighting function dimension 2",row)
However, if the widget hasn't been created, I get an Attribute error:
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1413, in __call__
return self.func(*args)
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 3141, in __call__
self.__callback(self.__value, *args)
File "test.py", line 224, in setdimensionproperties
if self.weightingFuncNameDim2.winfo_exists():
AttributeError: Window instance has no attribute 'weightingFuncNameDim2'
Ideally I would like the if statement to return 1 if the widget exists and 0 if it doesn't, which was what I thought .winfo_exists() did. How can I get around this error? Also, how can I make sure that .grid_remove removes both the widget and the label?
I see three possible solutions. In order of preference:
Add self.weightingFuncNameDim2 = None to your class' __init__ method, and change your if condition to if self.weightingFuncNameDim2 is not None and self.weightingFuncNameDim2.winfo_exists():
Change your condition to if hasattr(self, "weightingFuncNameDim2") and self.weightingFuncNameDim2.winfo_exists():
Put your if block inside a try-except block that catches and ignores AttributeErrors.
...However, if the widget hasn't been created, I get an Attribute error:
The best solution is to make sure your class always has the attribute, even if the widget doesn't exist. Set it to None, and then reset it when you create the widget. Then your conditional becomes:
if self.weightingFuncNameDim2 is not None:
self.weightingFuncNameDim2.grid_remove()
how can I make sure that .grid_remove removes both the widget and the
label?
grid_remove will always only remove one widget from view. However, if that widget contains other widgets, those other widgets will also be removed from view.
Related
I am trying to limit the number of characters that can be input in a list of Entry widgets. I tried using the following:
def character_limit(entry_text):
if len(entry_text.get()) > 0:
entry_text.set(entry_text.get()[:10])
player_names = []
for i in range(num_players):
player_names.append(tk.StringVar())
player_names[i].trace("w", lambda *args: character_limit(player_names[i]))
player_name_entry = tk.Entry(top, textvariable=player_names[i])
player_name_entry.grid(row=i, column=0)
But this only limits the last Entry widget. How can I fix this?
The looping problem is a very commonly seen problem and to fix it, you have to store the current value of the iteration within the lambda itself:
def character_limit(*args, entry_text):
# if len(entry_text.get()) > 0: Can remove this line as it seems to not really be doing anything
entry_text.set(entry_text.get()[:10])
for i in range(num_players):
...
player_names[i].trace("w", lambda *args, i=i: character_limit(entry_text=player_names[i]))
The reason you use *args is because, trace passes 3 arguments to the function itself, that we don't need mostly.
But a more better method to do this will be to use validation for the entry widgets as this will prevent you needing to create a StringVar and trace its activity unnecessarily:
def character_limit(inp):
if len(inp) > 10:
return False
return True
player_names = []
vcmd = (root.register(character_limit), '%P')
for i in range(num_players):
player_name_entry = Entry(root, validate='key', validatecommand=vcmd)
player_name_entry.grid(row=i, column=0)
player_names.append(player_name_entry)
Read:
tkinter creating buttons in for loop passing command arguments
Interactively validating Entry widget content in tkinter
The problem is not related to the widget. Variable i is not local to the lambda functions, so the last value of i is used for every function.
To create local variables change your lambda into:
player_names[i].trace("w", lambda *args, n=i: character_limit(player_names[n]))
For a good description see https://docs.python.org/3/faq/programming.html#why-do-lambdas-defined-in-a-loop-with-different-values-all-return-the-same-result
This question already has an answer here:
TypeError: 'NoneType' object is not callable with tkinter
(1 answer)
Closed 1 year ago.
I'm trying to make a program that you can add infinite rooms to, so all of my code is built around using one variable to deduce which room is which. However when I run it, it gives me an error that doesn't directly reference any one line in my code, and since I'm a pretty new programmer, I don't know what it means. Also my code is pretty all over the place and incomplete. Thanks for any help!
The error
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\SCA0023\AppData\Local\Programs\Python\Python38\lib\tkinter\__init__.py", line 1892, in __call__
return self.func(*args)
TypeError: 'NoneType' object is not callable
The Code
from tkinter import *
root = Tk()
class Room:
def __init__(self, items):
self.objects = []
self.objects.append(items)
def list(self):
print(self.objects)
def addkitchenmenu(r): #add a new option menu attributed to a new room
globals()[f'kitchenvar_{r}'] = StringVar(root)
globals()[f'kitchenvar_{r}'].set("Add an appliance")
globals()[f'kitchenvar_{r}'].trace('w', applianceadd(r))
kitchenitems = ['Kettle', 'Toaster']
globals()[f'appliancelist_{r}'] = OptionMenu(root, globals()[f'kitchenvar_{r}'], *kitchenitems).pack()
addkitchen(r)
def applianceadd(r): #add a new room
globals()[f'kobjects_{r}'] = []
globals()[f'kobjects_{r}'].append(globals()[f'kitchenvar_{r}'].get())
items = globals()[f'kobjects_{r}']
globals()[f'kroom_{r}'] = Room(items)
globals()[f'kroom_{r}'].list()
def addkitchen(r): #add an appliance
globals()[f'addappliace{r}'] = Button(root, text='add appliance', command=lambda: applianceadd(r))
def newkitchencheck(): #find the next name for a room that isn't taken
varnotfound = True
a = 0
while varnotfound:
if f'kroom{a}' in globals():
a += 1
else:
r = a
varnotfound = False
addkitchenmenu(r)
addroombutton = Button(root, text="add kitchen", command=newkitchencheck)
addroombutton.pack()
root.mainloop()
You are passing result of applianceadd(r) (which is None) to .trace(). Change to .trace("w", lambda *_: applianceaddr(r)).
I'm brand new at python, and didn't understand the other answers for this question. Why when I run my code, does int(weight[0]) not convert variable "weight" into a integer. Try your best to dumb it down because I'm really new and still don't quite understand most of it. Here is the relevant section of my code
weight = (lb.curselection())
print ("clicked")
int(weight[0])
print (weight)
print (type(weight))
and heres my code for this script
lb = Listbox(win, height=240)
lb.pack()
for i in range(60,300):
lb.insert(END,(i))
def select(event):
weight = (lb.curselection())
print ("clicked")
int(weight[0])
print (weight)
print (type(weight))
lb.bind("<Double-Button-1>", select)
Thanks
When I run the code, it comes up with TypeError: int() argument must be a string, a bytes-like object or a number, not 'tuple'
and I want it instead to convert the "weight" variable into a integer, so I can use it for math operations.
Full Traceback:Traceback (most recent call last):
File "C:\Users\Casey\AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1699, in __call__
return self.func(*args)
File "C:/Users/Casey/AppData/Local/Programs/Python/Python36-32/s.py", line 11, in select
int(weight)
TypeError: int() argument must be a string, a bytes-like object or a number, not 'tuple'
what you're looking for is
weight = int(weight[0])
int is a function that returns an integer, so you have to assign that return to a variable.
if what you're looking for is to reassign the variable weight with the value of its first record, that code should work for you.
If the item is already an integer then the int call might be redundant, you might be able to get it with just
weight = weight[0]
I noticed you were using lb.bind("<Double-Button-1>", select) here. This does get around the issue with curselection() returning the last selected list item but I would say using lb.bind('<<ListboxSelect>>', select) would work better for this. Binding to <<ListboxSelect>> works because this event triggers after the selection has changed and when you go to call curselection() using this event instead you will get the correct output you are looking for.
Here is a bit of code that provides an example use of the <<ListboxSelect>> event:
import tkinter as tk
class Application(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.lb = tk.Listbox(self.parent, height=4)
self.lb.pack()
self.lb.bind('<<ListboxSelect>>', self.print_weight)
for item in ["one: Index = 0", "two: Index = 1", "three: Index = 2", "four: Index = 3"]:
self.lb.insert("end", item)
def print_weight(self, event = None):
# [0] gets us the 1st indexed value of the tuple so weight == a number.
weight = self.lb.curselection()[0]
print(weight)
if __name__ == "__main__":
root = tk.Tk()
app = Application(root)
root.mainloop()
You will notice the print out in the console will be the current selected item on a single click. This will prevent the need for a double click.
So i'm making a hangman game in tkinter and I'm trying to make the buttons disappear when pressed and reappear when the restart button is pressed. I'm still learning to code and have tried to adjust it to include classes to make the code clearer. This is the code I've used to create the button widgets:
class Buttons:
def __init__(self, letter, column, row):
self.letter = letter
self.column = column
self.row = row
def create(self):
column = self.column
row = self.row
letter = self.letter
self = tk.Button(window, text=self.letter, bg = colour, font=FONT,
command=lambda : check(letter))
self.place(relx=column, rely=row)
And then I place the buttons like this and it all works fine:
A = Buttons('A', column1, row1).create()
What I want to do however is access the 'self.letter', 'self.row', and 'self.column' outside of the class definition, however it says that the object is a Nonetype when I try to use 'A.letter' and it has no attribute letter.
Traceback (most recent call last):
File "C:\Users\cameron\Documents\Python\Hangman\Hangman v2.1.py", line 227, in <module>
print(A.letter)
AttributeError: 'NoneType' object has no attribute 'letter'
Does anyone know how I could access this?
Thank you
You don't need to reassign self. Try this:
def create(self):
#column = self.column # don't need these
#row = self.row
#letter = self.letter
self.tkbutton = tk.Button(window, text=self.letter, bg = colour, font=FONT, command=lambda : check(letter))
# Stashes the tk.Button instance away in `self.tkbutton`.
self.tkbutton.place(relx=self.column, rely=self.row)
self stays the same all the time, so you should never say self = <whatever>. Instead, use self.<some field name> = <whatever value>. Then the <some field name> will be accessible from outside as A.<some field name>.
Then, to use the button, try
A = Buttons('A', column1, row1) # calls A.__init__()
A.create()
# at this point A.tkbutton exists and is accessible.
Although a better approach might be to combine create's work into __init__ so that the button is created and placed in the A=Buttons(...) call.
Edit The reason you got a NoneType error is that create() does not include a return statement. Therefore, its return value is None. As a result, A=Buttons(...).create() returns None, i.e., a NoneType.
I'm having problems with my Tkinter Entry widget.
I'm just testing things out and would like to have my callback print out whatever I typed out in Entry self.a. but I'm getting this error.
File "C:\Python27\lib\lib-tk\Tkinter.py", line 1410, in call
return self.func(*args) File "C:/Users/Andy/testimage.py", line 146, in get
print a.get(self) NameError: global name 'a' is not defined
I was wondering if someone can tell me what I'm doing wrong. I linked the callback function correctly because if I make it print "aasdfasd" instead, it will print that when I press the button.
def clicked_wbbalance(self):
self.top = Toplevel()
self.top.title("LASKJDF...")
Label(self.top, text="Enter low level").grid(row=0, column=0,padx=10)
Label(self.top, text="Enter high level").grid(row=1, column=0,padx=10)
Label(self.top, text="Values must be between 0 to 255").grid(row=3, column=0)
Button(self.top, text="Ok", command=self.get).grid(row=3, column = 1)
self.a =Entry(self.top).grid(row=0, column=1,padx=10)
self.b =Entry(self.top).grid(row=1, column=1,padx=10)
def get(self):
print self.a.get(self)
As RocketDonkey pointed out, your traceback does not match the code you posted.
Your code as written will generate a traceback like this:
AttributeError: 'NoneType' object has no attribute 'get'
The root problem is that grid returns None. That means that attributes a and b will be None because they are assigned the result of calls to grid. Fix that by puting object creation and widget placement on different lines:
self.a = Entry(self.top)
self.b = Entry(self.top)
self.a.grid(row=0, column=1,padx=10)
self.b.grid(row=1, column=1,padx=10)
You traceback says print a.get(self) NameError: global name 'a' is not defined, but the code you posted uses the syntax print self.a.get(self) (which would appear to be correct). Therefore if you check on line 146, you aren't prefacing a with self, meaning that instead of referencing the property a of the instance, you are trying to reference a on its own, which is not defined. Try adding self in front of a on line 146 and see if the problem continues.