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.
Related
I have a list named chosenTestHolder (imported from the my_config file) that consists of several objects each with the attribute 'sentence'.
When pressing the button 'Press' for the first time, the attribute 'sentence' of the first object in the chosenTestHolder should be displayed in the text widget. The next time the button 'Press' is pressed the attribute 'sentence' of the second object in chosenTestHolder should be displayed and so on.
I am using lambda event for binding the 'Press' button and tries to use a new sentences as its first arguments after each pressing of the 'Press' button. However, it keeps showing the first sentence.
When searching Stackoverflow I have seen in
Using lambda function to change value of an attribute that you can't use assignments in lambda expressions but by reading that I still have not figured out how to solve my problem.
Grateful for help! Code is below!
main.py
from tkinter import font
import tkinter as tk
import tkinter.ttk as ttk
import my_config
import Testlist as tl
class TestWidgetTest:
def __init__(self):
ram = tk.Frame(root)
ram.grid(in_=root,row=0, column=0)
self.myText = tk.Text(ram, height = 5)
self.myText.grid(row=0,column=1)
my_config.counter = 0
self.myButton = tk.Button(ram, text = 'Press')
self.myButton.grid(row =1, column =0, columnspan =2)
indata =[my_config.chosenTestHolder[my_config.counter] , self.myText]
self.myButton.bind('<ButtonRelease-1>',lambda event, arg=indata : self.TagConfigure(event, arg))
def TagConfigure(self, event, arg):
arg[1].delete('1.0',tk.END)
arg[1].insert('1.0',arg[0].sentence)
my_config.counter += 1
root = tk.Tk()
TestWidgetTest()
root.mainloop()
my_config.py
import Testlist as tl
testListHolder = [ ['Fabian was very tired'],
['Thomas light the fire'],
['Anna eat a red apple ']]
chosenTestHolder = []
count = 0
while count <(len(testListHolder)):
chosenTestHolder.append(tl.Testlist(testListHolder[count][0]))
count += 1
counter = 0
Testlist.py
class Testlist:
def __init__(self, sentence):
self.sentence = sentence
Your issue is the assignment of indata.
You do only assign in init.
To get your code working you need to re-configure your sentecte...
indata =[my_config.chosenTestHolder[my_config.counter] , self.myText]
self.myButton.bind('<ButtonRelease-1>',lambda event, arg=indata : self.TagConfigure(event, arg))
I would advise to keep track of the current sentence as an instance variable.
class Test_widget(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, args, kwargs)
self.sentences=["a", "b", "c", "d"] # the data
self.show = tk.StringVar() # the current displayed data
self.show.set("NULL")
self.counter=0 # the indexer
tk.Label(self, textvariable=self.show).grid(row=0)
tk.Button(self, command=self.click).grid(row=1)
def click(self, event):
self.show.set("%s"%self.sentences[self.counter]) # use the indexer to access the data
self.counter = self.counter + 1 # modify the indexer
if self.counter = len(self.sentences): # make sure you dont run in index-err
self.counter = 0
As you see, there is no need at all for the lambdas.
Edit
As to your questions:
The change in your original code was not intended.
I do not see a use case where you can use a lambda for its use inside your code.
At least none where a lambda is necessary.
Please remember to use lambda only and exclusively if there are
no ( == NULL ) other options.
Using inheritance (thats what the mechanism is called), you can inherit functions, "default" behaviour from other classes. It is a common mechanism in programming and not exclusive to python.
It is used like any normal object except you have to call the constructor of the base class (what I do using tk.Frame.__init__(self, args, kwargs) inside the init method. For more information on inheritance please refer to the uncounted manuals and tutorials available for that topic (google is your friend now that you know what the mechanism is called).
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.
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.
I am working on an application that is supposed to support both running from a console and from a GUI. The application has several options to choose from, and since in both running modes the program is going to have the same options obviously, I made a generalisation:
class Option:
def __init__(self, par_name, par_desc):
self.name = par_name
self.desc = par_desc
class Mode():
def __init__(self):
self.options = []
self.options.append(Option('Option1', 'Desc1'))
self.options.append(Option('Option2', 'Desc2'))
self.options.append(Option('Option3', 'Desc3'))
self.options.append(Option('Option4', 'Desc4'))
self.options.append(Option('Option5', 'Desc5'))
#And so on
The problem is that in GUI, those options are going to be buttons, so I have to add a new field to an Option class and I'm doing it like this:
def onMouseEnter(par_event, par_option):
helpLabel.configure(text = par_option.desc)
return
def onMouseLeave(par_event):
helpLabel.configure(text = '')
return
class GUIMode(Mode):
#...
for iOption in self.options:
iOption.button = Button(wrapper, text = iOption.name, bg = '#004A7F', fg = 'white')
iOption.button.bind('<Enter>', lambda par_event: onMouseEnter(par_event, iOption))
iOption.button.bind('<Leave>', lambda par_event: onMouseLeave(par_event))
#...
There is also a "help label" showing the description of the option every time a mouse hovers over it, so there I am binding those functions.
What is happening is that while I am indeed successfully adding a new field with a button, the bind function seems to mess up and the result is this:
Help label is always showing the description of the last option added, no matter over which button I hover. The problem seems to go away if I directly modify the Option class instead, like this:
class Option:
def __init__(self, par_name, par_desc):
self.name = par_name
self.desc = par_desc
self.button = Button(wrapper, text = self.name, bg = '#004A7F', fg = 'white')
self.button.bind('<Enter>', lambda par_event: onMouseEnter(par_event, self))
self.button.bind('<Leave>', lambda par_event: onMouseLeave(par_event))
But I obviously can't keep it that way because the console mode will get those fields too which I don't really want. Isn't this the same thing, however? Why does it matter if I do it in a constructor with self or in a loop later? I therefore assume that the problem might be in a way I dynamically add the field to the class?
Here is the full minimal and runnable test code or whatever it is called, if you want to mess with it: http://pastebin.com/0PWnF2P0
Thank you for your time
The problem is that the value of iOption is evaluated after the
for iOption in self.option:
loops are complete. Since you reset iOption on each iteration, when the loop is completed iOption has the same value, namely the last element in self.options. You can demonstrate this at-event-time binding with the snippet:
def debug_late_bind(event):
print(iOption)
onMouseEnter(event, iOption)
for iOption in self.options:
iOption.button = Button(wrapper, text = iOption.name,
bg = '#004A7F', fg = 'white')
iOption.button.bind('<Enter>', debug_late_bind)
which will show that all events that iOption has the same value.
I split out the use of iOption to debug_late_bind to show that iOption comes in from the class scope and is not evaluated when the bind() call is executed. A more simple example would be
def print_i():
print(i)
for i in range(5):
pass
print_i()
which prints "4" because that is the last value that was assigned to i. This is why every call in your code to onMouseEnter(par_event, iOption) has the same value for iOption; it is evaluated at the time of the event, not the time of the bind. I suggest that you read up on model view controller and understand how you've tangled the view and the controller. The primary reason this has happened is that you've got two views (console and tk) which should be less coupled with the model.
Extracting the .widget property of the event is a decent workaround, but better still would be to not overwrite the scalar iOption, but instead use list of individual buttons. The code
for n, iOption in enumerate(self.options):
would help in creating a list. In your proposed workaround, you are encoding too much of the iOption model in the tkinter view. That's bound to bite you again at some point.
I don't know what the actual problem was with my original code, but I kind of just bypassed it. I added a dictionary with button as a key and option as a value and I just used the par_event.widget to get the option and it's description, which is working fine:
buttonOption = {}
def onMouseEnter(par_event):
helpLabel.configure(text = buttonOption[par_event.widget].desc)
return
def onMouseLeave(par_event):
helpLabel.configure(text = '')
return
class GUIMode(Mode):
def run(self):
#...
for iOption in self.options:
iOption.button = Button(wrapper, text = iOption.name, bg = '#004A7F', fg = 'white')
iOption.button.bind('<Enter>', lambda par_event: onMouseEnter(par_event))
iOption.button.bind('<Leave>', lambda par_event: onMouseLeave(par_event))
buttonOption[iOption.button] = iOption
#...
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.