Python Windows get key press length and time between each press - python

I'm trying to make a button recorder for a game I'm working on, but it requires timings (around 100 or so)
I need a program that can record when I press the spacebar in another program, for how long it's held down for (heldtime), and the time in between the next press in milliseconds (waittime). (formatted as jump(heldtime, waittime)) Pressing the Z key stops the recording.
I am using Python 2.7
If any more information is needed, I will edit to add it upon request.

You can use this to learn how to set a timer:
How to create a timer on python
And use this to learn how to create events to listen for the spacebar being pressed (provided you're using the Tkinter module to write your code):
http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
Here is what that might look like put together:
from timeit import default_timer
from Tkinter import *
key_pressed = False
last_start = 0
heldtime_array = []
def start_stop_timer():
if key_pressed == False:
last_start = default_timer()
key_pressed = True
else:
heldtime_array[len(heldtime_array)] = default_timer() - last_start
root = Tk()
frame = Frame(root, width=100, height=100)
# This will work with any key. See above link for specific keys.
frame.bind("<Key>", start_stop_timer)
frame.pack()
# Do other customisation/setup of your window here.
root.mainloop()

Related

How to automatically update a tkinter ui

I'm trying to make an UI in python with tkinter. I want it to show the very volatile value of something in the right corner (I'm getting the value through an api). This value can changes almost every second so I want to be sure it's up to date. I could make a button that refresh the ui but that's not really cool for the user. My question is how can I automatically refresh the value in the ui every x seconds with tkinter or is it isn't possible what's a better solution.
You can make a Thread that is running in the background and use StringVar() to update the value every x seconds in tkinter.
Example code that updates the time:
import tkinter as tk
from threading import Thread
import datetime
import time
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
# Make a StringVar that will contain the value
self.value = tk.StringVar()
# Make a label that will show the value of self.value
self.label = tk.Label(self, textvariable=self.value).pack()
# Make a thread that will run outside the tkinter program
self.thread = Thread(target=self.show_time)
# set daemon to True (This means that the thread will stop when you stop the tkinter program)
self.thread.daemon = True
# start the thread
self.thread.start()
tk.Button(self, text="Click me", command=lambda: print("Hello World")).pack()
def show_time(self):
# The thread will execute this function in the background, so you need to while loop to update the value of self.value
while True:
# Get the time (in your case, you need to get the api data)
data = datetime.datetime.now().strftime('Time: %H:%M:%S, Milliseconds: %f')
# Update the StringVar variable
self.value.set(data)
# Pause the while loop with 1 second, so you can set an interval to update your value
time.sleep(1)
# rest of your code.
root = SampleApp()
root.mainloop()

Tkinter unbinding key event issue

