Issue with passing in variables with and event in python tkinter - python

I am writing a simple 3d simulator with tkinter and I am encountering a problem with passing arguments through a function bound to an event.
my function is
def left(event):
cam1 = 0
cam2 = 0
def render( cam1, cam2):
w.delete("all")
cam1 = cam1 + 5
cam2 = cam2 + 2
lol = 0
w.create_rectangle(10+cam1,10+cam1,190+cam1,190+cam1)
w.create_rectangle(40+cam2,40+cam2,160+cam2,160+cam2)
w.create_line(10+cam1,10+cam1,40+cam2,40+cam2)
w.create_line(40+cam2,160+cam2,10+cam1,190+cam1)
w.create_line(160+cam2,160+cam2,190+cam1,190+cam1)
w.create_line(190+cam1,10+cam1,160+cam2,40+cam2)
w.after(10,render(cam1,cam2))
i want to be able to have cam1 and cam2 not be 0 every time i call the function but it wont let me pass in more arguments with the event, i use
w.bind("<Right>", right)
to bind it

The after call is incorrect. You must give it a reference to a function. Additional arguments can be given. In your case it would be:
w.after910, render, cam1, cam2)
As for the binding, the most common solution is to use lambda or functools.partial. For example:
w.bind("<Right>", lambda event, arg1="something", arg2="something else": right(arg1, arg2))
Variations of this question have been asked dozens of times on stackoverflow. Try searching this site for [tkinter] bind argument. Your question should probably be marked as a duplicate, but it's hard to know which question is the best original question.

Related

How to use a LifoQueue for function calls?

I am making a Sudoku solver and want to make the instructions go one at a time, in a LIFO structure to resemble how most (or at least I) play Sudoku.
The relevant parts of my solver class -
class SudokuSolver():
possible_answers = None
def __init__(self, board):
self.board = board
self.instruction_stack = LifoQueue()
...
def work_on_group(self, group_num):
print(f"working on group {group_num}")
def work_on_row(self, row):
print(f"working on row {row}")
def work_on_column(self, column):
print(f"working on column {column}")
def do_next_step(self):
if self.instruction_stack.empty():
self.instruction_stack.put(self.work_on_group(9))
self.instruction_stack.put(self.work_on_group(8))
...
self.instruction_stack.put(self.work_on_group(2))
self.instruction_stack.put(self.work_on_group(1))
self.instruction_stack.pop()
Where eventually my work_on_group, work_on_row and work_on_column functions will have criteria that will add instructions to the stack as results unfold. However, right now, what I am getting when I try do_next_step() is
working on group 9
working on group 8
...
working on group 2
working on group 1
So it seems like my functions are evaluating as I put them into the stack instead of waiting for me to pop them.
Ideally, what I would see when this runs correctly, is only
working on group 1
since that's the last instruction given, and I only have one pop command.
One other thing of note - those these three functions only need one paramter, I can see more complex functions based on the board that would require more parameters, and would like my stack/pop to be able to handle that - store the function reference and then run with with an arbitrary number of *args **kwargs. How can I accomplish this?
Edit:
I've come up with this which works and I think would work fro an arbitrary number of positional parameters, but don't know how I would incorporate keyword argument with this.
def do_next_step(self):
if self.instruction_stack.empty():
self.instruction_stack.put((self.work_on_group,(9)))
self.instruction_stack.put((self.work_on_group,(8)))
self.instruction_stack.put((self.work_on_group,(7)))
self.instruction_stack.put((self.work_on_group,(6)))
self.instruction_stack.put((self.work_on_group,(5)))
self.instruction_stack.put((self.work_on_group,(4)))
self.instruction_stack.put((self.work_on_group,(3)))
self.instruction_stack.put((self.work_on_group,(2)))
self.instruction_stack.put((self.work_on_group,(1)))
func, params = self.instruction_stack.get()
func(params)
I got this working by changing my do_next_step function to the following
def do_next_step(self):
"""
Creates, and then does, next step into stack.
"""
if self.instruction_stack.empty():
self.instruction_stack.put((self.work_on_group,(9,),{}))
self.instruction_stack.put((self.work_on_group,(8,),{}))
self.instruction_stack.put((self.work_on_group,(7,),{}))
self.instruction_stack.put((self.work_on_group,(6,),{}))
self.instruction_stack.put((self.work_on_group,(5,),{}))
self.instruction_stack.put((self.work_on_group,(4,),{}))
self.instruction_stack.put((self.work_on_group,(3,),{}))
self.instruction_stack.put((self.work_on_group,(2,),{}))
self.instruction_stack.put((self.work_on_group,(1,),{}))
func, params, keyword_params = self.instruction_stack.get()
func(*params, **keyword_params)

