stop self.after when interrupt happens - python

I started coding snake in python 3. As GUI i use Tkinter.
I got a timer which waits for a second an then calls the method again. Well now my question is how to i stop the self.wait?
I know i could work around this pretty easily, but i had this problem already somewhere else, so it would be nice to know how i can stop this.
This is the method which moves the snake around. (Only the timer is critical). The timer is here so it moves every second.
def move_snake(self):
self.after(1000, self.move_snake)
# code goes on
Now if i change the direction (by pressing a button) i do following:
def change_direction(self, event):
self.pressed = event.keysym
self.move_snake()
If i do this this way the "old" timer still is active and therefore the method gets called multiple times (it adds an additional timer when you press an button).
It would be nice that just the latest timer is activated.
Do you need more information?

Assuming that move_snake uses self.pressed, you don't need to call move_snake inside of change_direction.
However, if you really want to stop the old loop and start a new loop, you can save the id that is returned from after and give that to after_cancel:
def move_snake(self):
self.after_id = self.after(1000, self.move_snake)
# code goes on
def change_direction(self, event):
self.pressed = event.keysym
# cancel the old loop
self.after_cancel(self.after_id)
# start a new loop
self.move_snake()

Related

How can my program be prevented from freezing?

I have decided to finally work on a project, as I've tried to code in python before, with at least some success. In my project, I am trying to build a menu that lets me "Auto-farm" in a game. It uses 3 modules, namely pynput, pause, and PySimpleGUI.
Whenever I run the code, it runs fine, until I click the button that starts the automation part. It runs completely fine, but I have to force close the GUI prompt that shows up as it just completely stops responding to input until you close it.
How I can make a stop button, and stop my program from freezing up?
I am using 2 files to keep this project slightly more organized, although I don't know if this is the best way to go around doing this. These 2 files are main.py and chand.py.
main.py
import PySimpleGUI as sg
import chand
loop = 0
layout = [[sg.Text("Welcome to RedGrowie's autofarm menu!")], [sg.Button("Chandeliers")]]
window = sg.Window("Autofarming Menu", layout)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
if event == "Chandeliers":
loop = 1
if loop == 1:
chand.Chandeliers.start(self=chand.Chandeliers)
window.close
chand.py
from pynput.keyboard import Key, Controller
import pause
keyboard = Controller()
start = "0"
class Chandeliers:
def d_press(self):
keyboard.press("d")
pause.milliseconds(70)
keyboard.release("d")
pause.milliseconds(300)
keyboard.release(Key.space)
def space_press(self):
keyboard.press(Key.space)
pause.milliseconds(1)
#keyboard.release(Key.space)
def start(self):
start = "1"
while start == "1":
self.d_press(self)
self.space_press(self)
Your Chandeliers.start function loops indefinitely, so the call from the main loop in main.py never gets returned to. If you want both loops to be running at the same time, you probably need to use threading or some other means of concurrency. Or you might be able to interleave the two loops somehow, depending on the timing requirements for each one.
As a side note, you are using your Chandeliers class in a very odd way. You're never creating an instance of the class, but rather calling the methods it defines as if they were class methods (but with manual passing of the class, in the misleadingly named self argument.
You should probably not do that. Either treat the class as a normal one, and create an instance:
cha = chand.Chandeliers()
chat.start() # and change start to not manually pass self any more
Or you should do away with the unneeded class all together and just make the methods into top-level functions.

python curses while loop and timeout

I am having a hard time understanding the window.timeout() function. To be more specific, I am toying with a "snake" game in python:
s = curses.initscr()
curses.curs_set(0)
w = curses.newwin()
w.timeout(100)
while True:
move snake until it hits the wall
I understand that in this case, timeout(100) determines how fast the snake "moves", i.e. printing out new characters on the screen. However, I got stuck when I want to amend the code so that it waits until someone press "start". I wrote something like:
w.timeout(100)
while True:
if w.getch() is not start:
stay at the initial screen
else:
while True:
move the snake until it hits the wall
However, in this case, the timeout(100) seems to govern how long each time the program waits for w.getch(), not how long to wait between each time the snake moves. Also, I notice that in the first example, the timeout is declared at the top, outside the while loop. This looks weird to me because normally if I want to pause a while loop, I would put sleep() at the bottom inside the while loop.
If you want to pause between snake moves, you could use napms to wait a given number of milliseconds (and unlike sleep, does not interfere with screen updates). Setting w.timeout to 100 (milliseconds) is probably too long. If you're not concerned with reading function-keys, you could use nodelay to set the w.getch to non-blocking, relying on the napms to slow down the loop.
Regarding the followup comment: in ncurses, the wtimeout function sets a property of the window named _delay, which acts within the getch function, ultimately passed to a timed-wait function that would return early if there's data to be read.