In the code below, pressing the space bar twice results in two successive beeps. I want to avoid this and instead disable the key while the first beep is happening. I thought unbinding the space key might work, but it doesn't. It's strange that only two beeps seem to stack up rather than more. I'm guessing maybe the cause of the issue is that winsound.Beep is non-blocking so the rebinding occurs almost instantly.
Any suggestion on how to get this to work please?
import winsound
from tkinter import *
def beep(e):
frame.unbind("<space>")
winsound.Beep(440, 1000)
frame.bind("<space>", beep)
root = Tk()
frame = Frame(root, width=100, height=100)
frame.bind("<space>", beep)
frame.pack()
frame.focus_set()
root.mainloop()
Here is a solution that takes the focus away from the widget, so the binding wont get triggered:
import winsound
from tkinter import *
def beep(event):
dummy.focus_set() #setting focus to dummy
winsound.Beep(440, 1000) #playing it
root.after(1000,frame.focus_set) #setting focus back after playing for 1000 ms
root = Tk()
dummy = Label() #making a dummy widget
dummy.pack()
frame = Frame(root, width=100, height=100)
frame.bind("<space>",beep)
frame.pack()
frame.focus_set()
root.mainloop()
I've commented it to understand better, but this is just a way around and its not that complicated to understand either.
Also keep in mind, in all cases of using winsound, as long as that beep has started and finished playing, the GUI will be unresponsive, that is, GUI will be unresponsive for 1 sec(in your case).
This should fix it however you have to download keyboard module with pip install keyboard :
import winsound
from tkinter import *
import keyboard
from _thread import start_new_thread
def beep():
while True:
if keyboard.is_pressed('space'):
winsound.Beep(440, 1000)
root = Tk()
frame = Frame(root, width=100, height=100)
start_new_thread(beep, ())
frame.pack()
frame.focus_set()
root.mainloop()
First start_new_thread()(syntax is important) makes beep() threaded (runs in background?) and its a while loop so it runs continuously and whenever you press space it will beep and even if you spam space it will still just run one beep. However there is a downside. It will run while script is not terminated so if you focus out it will still beep if you press spacebar
You can use the elapsed time since the last successful keypress to decide if the beep should be produced or not.
maybe like this: I do not have access to winsound, so I am using an os feature to mimic a beep. You can comment this out, and uncomment the calls to winsound
# import winsound
import os
import tkinter as tk
import time
def beep(e, time_limit=1, timer=[0]):
t0 = timer[0]
t1 = time.time()
delta_t = t1 - t0
if delta_t < time_limit:
return
# winsound.Beep(440, 1000)
os.system('say "Beep"')
timer[0] = t1
root = tk.Tk()
frame = tk.Frame(root, width=100, height=100)
frame.bind("<space>", beep)
frame.pack()
frame.focus_set()
root.mainloop()
You can bind back the event via after_idle():
def beep(e):
e.widget.unbind('<space>')
winsound.Beep(440, 1000)
e.widget.after_idle(e.widget.bind, '<space>', beep)
explanation:
The callback passed to after_idle() will be executed when tkinter mainloop is idle, i.e. no pending works/events to be handled. So if the spacebar is pressed many times, the first press triggers beep() in which tkinter unbinds the event, beep and then schedules the rebind. After beep() returns, tkinter keeps handling the pending tasks, i.e. handle the rest spacebar events (but at that moment, no bind is active) and then do the after_idle schedule task which is the rebind.

How to break out of an infinite loop with a Tkinter button?

I am working in a program where I use Tkinter for the UI. I am writing a code to play an audio repeatedly. I am using pygame.mixer.music() for playing audio.
In the UI I created two buttons ("Start" and "Stop"). I attached a method which contains the loop structure to the start button, so that when the Start button is pressed the loop will be executed and starts playing audio repeatedly. Now I don't know how to attach the Stop button. Like, when Stop button is pressed the control should exit the loop. Can I use interrupts or some other thing like that? Iam totaly new to the concept of interrupts. To proceed with that, help me with what kind of interrupt, what is the library for that, etc. If not please help me how to proceed with the stop button.
Here is my code:
from pygame import *
from Tkinter import *
import time
root = Tk()
root.geometry("1000x200")
root.title("sampler")
m=1
n=1
mixer.init()
def play():
while m==1:
print 'playing'
mixer.music.load('audio 1.mp3')
mixer.music.play()
time.sleep(n)
start = Button(root, text="play", command = play)
start.pack()
stop = Button(root, text="Stop")
stop.pack()
mainloop()
n defines how long the audio should be played for each loop.
Python doesn't exactly support interrupts, the closest thing would probably be some sort of signal handler, which are supported via its signal library. However they may not work well with Tkinter (or pygame), so I don't think that would be a good approach—and they're not really necessary anyway because what you want to do can be handled within Tkinter's mainloop().
Although it may seem somewhat complex, the way I would suggest implementing it would be to encapsulate most of the playing control functionality within a single Python class. This will reduce the use of global variables, which will make the program easier to debug and develop further (because of the many advantages of Object-Oriented Programming — aka as OOP).
Below illustrates what I mean. Note, I'm using Python 3, so had to make a few additional changes to your code in order for it would work with that version. I'm not sure, but this version ought to work in Python 2, as well, except you'll need to change the import of the Tkinter module as indicated.
from pygame import *
from tkinter import * # Change to "from Tkinter import *" for Python 2.x.
class PlayController(object):
def __init__(self, mixer, music_filename, polling_delay):
self.mixer = mixer
self.music_filename = music_filename
self.polling_delay = polling_delay # In milliseconds.
self.playing = False
def play(self):
if self.playing:
self.stop()
self.mixer.music.load(self.music_filename)
self.mixer.music.play(-1) # -1 means to loop indefinitely.
self.playing = True
root.after(self.polling_delay, self.check_status) # Start playing check.
def stop(self):
if self.playing:
self.mixer.music.stop()
self.playing = False
def check_status(self):
if self.playing:
print('playing')
root.after(self.polling_delay, self.check_status) # Repeat after delay.
root = Tk()
root.geometry("1000x200")
root.title("Sampler")
mixer.init()
play_control = PlayController(mixer, 'tone.wav', 1000)
Button(root, text="Play", command=play_control.play).pack()
Button(root, text="Stop", command=play_control.stop).pack()
mainloop()
You need to add a command to your button... stop = Button(root, text="Stop", command=stop)
Just adding a stop command probably wont work because the way your infinite loop is structured, you can't interact with the tkinter interface while play is clicked. Try restructuring your program like this:
from Tkinter import *
from pygame import *
import time
import threading
switch = True
root = Tk()
n = 1
mixer.init()
root.geometry("1000x200")
root.title("sampler")
def play():
def run():
while switch:
print 'playing'
mixer.music.load('audio 1.mp3')
mixer.music.play()
time.sleep(n)
if not switch:
break
thread = threading.Thread(target=run)
thread.start()
def switch_on():
global switch
switch = True
play()
def switch_off():
global switch
switch = False
def kill():
root.destroy()
onbutton = Button(root, text="Play", command=switch_on)
onbutton.pack()
offbutton = Button(root, text="Stop", command=switch_off)
offbutton.pack()
killbutton = Button(root, text="Kill", command=kill)
killbutton.pack()
root.mainloop()
This way the tkinter buttons are running on separate threads, so while one is looping you can still interact with the other.

