Tkinter: how to make functions in after() work with variable objects? - python

I'm a beginner programmer and I'm trying to learn how to make code run continuously in tkinter.
My aim for testing is just to get a label to change colour every 2 seconds regardless of input.
I understand I can use the following format (with lbl initialised as a Label)
def switch():
if lbl.cget('bg') == 'blue':
lbl.config(bg='black')
else:
lbl.config(bg='blue')
lbl.after(2000, switch)
This works fine. However, I want to be able to call switch for any label rather than just lbl specifically.
If I try the below I immediately get a recursion depth error
def switch(i):
if i.cget('bg') == 'blue':
i.config(bg='black')
else:
i.config(bg='blue')
i.after(2000, switch(i))
lbl.after(2000, switch(lbl))
I'm not sure why this is a the case, or what I could do to get round it so any help would be appreciated!!!

You can pass positional arguments to after. To run switch(i) after 2 seconds you can call after like this, adding the positional arguments after the function:
i.after(2000, switch, i)
Likewise, to run switch(lbl) do this:
lbl.after(2000, switch, lbl)

Related

Create a Tkinter window auto update without using "after()" function

I'm using Tkinter for a small overlay on a screen that got to be update every 1 or 2 second. I search a lot about it and find the after() function that could be execute after the mainloop. But this one doesn't work quitely, the idea is to call a after() function witch contain another after() function and the main function that we want to execute in the loop, like that :
def my_functions():
print('task done')
ws.after(1000, my_functions)
ws.after(1000, my_functions())
But this one got a limit of "function calling itself" of 992, that means my window will refresh her value only for 16 min 32 sec (or 992 sec) then will crash.
I could maybe destroy and recreate all my window and loop before the limit but I doesn't love this solution and I would have to work a lot on it, I would prefer a easyest solution, but I'm searching for 3 days and doesn't find anything good.
With trying to reproduce the following error :
RecursionError: maximum recursion depth exceeded while calling a Python object
I find the solution, so for any other I will write it.
Like you said, you can't use the after() method if you pass an argument like that:
root.after(1000, task(arg))
If you want to pass an argument you have to do like that:
root = Tk()
x = 0
def task(x):
x += 1
print(x)
root.after(2000, task, x) # reschedule event in 2 seconds
root.after(2000, task, x)
root.mainloop()
If you use the first solution, it's only rise the error after the of the maximum recursion, tkinter doesn't find any mistake in the formulation and you can still execute your code.
Which is a bit weird for me, but I just doesn't understand quitely the documentation when I read it. And with re-reading it seems logic now.

How to fix 'TypeError: MOVE() takes 0 positional arguments but 1 was given' in Python

Trying to make a simple drawing program based on x an y coordinates an i'm using a function to draw and modify the coordinates in one call without actually giving valuea to the function itself using global variables for what needs modification by the function but it still seees as if i've given it actual direct input.
In a previous version i got away by using a class to memorize the ghanging coordinates and functions of the respective class to do the drawing, but in this case the input method is slightly different since i'm using the scale widget isntead of the buttons and as i mentioned before i did try using global variables in both programs actually and it doesn't work in either of them.
from tkinter import *
win=Tk()
win.title("Etch a Schetch")
win.configure(background="grey")
win.configure(bd=5)
global xa
xa=0
global ya
ya=0
def MOVE():
tabla.create_line(xa,ya,sx.get(),sy.get())
xa=sx.get()
ya=sy.get()
tabla=Canvas(win,width=300,height=300)
sx=Scale(win,from_=0,to=300,length=300,orient="horizontal",command=MOVE)
sy=Scale(win,from_=0,to=300,length=300,command=MOVE)
ex=Button(win,text="Exit",command=exit)
tabla.grid(row=1,column=1)
sx.grid(row=2,column=1)
sy.grid(row=1,column=2)
ex.grid(row=2,column=2)
win.mainloop()
If it would work it would be kinda like an etch a sketch.
I actually just realized what the problem was, to quote mkiever who commented first but i didn't understand untill i did some individuall testing on the interaction between "Scale" and the command that is calling. To put it short and easier to understand the function that is beeing called by "Scale" as its command automaticly takes the value of the scale as an input to the function, as a rezult i only need one declared variable in the paranthesis when i define the function but no paranthesis or input variable is required to give an input to it from the scale.
EXAMPLE:
from tkinter import *
import time
win=Tk()
def P(a):
print(a)
sx=Scale(win,from_=0,to=300,length=300,orient="horizontal",command=P)
sx.pack()
win.mainloop()
Some alteration to the code is still required but it'll be much easier without that pesky error showing up.
Thank you everyone for your advice.

Issue with differentiating between two of the same items in a tkinter menu