Code Returns Error?(Python) itemconfigure is not defined?

Hello I am making a timer. Here is my code:
from tkinter import*
import time
Screen=Tk()
Screen.resizable(0,0)
myText=Label(Screen,text="Welcome To X Timer!",font=(None,50),bg="green")
myText.pack()
aText=Label(Screen,text="0:0",font=(None,30))
aText.pack()
def start_timer():
x=1
while(True):
time.sleep(1)
x=x+1
itemconfigure(aText,text=x)
strBTN=Button(Screen,text="Start",bg="purple",font=
("Helvetica",45),command=start_timer)
strBTN.pack()
Screen.mainloop()
But on line 14 is says: Error:itemconfigure is not defined. Please help!
It's unclear exactly what it is you're trying to do, but your start_timer function is an infinite busy loop that will hang your GUI, so I assume that's not it! Maybe you meant to call Tk.after?
def start_timer(x=0):
x+=1
Screen.after(1000, lambda x=x: start_timer(x))
# 1000 is the time (in milliseconds) before the callback should be invoked again
# lambda x=x... is the callback itself. It binds start_timer's x to the scope of
# the lambda, then calls start_timer with that x.
itemconfigure(aText,text=x)
I'm going out on a limb and say that you expect itemconfigure(aText, text=x) to change the text on the label? You should instead be using:
...
aText.config(text=x)
To change the text of a Label you have to use Label's method config(). So, instead of itemconfigure(aText,text=x), do aText.config(text=x). I think itemconfigure() function doesn't exist.
Also, there are other problems. For example, if you define a function with an infinite loop as a button callback, the button will always remain pressed (buttons remain pressed till the callback finishes). That's why I recommend you to use Screen's method after() at the end of the callback, and make it execute the same function.
after() executes a function after the number of milliseconds entered, so Screen.after(1000, function) will pause the execution during a second and execute the function.
Also you can use s variable to store the seconds. When s equals 60, it resets to 0 and increases in 1 the number of minutes (m).
Here you have the code:
from tkinter import*
Screen=Tk()
Screen.resizable(0,0)
myText=Label(Screen,text="Welcome To X Timer!",font=(None,50),bg="green")
myText.pack()
aText=Label(Screen,text="0:0",font=(None,30))
aText.pack()
def start_timer():
global s, m, aText, Screen
aText.config(text = str(m) + ":" + str(s))
s += 1
if s == 60:
s = 0
m += 1
Screen.after(1000,start_timer)
s = 0
m = 0
strBTN=Button(Screen,text="Start",bg="purple",font=("Helvetica",45),command=start_timer)
strBTN.pack()
Screen.mainloop()
This one should work (in my computer it works properly). If you don't understand something just ask it.

Python: lambdas in a for loop [duplicate]

This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 5 years ago.
I'm unsure where I'm going wrong with the below - I'm sure it's something basic but I'm still a bit unsure what the issue is. I'm trying to have a button change the range of the car when clicked, but it will only set the last one.
def draw:
car_column = 0
for car in boarding_cars:
tk.Label(transport_frame, text="{}".format(car.name)).grid(row=0, column=car_column)
tk.Button(transport_frame, text=car.range, command= lambda: change_range(car)).grid(row=1, column=car_column)
car_column += 1
def change_range(car):
print car.name
if car.range == "mid":
car.range = "close"
elif car.range == "close:
car.range = "mid"
I understand it's just setting everything as the last one in the list, but I'm unsure how to stop it doing that. Any help would be appreciated.
This is a common problem when people don't understand that lambda is late-binding. You need to use partial instead for this.
from functools import partial
tk.Button(transport_frame, text=car.range, command= partial(change_range, car)).grid(row=1, column=car_column)