How can threading be used to create multiple independent re-usable object instances?

I am building a GUI in Python and using threads to generate multiple countdown timers that work independently. I have a minimized module for testing which i am including bellow. I want the button to start the count down and then when its clicked again stop it, then reset so that it can be started on the next click. Also reset on its own once time runs out. The only problem is that after it stops, I cannot get it to restart. I get the "cannot start a thread twice" error. I have been trying to use a conditional loop to get the thread to exit its self but it hasn't been working. I would really appreciate some insight
There are actually two things that i want this program to be able to do.
1) run all the way through the timer then automatically reset so that it can be restarted
2) be stopped in the middle of a countdown, have it automatically reset so that it can be restarted
I think that solving these issues will be valuable for the community to see because it is an example of a real world solution to the issue that a lot of people talk about on the forum which is how to get around the no restarting threads.
__author__ = 'iKRUSTY'
'''
there are two things i would like this code to be able to do
1) run all the way through the timer and then reset so that it can be restarted
2) be stopped before completing and then reset so that it can be restarted
'''
from tkinter import *
import time
import os
import threading
#Variables
global FRYER_ONE_TIME_VAR # holds the value for changing label text to update timer
global BASKET_ONE_TARGET_TIME #this is a user input that will determine the length of the countdown
global timerOneStayAlive #this is the value that i am attempting to use so that the thread closes after it is set to false
timerOneStayAlive = FALSE #initializes to false because the the invert function changes it to true once the button is clicked
FRYER_ONE_TIME_VAR=" " #used to pass time between functiuons
#Font Profiles
SMALLEST_FONT = ("Verdana", 9)
SMALL_FONT = ("Verdana", 10)
LARGE_FONT = ("Verdana", 12)
LARGEST_FONT = ("Verdana", 18)
class timer():
global BASKET_ONE_TARGET_TIME
BASKET_ONE_TARGET_TIME = 5 #Just setting it manually for now
def __init__(self):
self.s = 0 #these values are used to convert from seconds to a minute:second format
self.m = 0 #these values are used to convert from seconds to a minute:second format
def SetTime(self, seconds):
self.seconds=seconds #this is a counter that will be used to calculate remaining time
def TimerReset(self):
self.seconds = BASKET_ONE_TARGET_TIME #resets counter to target time
def StartCountdown(self, FryerLabel): #takes a label as an argumet to tell it where to display the countdown
global timerOneStayAlive
print("StartCountdown Started!") #USED FOR TROUBLE SHOOTING
self.seconds = BASKET_ONE_TARGET_TIME #set start value for seconds counter
self.seconds=self.seconds+1 #makes the full time appear upon countdown start
while self.seconds > 0:
FRYER_ONE_TIME_VAR = self.CalculateTime() #Calculate time reduces the counter by one and reformats it to a minute:second format. returns a string
FryerLabel.config(text=FRYER_ONE_TIME_VAR) #Update Label with current value
print(self.seconds) #USED FOR TROUBLE SHOOTING
time.sleep(1)
# reset label with default time
if self.seconds == 0: #Reset once counter hits zero
print("resetting time") #USED FOR TROUBLE SHOOTING
self.seconds = BASKET_ONE_TARGET_TIME + 1
FRYER_ONE_TIME_VAR = self.CalculateTime()
FryerLabel.config(text=FRYER_ONE_TIME_VAR)
break
print("TimerStayAlive before invert: ", timerOneStayAlive) #USED FOR TROUBLE SHOOTING
timerOneStayAlive = invert(timerOneStayAlive) #inverts the value back to FALSE so that Ideally the loop breaks
# and the thread completes so that it can be called again
print("TimerStayAlive after invert: ", timerOneStayAlive) #USED FOR TROUBLE SHOOTING
print("end startcountdown") #USED FOR TROUBLE SHOOTING
def CalculateTime(self):
#print("CalculateTime has been called")
lessThanTen=0
self.seconds = self.seconds - 1
self.m, self.s = divmod(self.seconds, 60)
if self.s<10:
lessThanTen=1
#create time String Variables
colonVar=':'
minutesString = str(self.m)
secondsString = str(self.s)
#insert variables into string array
timeArray = []
timeArray.append(minutesString)
timeArray.append(colonVar)
if lessThanTen == 1:
timeArray.append("0")
timeArray.append(secondsString)
#prepare for output
self.timeString = ''.join(timeArray)
return self.timeString
def invert(boolean):
return not boolean
def BasketOneButtonClicked():
print("button clicked") #USED FOR TROUBLE SHOOTING
global timerOneStayAlive
timerOneStayAlive = invert(timerOneStayAlive) #Changes from FALSE to TRUE
if timerOneStayAlive == TRUE:
print("timerOneStayAlive: ", timerOneStayAlive) #USED FOR TROUBLE SHOOTING
basketOneThread.start()
updateButtonStatus() #changes text of button depending on whether timerOneStayAlive is TRUE or FALSE
else:
print("timerOneStayAlive: ", timerOneStayAlive) #USED FOR TROUBLE SHOOTING
return
def updateButtonStatus():
global timerOneStayAlive
if timerOneStayAlive == FALSE:
basketOneStartButton.config(text="Start")
if timerOneStayAlive == TRUE:
basketOneStartButton.config(text="Stop")
def BasketOneThreadComShell(): # I used this so that i can ideally call multiple functions with a single thread
'''
This is where i think the problem may be. this is what is called when the thread is initialized and theoretically
when this completes the thread should come to a halt so that when the button is reset, the thread can be called again
I think that the function is completing but for some reason the thread keeps on running.
'''
global timerOneStayAlive
print("ComShell Started") #USED FOR TROUBLE SHOOTING
while timerOneStayAlive:
basketOneTimer.StartCountdown(countdownLabelBasket1)
updateButtonStatus()
if timerOneStayAlive == FALSE: #redundant because while loop should do it. i just tried it because i couldnt get the process to end
break
print("Threadshell has ended") #USED FOR TROUBLE SHOOTING
return
print("after return check") #USED FOR TROUBLE SHOOTING
root = Tk()
'''
the following is all just building the GUI Stuff
'''
Container = Frame(root)
Container.grid(row=1, column=0, padx=10, pady=10)
countdownContainerBasket1 = Label(Container, width=10, height=5)
countdownContainerBasket1.grid(row=2, column=0, sticky=NSEW)
countdownLabelBasket1 = Label(countdownContainerBasket1, text="Basket 1", background="white", anchor=CENTER, width=10, height=6, font=LARGE_FONT, padx=20)
countdownLabelBasket1.pack()
basketOneTimer = timer()
basketOneTimer.SetTime(5)
basketOneStartButton = Button(Container, text="Start", font=LARGE_FONT, command=BasketOneButtonClicked)
basketOneStartButton.grid(row=3, column=0, padx=10, pady=10)
basketOneThread = threading.Thread(target=BasketOneThreadComShell) #this is where the thread is initialized. start() is called in BasketOneButtonClick
print(threading.active_count) #tried to use this to see if the thread was exiting but it didnt help
root.mainloop()
You'll probly want to give this a run just to see what the GUI looks like and what i mean when i say timer. Try letting it run all the way through and resetting to get an idea of what im talking about.
Let me know if there is any other info that would be helpful, and keep in mind this is my first week with python so im no expert. Thanks all
The python community is very lucky to have a style guide that is very well accepted https://www.python.org/dev/peps/pep-0008/. It is much harder for me to quickly understand the problems you are talking about due to your non pep8 naming and formatting.
Other threads cannot interact with your widgets, only the tkinter mainloop can do that. You can use the after method provided by tkinter to run a function after a given time period in the mainloop.
see this example
Mutli-threading python with Tkinter for an example!
To be clear threading with tkinter may work, but it is unreliable and you will get unexpected behavior that is hard to debug. Don't do it.
Suggestions
the tkinter after method would be called to start a function that indefinitely checks a queue for functions to run. It then schedules these functions to be run by tkinter using the after method
I would have a timer class with start, stop and pause methods.
When started it would count up/down and simply restart itself on completion. Each timer instance you create would also need a reference to a tkinter button.
to update the button you put a function on the queue that may look like
tkinter_queue.put(lambda: but.config(text=i))
now the function that checks the queue will update your button timers
Also as you are in your first week of python programming you can be sure that most problems you encounter will have answers here if not elsewhere. Please do your research before hand. Gui programming and threading are not the easiest topics so even more reason to do your research.