Weird behavior - wxPython Events

I have this following piece of code. It is working sometimes, it is not the other time.
def OnReset(self, event):
self.reset_pump.Disable() # Disables the button so it is clicked
self.WriteToController([0x30],'GuiMsgIn') # Sends the reset command
self.flag_read.set()
self.tr.join()
time.sleep(2)
start = time.time()
self.offset_text_control.Clear()
print "Helloin reset"
self.gain_text_control.Clear()
self.firmware_version_text_control.Clear()
self.pump_rpm_text_control.Clear()
self.pressure_text_control.Clear()
self.last_error_text_control.Clear()
self.error_count_text_control.Clear()
self.pump_model_text_control.Clear()
self.pump_serial_number_text_control.Clear()
self.on_time_text_control.Clear()
self.job_on_time_text_control.Clear()
#self.MessageBox('Pump RESET going on Click OK \n')
# Having the above step is useful
print time.time() - start
#self.ser.close()
wx.CallLater(3000, self.CalledAfter, [event,])
def CalledAfter(self, event):
self.tr = threading.Thread(target=ReadData, name="ReadThread", args=(self.ser, self.flag_read))
self.tr.daemon = True
self.tr.start()
self.reset_pump.Enable()
What it does is When I click the Reset button on my GUI, it has to clear certain text fields on the GUI. It has to clear it, only after joining the self.tr thread.
Once it clears, it will execute the command wx.CallLater(3000, self.CalledAfter, [event,]). Which then starts a new thread again.
Apparently The .Clear() command is working very at a non consistent level, it is working some time, not working the other times, and working again.
Any idea why this might happen would be very helpful.
There seems to be a difference in the way SetValue and Clear update the windows. Calling SetValue("") seems to be a suitable workaround to this behavior.

Tkinter Frame Not Recognizing Keypresses

This question is NOT a duplicate of this question: Why doesn't the .bind() method work with a frame widget in Tkinter?
As you can see, I set the focus to the current frame in my game_frame() method.
I'm writing a Chip-8 emulator in Python and using Tkinter for my GUI. The emulator is running, but I can't get Tkinter to recognize keypresses. Here is my code:
def game_frame(self):
self.screen = Frame(self.emulator_frame, width=640, height=320)
self.screen.focus_set()
self.canvas = Canvas(self.screen, width=640, height=320, bg="black")
self._root.bind("<KeyPress-A>", self.hello)
for key in self.CPU.KEY_MAP.keys():
print(key)
self.screen.bind(key, self.hello)
self.screen.pack()
self.canvas.pack()
def hello(self, event):
if event.keysym in self.CPU.KEY_MAP.keys():
self.CPU.keypad[self.CPU.KEY_MAP[event.keysym]] = 1
self.CPU.key_pressed = True
self.CPU.pc += 2
sys.exit()
def run_game(self, event):
self.game_frame()
self.CPU.load_rom("TANK")
while True:
self._root.update()
self.after(0, self.CPU.emulate_cycle)
Could you please help me figure out what's going wrong? I think it might have something to do with my game loop interfering with the key bindings, but I'm not sure. The hello method never gets called when I run the game because the program continues to run in an infinite loop and never exits, regardless of what key is pressed. Thank you!
The problem could be due to two things. Without seeing all your code it's impossible to say for sure.
For one, you are binding to a capital "A" rather than a lowercase "a" -- have you testing that the binding works or not when you press a capital A?
Also, you are using after and update incorrectly. You may be starving the event loop, preventing it from processing key presses. The right way to run a function periodically is to have a function that (re)schedules itself.
class CPU_Class():
...
def run_cycle(self):
self.emulate_cycle()
self._root.after(1, self.run_cycle)
Two things to note:
don't use after(0, ...) -- you need to give tkinter at least a ms or so to process other events.
the run_cycle function is responsible for running one cycle, and then scheduling the next cycle to run in the future.
Once you do that, you no longer need your while loop. You can simply call run_cycle once, and that will start the CPU running.