The program I created allows for any letter on the keyboard the user types to be written on the turtle graphics canvas. In my program, I have also created a Python menu to which a Point object (for each letter function) is written to every time a user executes a function/draws a letter. However, because of the nature of my program, the user can also attach two of the same functions to the menu. If two of the same things get attached to the menu, for example two functions, I need a way to differentiate between them somehow. To do this, I have created a counter in another function and called that counter function in the function where the menu is written to, like so:
Counter function:
def increase():
if not hasattr(increase, "counter"):
increase.counter = 0
increase.counter += 1
Code block when menu is written to:
global loki
kli.config(state = NORMAL)
loki = ("{}".format(po.getfunction()))
increase() #<-- Counter function
undo1.add_command(label = str(increase.counter) + Point.__str__(po), command = lambda: selectundo(undo1.index(po)))
Point.__str__ is this method in the Point class:
def __str__(self):
return "({})".format(self.function)
However, I keep getting this error whenever I select something from the menu:
undo1.add_command(label = str(increase.counter) + Point.__str__(po), command = lambda: selectundo(undo1.index(po)))
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/tkinter/__init__.py", line 2782, in index
i = self.tk.call(self._w, 'index', index)
tkinter.TclError: bad menu entry index "(<function draw_O at 0x105834d90>)"
I am thinking it has something to do with the following function, which undoes the function that is selected from the menu, but I am not sure:
def selectundo(x):
# This function undoes the function selected from the menu
for ty in range(x, len(function)):
undoHandler()
update()
listen()
Although, before I concatenated str(increase.counter) to Point.__str__(po), it worked perfectly.
So, what am I doing wrong here? Any help at all is much appreciated! :)
EDIT: Just to clear up what I am trying to do and why, I am trying to differentiate between two (or more) of the same functions if they are written to the menu and I am doing this because of the selectundo function (shown above) since, for example, if the user draws two (or more) of the same letter, I need to be able to differentiate between them, because right now, when I can't, the selectundo function undoes ALL instances of that letter, NOT just the first instance of what is pressed on the menu, which is what I actually want the program to do. If what I am trying to do to accomplish the task is not possible or if there is a better way to accomplish the task, please tell be about any/the other way that I can use to accomplish the task. I hope this helps alleviate any confusion! :)

TypeError: printName1() takes 0 positional arguments but 1 was given

So I have this very simple thing I wrote and it's killing me trying to figure out why it won't work. All it does it print a statement when you click.
So for the first example I had a button and assigned the function printName1 directly to it, which worked perfectly fine.
Then the next thing was to bind it using the .bind() function. So in this case we just have a frame that prints out certain things based on which button you press. But unfortunately whenever I use bind, it throws the error show above. References tkinter\__init__.py for the error, so it's not something directly in my code but maybe it needs to be done differently? Thanks guys.
from tkinter import *
root = Tk()
def printName1():
print('Jack')
def printName2():
print('John')
def printName3():
print('Jill')
frame = Frame(root, width=300, height=250)
frame.bind("<Button-1>", printName1)
frame.bind("<Button-2>", printName2)
frame.bind("<Button-3>", printName3)
frame.pack()
root.mainloop()
EDIT: The error is confusing because it made it seem like there was an extra argument when there should be 0. But actually I needed to add an argument to the functions and that was event. so it should be def printName1(event) and so on. Just figured I would let you guys know what worked for me in case anyone stumbles upon this.
If you refer to the documentation regarding tkinter events and bindings, you will see that when an event is triggered, the associated event object will be passed as the first (and only) argument to the bounded function (being printName1 and friends in your case).
So what you need to do is to modify those printName* functions to accept the event argument.
def printName1(event):
print('Jack')
Then what you desired to achieve should work.
Naturally, you could make the event argument optional as #TigerhawkT3 suggested.
Events, such as from the keyboard/mouse, are all sent to the application with information about the event: which key was it, where was the mouse when you clicked, that sort of thing. This means that any callback bound to such an event needs to take an argument. If you want to also bind it to a Tkinter Button, which doesn't take an event, you can handle that as well. Just define your functions with a default argument:
def printName1(event=None):
...

python, number of arguments trouble

I have seen plenty of this question around, but I am still not able to see what I'm doing wrong.
I have several sliders, and I want the value of the one I am moving, and which of the slider is being moved.
to do that:
def reading(self,value):
sender=self.sender()
slider=sender.objectName()[6:]
value_slider=value
return slider, value_slider
That seems to work, the problem is with the next function.
Now, I want to do some stuff with the value of the slider moved:
def prsn(self,slider,value_slider):
wv=np.linspace(380,780,401)
leds=np.genfromtxt('led_psd.txt')
leds_norm=leds/leds.max()
Pot_ajust=0
for i in range(0,leds_norm.shape[1]):
Pot_ajust=Pot_ajust+value_slider*leds_norm[:,slider];
And I have the error : prsn() takes exactly 3 arguments (1 given)
How come I am not "giving" to prsn() 3 arguments? It is like it is not reading value_slider and slider
How should I pass value_slider and slider to the other functions?
Thank you very much for any tip
Here's how you should call prsn:
s = stuff
vs = other_stuff
thingy.prsn(s,vs)
OR
thingy.prsn(slider=s,value_slider=vs)
This means that in prsn's scope:
self = thingy
slider = s
value_slider = vs
Please for now on when you ask a question include the line of code that causes the exception.

Categories