python label not changing dynamically

I want to see continuously changing value of label in tkinter window. But I'm not seeing any of it unless I make a keyboard interrupt in MS-CMD while running which shows me the latest assigned value to label. Plz tell me ..What's going on & what's the correct code ??
import random
from Tkinter import *
def server() :
while True:
x= random.random()
print x
asensor.set(x)
app=Tk()
app.title("Server")
app.geometry('400x300+200+100')
b1=Button(app,text="Start Server",width=12,height=2,command=server)
b1.pack()
asensor=StringVar()
l=Label(app,textvariable=asensor,height=3)
l.pack()
app.mainloop()
The function server is called when you click the button, but that function contains an infinite loop. It just keep generating random numbers and sending these to asensor. You are probably not seeing any of it because the server function is run in the same thread as the GUI and it never gives the label a chance to update.
If you remove the while True bit from your code, a new number will be generate each time you click the button. Is that what you wanted to do?
Edit after comment by OP:
I see. In that case your code should be changed as follows:
import random
from Tkinter import Tk, Button, Label, StringVar
def server():
x = random.random()
print x
asensor.set(x)
def slowmotion():
server()
app.after(500, slowmotion)
app = Tk()
app.title("Server")
app.geometry('400x300+200+100')
b1 = Button(app, text="Start Server", width=12, height=2, command=slowmotion)
b1.pack()
asensor = StringVar()
asensor.set('initial value')
l = Label(app, textvariable=asensor, height=3)
l.pack()
app.mainloop()
I also introduced a new function, slowmotion, which does two things: 1) calls server, which updates displays the value, and 2) schedules itself to be executed again in 500ms. slowmotion is first ran when you first click the button.
The problem with your code was that it runs an infinite loop in the main GUI thread. This means once server is running, the GUI will not stop and will not get a chance to display the text you asked it to display.

Categories