While loop is taking forever and freezing screen?

I have this large program, one of whose parts includes the click of a button (this GUI was made in wxPython). Basically, the button is a timer, and when you click on it, the text of the button switches based on the time left, e.g. 5 Seconds, 4 Seconds, 3 Seconds... For some reason, though, when I click on the button in the GUI, the entire program freezes and I have to force quit the app. (Everything else in the app works fine and responds, until I click on this one button). Here is the event handler that is called when the button is clicked.
Thanks in advance!
def TossUpTimer(self, event):
while self.tossuptimer > -1:
while self.tossuptimer > 0:
self.tossupbutton.SetLabel(str(self.tossuptimer) + "Seconds")
time.sleep(1)
self.tossuptimer -= 1
if self.tossuptimer == 0:
self.tossupbutton.SetLabel("Time is Up!")
time.sleep(1)
self.tossupbutton.SetLabel(str(self.tossuptimer) + "Seconds")
self.tossuptimer = 5
Why your GUI app freezes is an attempt to explain the basic idea, and all of the different options for dealing with it.
But here's a short version:
The whole point of a GUI app is that it's running an event loop, responding to events as they come in from the user or the OS. You write handlers for particular events. Until your handler returns, the GUI can't process the next event. That means that the entire app is frozen.
You need to return from your handler as quickly as possible. What you can't do, ever, is time.sleep(1) or a long while loop or anything of the kind directly in a handler.
There are various ways around this. The two most common are to ask the event loop to run some of your code later, or to move it to a background thread. (wx adds a third way, SafeYield and friends, which is appropriate in some cases, but probably not what you want here.)
For the first alternative, the idea is that, instead of looping around sleep, you write a function that handles a single iteration of the loop, and schedule a timer to fire that function once/second. This is known as "turning your control flow inside out", which can be a bit hard to get your head around.
Unfortunately, I'm not 100% sure what you're trying to do, because, as Jon Clements points out, your logic doesn't make sense even in a sequential program. But I'll write something simple: when you click the button, it will count down once/second for 5 seconds, then stop counting.
def OnTossUpTimer(self, event):
self.tossuptimer -= 1
if self.tossuptimer > 0:
self.tossupbutton.SetLabel(str(self.tossuptimer) + "Seconds")
else:
self.tossupbutton.SetLabel("Time is Up!")
self.tossuptimer_timer.Stop()
def TossUpTimer(self, event):
self.tossuptimer = 5
self.tossupbutton.SetLabel(str(self.tossuptimer) + "Seconds")
self.tossuptimer_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTossUpTimer, self.tossuptimer_timer)
self.tossuptimer_timer.Start(1000, False)
See Using wx.Timers and the Timer docs for more details.
The threaded version is simpler in some ways, more complicated in others. You can leave your sequential logic alone, but you can't talk to any GUI widgets except indirectly, by calling PostEvent. So:
class LabelUpdateEvent(wx.PyEvent):
EVT_LABEL_UPDATE_ID = wx.NewId()
def __init__(self, data):
wx.PyEvent.__init__(self)
self.SetEventType(EVT_LABEL_UPDATE_ID)
self.data = data
def SetLabelOnMainThread(self, value):
wx.PostEvent(self.SetLabelForReal, ResultEvent(value))
def SetLabelForReal(self, event):
self.tossupbutton.SetLabel(event.data)
def TossUpTimerThread(self):
self.tossuptimer = 5
while self.tossuptimer > 0:
self.SetLabelOnMainThread(str(self.tossuptimer) + "Seconds")
time.sleep(1)
self.tossuptimer -= 1
self.SetLabelOnMainThread(str(self.tossuptimer) + "Time is Up!")
def TossUpTimer(self, event):
threading.Thread(target=self.TossUpTimerThread).start()
If you're doing a lot of this, you probably want to write more generic "on-main-thread" functions, instead of all this boilerplate for each function. (You can, in fact, write a single completely-generic function, which wxPython really ought to come with but doesn't.)

Categories

Resources