How to use self and event in a class function? - python-3.x

No matter how I try it I keep getting the error that I need to send an argument to the event.
and yes before you say to go look at the documentation. I have looked at it but it doesn't help with what I'm doing because it is never inside a class.
from tkinter import *
class App(Tk):
def __init__(self):
Tk.__init__(self)
self.callback()
self.initUI()
self.mainloop()
def callback(self, event):
print("made it")
def initUI(self):
canvas = Canvas(self, height = 300, width = 300)
canvas.create_rectangle(1.5,1.5,40,40, tag = "obj1")
canvas.tag_bind("obj1", '<Button-1>', callback) #where I assume the problem is happening
A = App()
EDIT: callback keeps saying it needs another argument but I don't know what it needs

You have defined your callback to take two arguments: self (meaning, it's a method on an object) and event. When you call it directly with self.callback(), self gets automatically passed, but you aren't passing in an event. That is why you get the error saying it needs another argument. It expects two, but is only getting one.
It's unclear what you are attempting to do by directly calling your callback, but a quick fix is to make the event attribute optional so you can call the callback directly or via a binding. Of course, this will only work if your binding doesn't actually use the event parameter. If it does, when calling it without an event you can expect it to fail.
Here's an example of making the event parameter optional:
def callback(self, event=None):
print("made it")
That solves one problem, but you also have to change how your binding is defined or your code will crash when it starts up. You need to prepend self to the callback:
canvas.tag_bind("obj1", '<Button-1>', self.callback)
When callback is called, self again is automatically passed as the first argument. Tkinter will automatically add the event parameter.
Pedantically speaking, you should not be calling mainloop() inside of __init__. That prevents the App object from fully being constructed. The proper way to do it is to call mainloop outside of the constructor:
A = App()
A.mainloop()

Related

Understanding how binding a StringVar to an tkinter widget works with events

When I call a function from a tkinter widget event I need to add a second parameter in the definition line of the function. Like onChange(self,event) or onChange(self,test). "event" and "test" being the second parameter.
Like:
def onChange(self, test):
print('Content is: ', test.widget.get())
#this also works, but only when having "test" or any other other second parameter name in the def line:
print('Content is: ', self.var.get())
My problem as a beginner in Python/Tkinter is that I do not understand this second parameter and why it is needed. Do anyone have a good explaination of this and/or references to other sources to improve my understanding?
I also would like to understand why this function without the second parameter does not work.
def onChange(self):
print('Content is: ', self.var.get())
A small sample program:
import tkinter as tk
from tkinter import ttk
class GUI:
def __init__(self):
self.window = tk.Tk()
self.create_widgets()
# The following function works with "test" or any other second parameter name in the def line:
def onChange(self, test):
print('Content is: ', test.widget.get())
#this also works, but only when having "test" or any other other second parameter name in the def line:
print('Content is: ', self.var.get())
## The following function does not work:
# def onChange(self):
# print('Content is: ', self.var.get())
def create_widgets(self):
# Makes an Entry widget:
self.string_entry = ttk.Entry(self.window,width=30)
self.string_entry.grid(row=0,column=0)
self.var = tk.StringVar()
self.var.set('Change me and press enter!')
self.string_entry["textvariable"]=self.var
self.string_entry.bind('<Key-Return>', self.onChange)
program = GUI()
program.window.mainloop()
You are allready adding variables that are "extra".
the self variable that you add in the call signature is also a variable that gets filled by the system (python )when calling a class method.
In the same way when an callback is being called by tkinter by the an event, tkinter adds extra variables, in this case the event.
The event variable contains extra information on why the callback is called, this to enable the same callback to be able to handle several different events.
The page http://effbot.org/ is pretty old and is mainly describing Python2 code but it is in most cases update enough to learn about tkinter and how it works.
The Tk framework that calls your function, passes a variable to your callback function (as well as the self variable all methods are called with). When your second method is called it can't accept the second argument so throws a TypeError.
There is more information about the bind methods here.
If you don't care about the argument, you can use the varargs syntax to ignore anything that's passed in.
def onChange(self, *_):
print('Content is: ', self.var.get())

Python - can call same class twice(or more) in thread?

I don't very understand the classes logic in python but cannot answer on web.
I have create a class to generate person info:
class person:
def fristnameGen(gender):
...
def emailGen(firstname,surname):
...
i create a bot to call it like this:
from person import *
class bots:
def __init__(self):
self.person = person()
def createDB(self):
print(self.person.name)
#do something...
finally i call it by a button with thread
from bots import *
import threading
class Panel:
def __init__(self):
self.top = tk.Tk()
self.bot = bots()
self.buildUI()
def foo(self):
self.bot.createDB(self.stringPhone.get())
def threadTheAction(func, *args):
t = threading.Thread(target=func, args=args)
t.setDaemon(True)
t.start()
def buildUI(self):
Button = tk.Button(self.top, text ="Start", command = lambda :self.threadTheAction(self.foo))
I get this error:
TypeError: 'Panel' object is not callable
However, i call it directly, its work
Button = tk.Button(self.top, text ="Start", command = lambda :self.foo())
How to fix the error?
...
2. Moreover, i tried create p1 = person() and p2= person() and print it. Found p1 and p2 is a same person, i prefer each new a class have a new one. How to generate "new person" using classes?
Thank you
You seem to have a lot of confusion about Object Oriented Programming in Python. Some of your methods have self parameters and some do not, seemingly at random. That's the source of your current error.
The threadTheAction method in your Panel class is getting the Panel instance passed in as its first argument, but that's named func in the method (since you omitted self). The real function you're passing as an argument gets caught in the variable argument *args. When the thread tries unsuccessfully to call it, you get an exception. Adding self before func would fix the immediate problem:
def threadTheAction(self, func, *args):
I suspect if your code got further along, you'd run into other errors with other methods without self in their parameter lists. For instance, none of the methods you've shown in person are likely to work correctly.
As for your second question, you haven't shown enough of person to know what's happening, but you're probably doing instance variables wrong somehow. With no self parameter in the methods, that's almost inevitable (since you assign to self.whatever to set a whatever attribute on the current instance). If you need help squaring that away, I suggest you ask a separate question (Stack Overflow is best when each question is self-contained) and provide the full code for your person class.

Binding method with multiple arguments to <<ListboxSelect>>

Trying to make a method execute on the selection of an item in a tkinter listbox. I can bind some function to is using .bind("<<ListboxSelect>>", method), but if i try to give any arguments to it, like method(argumentone, argumenttwo), the method no longer executes on the selection of an item from the listbox. It is however executed at startup when Mainloop starts. Furthermore, giving arguments to a method is problematic, as i cant pass the event argument already passed to any bound method, as it is not assigned to any variable beforehand. I'd like to be able to pass the event and one other variable to some method ideally. Any help on achieving this would be very welcome. Some example code of what I'm trying to achieve
def onSelection(event, foo):
widget = event.widget
print(widget.curselection())
print(foo)
sam = "sam"
chars = Listbox(main)
chars.insert(someList)
chars.bind("<<ListboxSelect>>", onSelection(evt, sam))
Note that this code doesn't work. evt isn't defined.
When you specify a function in bind() that ends in parenthesis it just runs the function without bind generating an event. Instead you have to give bind a name. You can accomplish this with lambda or functools.partial. I'll use lambda in my example.
Bind is generating an event when triggered, so lambda will have to take it as input. Then call onSelection() with desired arguments.
from tkinter import *
main = Tk()
def onSelection(event, foo):
widget = event.widget
print(widget.curselection())
print(foo)
someList = ['Stilton', 'Brie', 'Edam', 'Cheddar', 'Ilchester']
chars = Listbox(main)
chars.pack(padx=10, pady=10)
for item in someList:
chars.insert("end", item)
sam = "sam"
chars.bind("<<ListboxSelect>>", lambda event: onSelection(event, sam))
# Take care of event created by bind ----^ ^
# Pass the event as well as argument to callback function ----|
main.mainloop()

tkinter function repeats itself twice when ttk widgets are engaged

The program works as intended when I simply use tkinter's widgets. When I use ttk's widgets the program repeats itself twice. I tried almost everything in my knowledge to fix this, I believe that *args have something to do with it. Is there anyway to prevent my function _up_options from running twice?
from tkinter import *
from tkinter import ttk
root = Tk()
first = StringVar(root)
second = StringVar(root)
Ore = {'Options': [''], 'Yes': ['One'], 'No': ['Two']}
entry1 = ttk.OptionMenu(root, first, *Ore.keys())
entry2 = ttk.OptionMenu(root, second, '')
entry1.pack()
entry2.pack()
def _up_options(*args):
print('update_options')
ores = Ore[first.get()]
second.set(ores[0])
menu = entry2['menu']
menu.delete(0, 'end')
for line in ores:
print('for')
menu.add_command(label=line, command=lambda choice=line: second.set(choice))
first.trace('w', _up_options)
root.mainloop()
PS, I used *args in my function to work. If anyone can explain this, I would be very grateful
I think I figured this out. The problem is that the variable actually is set twice by the ttk OptionMenu.
Take a look at this piece of code from the tkinter OptionMenu:
for v in values:
menu.add_command(label=v, command=_setit(variable, v, callback))
This adds a button to the menu for each value, with a _setit command. When the _setit is called it sets the variable and another callback if provided:
def __call__(self, *args):
self.__var.set(self.__value)
if self.__callback:
self.__callback(self.__value, *args)
Now look at this piece of code from the ttk OptionMenu:
for val in values:
menu.add_radiobutton(label=val,
command=tkinter._setit(self._variable, val, self._callback),
variable=self._variable)
Instead of a command this adds a radiobutton to the menu. All radiobuttons are "grouped" by linking them to the same variable. Because the radiobuttons have a variable, when one of them is clicked, the variable is set to the value of the button. Next to this, the same command is added as in the tkinter OptionMenu. As said, this sets the variable and then fires another command of provided. As you can see, now the variable is updated twice, once because it is linked to the radiobutton and once more because it is set in the _setit function. Because you trace the changing of the variable and the variable is set twice, your code also runs twice.
Because the variable is set twice from within the ttk code, I guess there's not much you can do about that. If you don't change the variable from any other part of your code than from the OptionMenu though, you could choose to not trace the variable, but instead add your function as command to the OptionMenu:
entry1 = ttk.OptionMenu(root, first, *Ore.keys(), command=_up_options)
P.S. this was introduced with this commit after this bugreport.
I guess when adding the variable=self._variable the command should have been changed to just command=self._callback.
You can understand the problem in the error message:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\user\AppData\Local\Programs\Python\Python36\lib\tkinter__init__.py", line 1699, in call
return self.func(*args)
TypeError: _up_options() takes 0 positional arguments but 3 were given
Initially, you don't use _up_options When you change the Options you call _up_options to trace the first StringVar and change it to the value of the next object in the dictionary.
Now when you do that you are running on all the objects in the dictionary, therefore, you need the *args so the lambda function will run on all args given!
As for your problem:
When I use ttk's widgets the program repeats itself twice.
EDIT
See #fhdrsdg's answer!
The solution is just to change command=tkinter._setit(self._variable, val, self._callback) to command=self._callback.
Hope you find this helpful!
Instead of tracing the StringVar, add a callback as command argument for OptionMenu constructor.
I created a subclass of ttk.OptionMenu to solve this (as well as to provide slightly simpler usage of the widget and a more useful callback). I think this is a more stable approach than modifying the original class directly or just overriding the original method because it guarantees compatibility with potential changes to the built-in/original widget in future Tkinter versions.
class Dropdown( ttk.OptionMenu ):
def __init__( self, parent, options, default='', variable=None, command=None, **kwargs ):
self.command = command
if not default:
default = options[0]
if not variable:
variable = Tk.StringVar()
if command:
assert callable( command ), 'The given command is not callable! {}'.format( command )
ttk.OptionMenu.__init__( self, parent, variable, default, *options, command=self.callBack, **kwargs )
else:
ttk.OptionMenu.__init__( self, parent, variable, default, *options, **kwargs )
def callBack( self, newValue ):
self.command( self, newValue )
You can then use it like this:
def callback( widget, newValue ):
print 'callback called with', newValue
print 'called by', widget
options = [ 'One', 'Two', 'Three' ]
dropdown = Dropdown( parent, options, command=callback )
dropdown.pack()
Besides avoiding the double-trace issue, other notable differences from the original ttk.OptionMenu includes not needing to supply a Tkinter variable or default value if you don't need them (the default item will be the first item in the options list if not provided), and being able to get the widget that called the callback function when it fires. The latter is very helpful if you have many dropdown widgets sharing the same callback and you need to know which one is being used within the call.
Soon after writing this, I also found another solution using lambda: Passing OptionMenu into a callback (or retrieving a reference to the used widget)
I thought I might still share this Dropdown widget anyway since it can make the higher-level code simpler, and it provides a good base if you have some other custom methods to add in.

Passing StringVar object from Entry to Label within functions in tkinter

Hi I've been struggling to get this to work, each time i change something I receive another error. I've been trying to create an entry box with a function and then get the variable from the entry box into a label, created by a button press. When I tried to do this often this error came up.
TypeError: get() missing 1 required positional argument: 'self'
I then put self in in the method brackets.
command = lambda: x.myFunc(self.my_variable.get(self))
Then another error, which I'm not sure how to sort out.
AttributeError: 'My_Class' object has no attribute '_tk'
Here's the full code, I'm new to classes and self, so any corrections are welcome.
from tkinter import *
import time
class My_Class:
def start(self):
self.root=Tk()
self.my_variable=StringVar
self.entry_box=Entry(self.root, textvariable=self.my_variable)
self.entry_box.pack()
self.button=Button(self.root,text="Pass variable now",
command=lambda:x.myFunc(self.my_variable.get(self)))
self.button.pack()
def myFunc(self,my_variable):
self.lab=Label(self.root,text=self.my_variable)
self.lab.pack()
x=My_Class()
x.start()
This is the correct way to create a StringVar object:
text = StringVar() # note additional ()
Can you explain me what x is in the following statement:
lambda: x.myFunc(self.my_variable.get(self))
x is not visible inside the class, because it's declared outside the class.
myFunc is not indented correctly: you should indent it like the __init__ method.
I really recommend you to watch some tutorials on OOP before proceeding. You are basically trying to guess how OOP works.
If you make myFunc A method if the class (which you might be trying to do; it's hard to know because your indentation is wrong), you don't have to pass anything to myFunc. That function has access to everything in the class, so it can get what it needs, when it needs it. That lets you eliminate the use of lambda, which helps reduce complexity.
Also, you normally don't need a StringVar at all, it's just one more thing to keep track of. However, if you really need the label and entry to show exactly the same data, have them share the same textvariable and the text is updated automatically without you having to call a function, or get the value from the widget, or set the value n the label.
Here's an example without using StringVar:
class My_Class:
def start(self):
...
self.entry_box = Entry(self.root)
self.button = Button(..., command = self.myFunc)
...
def myFunc(self):
s = self.entry_box.get()
self.lab = Label(..., text = s)
...

Resources