I know how to undo a drawing step in python turtle with turtle.undo(). But how can I make a Redo function ?
from tkinter import *
...#Just some other things
def undoStep():
turtle.undo()
def redoStep():
#What to put here
root.mainloop()
To make a redo function, you need to keep track of the each action, in a list actions for example. You will also need a variable i that tells you where you are in that list and each time you call undoStep, decrease i by one. Then redoStep has to perform the action actions[i]. Here is the code:
import turtle
actions = []
i = 0
def doStep(function, *args):
global i
actions.append((function, *args))
i += 1
function(*args)
def undoStep():
global i
if i > 0:
i -= 1
turtle.undo()
def redoStep():
global i
if i >= 0 and i < len(actions):
function, *args = actions[i]
function(*args)
i += 1
Related
I try to stop a .after function i tkinter in python 3.6, but have probems with namspace of variables. I get NameError: "name 'counter' is not defined" when I use this code:
I read this excellent post, from where I got the idea of using two buttons instead of one.
Use start and stop function with same button in Tkinter
def count(ac,rs):
if ac:
global counter
counter += 1
label.config(text=counter)
label.after(1000, count)
if rs:
counter = 0
counter += 1
count()
def start():
ac = True
rs=False
count(ac,rs)
def stop():
ac = False
label.configure(text='0')
rs = True
count(ac,rs)
Since I declare the variable counter in the count() function, I don't understand the NameError.
On the following strip-down version of my program, I want to simulate an exchange where the user and the computer will act following a random sequence.
Here the variable row contains the sequence order. A value of 0 means the program is waiting for a user input (method val0). A value of 1 means there should be an automatic process (method val1).
It seems to work at the beginning when alternating 0 and 1 but as soon as we are waiting for two successive automatic calls, it goes out of whack.
I tried using the after method but I can't see how and where to insert it.
A while loop might do the trick on this example but the end program is more complex, with sequence interruption, reevaluation of the sequence and so on. So I don't know if it would still apply then.
from tkinter import *
class Application:
def __init__(self,master = None):
self.master = master
Label(master,text='press next').grid()
Button(master,text='Next',command=self.val0).grid()
self.index = IntVar()
self.index.set(0)
self.index.trace("w",self.nextTurn)
def val0(self):
print("User action")
self.index.set(self.index.get() +1)
def val1(self):
print("Automatic action")
self.index.set(self.index.get() +1)
def nextTurn(self, *args):
i = self.index.get()
if i >= len(row):
self.master.destroy()
return
if row[i] == 1:
self.val1()
if __name__ == "__main__":
row = [0,1,0,0,1,1,0,0,0,1,1,1]
root = Tk()
win = Application(root)
root.mainloop()
You can easily solve your problem by calling the nextTurn directly in the automatic action function:
def val1(self):
print("Automatic action")
self.index.set(self.index.get() +1)
self.nextTurn() # call nextTurn after this action
So if it was an automatic action, you step into the next row position, and call nextTurn again.
However this may become a problem if your row becomes too large because it uses recursion. In that case you will want another approach with the while you mentioned. For this second option you would only need to change nextTurn:
def nextTurn(self, *args):
i = self.index.get()
# while it is an automatic action and it has row values, keep calling val1
while i < len(row) and row[i] == 1:
self.val1() # call automatic action
i = self.index.get() #update the row position
else:
if i >= len(row):
self.master.destroy()
return
I'm rather new to Python and programming in general, so I apologise in advance if my terminology is incorrect.
hue_alert_delay = 0
def delays(name, delay):
global hue_alert_delay
if name == 'hue_alert_delay':
for i in range(0, delay):
hue_alert_delay += 1
time.sleep(1)
hue_alert_delay = 0
delays('hue_alert_delay', 60)
What I'm trying to achieve:
I would like the function to convert the 'name' parameter, which is a string input, into a pre-exiting variable, which will negate the need for multiple IF statements.
The above example includes only one IF statement, but for my project there will be a lot more and I would rather keep the function clean and simple.
This won't work, but it's what I'm trying to aim for:
hue_alert_delay = 0
def delays(name, delay):
global name
for i in range(0, delay):
name += 1
time.sleep(1)
hue_alert_delay = 0
delays('hue_alert_delay', 60)
Any assistance would be appreciated.
Use a dict:
values = {
'hue_alert_delay': 0
}
def delays(name, delay):
values[name] += 1
Whenever you feel like using "variable variables", what you most likely really want is a dict storing key-value associations. Yes, there are other ways to do literally what you want, but that soon leads to insane code.
Use a dictionary like so.
vars = {'hue_alert_delay':0}
def delays(name, delay):
for i in range(0, delay):
vars[name] += 1
time.sleep(1)
vars[name] = 0
You can also use globals()[name] but I won't recommend it.
Use a dictionary:
vars = {'hue_alert_delay':0}
def delays(name, delay):
for i in range(delay):
vars[name] += 1
time.sleep(1)
vars[name] = 0
delays('hue_alert_delay', 60)
I'm trying to get the last few lines of the following code to have when one of the original 6 buttons is pressed to call the appropriate function to rename the buttons. I've tried changing the command line to buttons[0].command = Pistols(). I've also tried using a if loop with a variable such as x == 1 to determine that if the button is pressed x with then be 1 and the for loop will call the function Pistols, but with no success. However the button automatically calls the function and renames the first button to ".44 Pistol" rather than what it should be "Pistols". I wan't the command to only be executed and call the function when pressed. I know that tkinter will automatically look to the function being called and run it's code. How can I either delay this or go about this in another way to have the functions code only execute when pressed. Thanks in advance!
from tkinter import *
buttons = []
clm = [1,2,1,2,1,2]
rw = [1,1,2,2,3,3]
btnmain_list = ['Pistol','Rifle','Assult Rifle','Submachine Gun','Heavy Weapon','Plasma Weapons']
btnpistol_list = ['.44 Pistol', '10mm Pistol', 'Pipe Bolt-Action Pistol','Flare Gun', 'Pipe Pistol', 'Pipe Revolver']
btnrifle_list = []
btnasrifle_list = []
btnsubgun_list = []
btnheavy_list = []
btnplasma_list = []
ms = Tk()
ms.title('Fallout 4 weapon mods and needed materials')
ms.geometry('450x400')
placement = Frame(ms)
placement.grid()
class Guns:
def Pistols ():
buttons[0] = Button(placement,height = '5',width = '20', text = btnpistol_list[0])
buttons[0].grid(column = clm[0], row = rw[0])
def Rifles ():
x = 0
def AssultRifles ():
x = 0
def SubmachineGuns ():
x = 0
def HeavyWeapons ():
x = 0
def PlasmaWeapons ():
x = 0
for i in range (6):
b = Button(placement,height = '5',width = '20', text = btnmain_list[i])
b.grid(column = clm[i], row = rw[i])
buttons.append(b)
buttons[0].command = Pistols()
I've found a solution by changing the class to this:
class Guns:
global counter
counter = 0
def pistolCycle():
global counter
buttons[0].config(text=btnpistol_list[counter])
if counter == len(btnpistol_list)-1:
counter=0
counter = counter+1
def Pistols ():
buttons[0] = Button(placement, height = '5',width = '20', text="Pistols", command = lambda: Guns.pistolCycle() )
buttons[0].grid(column = clm[0], row = rw[0])
def Rifles ():
x = 0
def AssultRifles ():
x = 0
def SubmachineGuns ():
x = 0
def HeavyWeapons ():
x = 0
def PlasmaWeapons ():
x = 0
for i in range (6):
b = Button(placement,height = '5',width = '20', text = btnmain_list[i])
b.grid(column = clm[i], row = rw[i])
buttons.append(b)
Pistols()
So, here's a breakdown of what happens:
Once your buttons are defined, the Pistol function is called, which adds all the features to your Pistol button, including changing the text, and adding the function it will call when pressed.
When the button is pressed, it calls to pistolCycle. What pistol cycle does, is takes the "counter" value, and changes the text of the button to the item in the list which is associated to it. EG, when the counter is 0, .44 Pistol is displayed.
The counter increases by one, each time pistolCycle is called, meaning the next time it's called, it will display the next item in the list.
Now, using global variables can get messy. I've given you the basic framework, so you may be able to use your own logic to get the variable "counter" to pass into pistolCycle each time (EG, pistolCycle(counter))
You will need to make a separate counter and cycle function in order for all the buttons to work.
I hope this helped!!
PS: The if statement in the pistolCycle function means that it wont try and get an item when it doesn't exist in the list.
I got an error at this code:
The error is in my function settings() in the Button() command. but I don't got any plan how to fix it, sorry. I can't put the 3 commands in an external function, cause it wouldn't get the variables...
from turtle import *
from tkinter import *
reset()
hastrail = 1
def moveup():
setheading(90)
forward(5)
def movedown():
setheading(270)
forward(5)
def moveright():
setheading(0)
forward(5)
def moveleft():
setheading(180)
forward(5)
def turnleft():
left(18)
def turnright():
right(18)
def forw():
forward(5)
def backw():
backward(5)
def trailrem():
global hastrail
if hastrail == 1:
penup()
hastrail = 0
else:
pendown()
hastrail = 1
def settings():
color(str(colorchooser.askcolor(title = "Change a line color")[1]),str(colorchooser.askcolor(title = "Change a fill color")[1]))
tk = Tk ()
tk.resizable(0,0)
tk.title("Shape, Shapesize, Pensize")
tk.geometry("400x90")
listbox = Listbox(tk)
listbox.place(x=0,y=0,width=200,height=90)
listbox.insert(1,"arrow")
listbox.insert(2,"turtle")
listbox.insert(3,"circle")
listbox.insert(4,"square")
listbox.insert(5,"triangle")
shsi = Scale(tk,width = 10,orient = HORIZONTAL)
shsi.place(x=200,y=0,width=200,height=30)
trsi = Scale(tk,width = 10, orient = HORIZONTAL)
trsi.place(x=200,y=30,width=200,height=30)
Button(tk,text="Save",command = lambda:shape(str(listbox.get(ACTIVE)))&shapesize(int(shsi.get()))&pensize(int(trsi.get()))).place(x=200,y=60,width=200,height=30)
onkeypress(moveup,"Up")
onkeypress(movedown,"Down")
onkeypress(moveright,"Right")
onkeypress(moveleft,"Left")
onkeypress(turnleft,"a")
onkeypress(turnright,"d")
onkeypress(forw,"w")
onkeypress(backw,"s")
onkeypress(trailrem,"t")
onkeypress(settings,"c")
listen()
mainloop()
Pls tell me what I've done wrong // fix it pls.
If you're trying to string together multiple expressions using the & operator, it isn't likely to work well, unless all of your function calls return integers, which isn't the case here. I don't recommend it, but you can put each command as a separate element of a collection such as a list or tuple:
Button(tk,text="Save",command = lambda:[
shape(str(listbox.get(ACTIVE))),
shapesize(int(shsi.get())),
pensize(int(trsi.get()))
]).place(x=200,y=60,width=200,height=30)
I can't put the 3 commands in an external function, cause it wouldn't get the variables
Ordinarily, this is true. But if you define the second function inside the first, all of its variables will still be visible.
def settings():
def save_button_clicked():
shape(str(listbox.get(ACTIVE)))
shapesize(int(shsi.get()))
pensize(int(trsi.get()))
#rest of `settings` code goes here...
Button(tk,text="Save",command = save_button_clicked).place(x=200,y=60,width=200,height=30)