Python tkinter widget image animation function with after in a loop misbehaving

I am slowly learning my way through Python and tkinter. :)
In a game I'm making there are animations of images displayed within widgets (namely buttons).
Animating frame-by-frame is mundane, so I came up with a function to help me automate a 10-frame animation.
In a loop of range(10) (as I have 10 frames) the function calls the after() method which has a function callback, each displaying next frame of animation.
Since time within the after method is larger for each consecutive iteration of the loop, each new frame should be displayed nicely after given time (here it's 34ms).
That's all fine in theory, however when I run the code and appropriate functions are called, the button does not animate properly. Only the last frame seems to pop out.
The way I see it, after some reading on how tkinter works, is that each after in a loop should set independent callback in tkinter's "timeline" to be called after some time. Thus in my opinion this code should work.
What do you make of it? What've I got wrong, is my logic about after() in a loop off?
#Python 3.4.3
def animateMine(object):
global firstAnimateMineCall
for frame in range(10):
frame += 1
time = 34 * frame
root.after(time, lambda: mineAnimationFrame(object, frame))
if firstAnimateMineCall and frame == 10:
root. after(500 , lambda: animateAllMines(object))
firstAnimateMineCall = False
In the doubtful event this'd be useful:
def mineAnimationFrame(object, frame):
tempDir = "Resources/Mine/saperx_mine_%s.png" % (frame)
tempImage = PhotoImage(file=tempDir)
object.configure(image=tempImage)
object.image = tempImage
object.disabled = True
A simplistic, good-looking and easy to implement solution to this problem I came up with (thanks to CurlyJoe's advice).
*A major pro of this design is that it's easy to adjust it to your frames quantity... you got 5? Just change 1 value and it's good to go! Got 900? Still easy. 6,02*10^23 frames? Still just 1 change ;]*
To adjust to your frame size, just change the list comprehension range(10) to whatever quantity you wish, the code will take care of the rest.
from tkinter import Tk, PhotoImage, Button, FLAT
root = Tk()
mineImagesList = [PhotoImage(file="Resources/Mine/saperx_mine_%s.png" % (frame)) for frame in range(1, 11)]
button = Button(root, bd=0, relief=FLAT, command= lambda: func(button))
def func (object, frame=0):
object.configure(image=mineImagesList[frame])
object.image = mineImagesList[frame]
print("Object image:", object.image)
if frame+1 < len(mineImagesList):
frame += 1
root.after(34, lambda frame=frame, object=object: func(object=object, frame=frame))
button.pack()
root.mainloop()

Python 3.3 : I do not understand Buttons and their lambdas in Tkinter() [duplicate]

This question already has answers here:
Local variables in nested functions
(4 answers)
Closed 9 years ago.
I am having some trouble with the button widgets.
Here's my code. Basically, what I would like to do is to print "0,0" when I press the first button, print "0,1" when I press the second button and so on. But what happens is that it always prints "1,1", which are last values in my for loops. How could I fix that?
Thanks a lot for helping
from tkinter import *
def show(x,y):
print(x,y)
root = Tk()
a = 0
for i in range(2):
for j in range(2):
Button(root, text=a, command=lambda:show(i,j)).grid(column=j, row=i)
a += 1
root.mainloop()
It is because of the closure property of Python. To fix this, change
Button(root, text=a, command=lambda:show(i,j)).grid(column=j, row=i)
to
def inner(i=i, j=j):
Button(root, text=a, command=lambda:show(i,j)).grid(column=j, row=i)
inner()
We are just wrapping the values of i and j with a function. Since the function show is not executed immediately, it just accepts the values of the two variables i and j. So, all the buttons now, will have the same i and j and they will get the values which are at the end of the loop.
Now, by defining a new function, we are getting a default parameter of the same names i and j and we get the values of i and j at the particular moment. So, the current values will be retained.

Categories

Resources