Timed Availability of controls in tkinter GUI - python-3.x

I'm working on a program that will stop users from changing a label after a random amount of time. There are two buttons, start and next, when the user presses start the start button is destroyed but is supposed to come back after a randomly selected amount of time. I tried to have the start button trigger a flag that starts a timer. When the timer reaches a certain value (count_to+1) the flag should go to zero, the start button should reappear, and the label should read end. The flag never seems to switch and the timer never initiates though. Can anyone tell me what I did wrong? and maybe point me towards a solution? Hear is the code:
import sys
from tkinter import *
import random
import time
mGui = Tk()
mGui.geometry('450x450+200+200')
mGui.title('Letters')
stored = ['A', 'b', 'c', 'd']
count_down = [10,20,30,40,50,60]
global count_to
global countFlag
count_to = IntVar()
countFlag = 0
Sec = 0
def run_counter():
count_to = random.choice(count_down)
while countFlag == 1:
Sec+=1
print(sec)
if Sec == count_to+1:
countFlag = 0
newbutton.destroy()
startbutton.grid(row=2,column=1)
phrase.configure(text='End')
return
def change_phrase():
fish = StringVar()
fish = random.choice(stored)
stored.remove(fish)
phrase.configure(text=fish)
#to help with debug
print(countFlag)
print(Sec)
print(count_to)
return
def start_count():
countFlag = True
count_to = random.choice(count_down)
print(countFlag)
startbutton.destroy()
run_counter
return
phrase = Label(mGui,text='Letter',fg='red',bg='blue')
phrase.grid(row=0,column=0, sticky=S,columnspan=2)
startbutton =Button(mGui, text='start',fg='black',bg='green',command=start_count)
startbutton.grid(row=2,column=1)
newbutton = Button(mGui,text='NEXT',fg='black',bg='red',command=change_phrase)
newbutton.grid(row=2,column=0)
#mEntry = Entry(mGui,textvariable=ment)
#mEntry.grid(row=3,column=0)
mGui.mainloop()

Tkinter programming becomes much less messy and confusing once you learn to use classes. Use Tkinter's after() method to call a function every "x" amount of time until the allotted time has elapsed.
import random
import sys
if sys.version_info[0] < 3:
import Tkinter as tk ## Python 2.x
else:
import tkinter as tk ## Python 3.x
class ButtonDisappear():
def __init__(self, root):
self.root=root
self.startbutton=tk.Button(root, text='disappear', fg='black',
bg='green', command=self.disappear)
self.startbutton.grid(row=2,column=1)
self.lab=tk.Label(self.root, text="", bg="lightblue")
def disappear(self):
## remove button
self.startbutton.grid_forget()
## grid label for time
self.lab.grid(row=0, column=0)
## "random" number
self.stop_time=random.choice([1, 2, 3, 4, 5])*1000
self.elapsed=0
self.root.after(100, self.time_it)
def time_it(self):
self.elapsed += 100
## function calls itself until time has finished
if self.elapsed < self.stop_time:
self.lab["text"]="%d of %d" % (self.elapsed, self.stop_time)
self.root.after(100, self.time_it)
## time elapsed so remove label and restore button
else:
self.lab.grid_forget()
self.startbutton.grid(row=2,column=1)
m_gui = tk.Tk()
m_gui.geometry('450x450+200+200')
m_gui.title('Letters')
B=ButtonDisappear(m_gui)
m_gui.mainloop()

Related

Making a flashing text in tinker

I want a text that flashes with a clock's seconds. This Link was helpful, but couldn't solve my problem. Below is my little working code:
from tkinter import *
from datetime import datetime
import datetime as dt
import time
def change_color():
curtime=''
newtime = time.strftime('%H:%M:%S')
if newtime != curtime:
curtime = dt.date.today().strftime("%B")[:3]+", "+dt.datetime.now().strftime("%d")+"\n"+newtime
clock.config(text=curtime)
clock.after(200, change_color)
flash_colours=('black', 'red')
for i in range(0, len(flash_colours)):
print("{0}".format(flash_colours[i]))
flashing_text.config(foreground="{0}".format(flash_colours[i]))
root = Tk()
clock = Label(root, text="clock")
clock.pack()
flashing_text = Label(root, text="Flashing text")
flashing_text.pack()
change_color()
root.mainloop()
This line of code: print("{0}".format(flash_colours[i])) prints the alternating colors on the console as the function calls itself every 200s. But the flashing_text Label's text foreground doesn't change colors.
Does anybody have a solution to this problem? Thanks!
Please forgive my bad coding.
Although you have changed the color of the flashing_text in the for loop twice, but the tkinter event handler (mainloop()) can only process the changes when it takes back the control after change_color() completed. So you can only see the flashing_text in red (the last color change).
To achieve the goal, you need to change the color once in the change_color(). Below is a modified change_color():
def change_color(color_idx=0, pasttime=None):
newtime = time.strftime('%H:%M:%S')
if newtime != pasttime:
curtime = dt.date.today().strftime("%B")[:3]+", "+dt.datetime.now().strftime("%d")+"\n"+newtime
clock.config(text=curtime)
flash_colors = ('black', 'red')
flashing_text.config(foreground=flash_colors[color_idx])
clock.after(200, change_color, 1-color_idx, newtime)
I would add it to a class so you can share your variables from each callback.
So something like this.
from tkinter import *
from datetime import datetime
import datetime as dt
import time
class Clock:
def __init__(self, colors):
self.root = Tk()
self.clock = Label(self.root, text="clock")
self.clock.pack()
self.flashing_text = Label(self.root, text="Flashing text")
self.flashing_text.pack()
self.curtime = time.strftime('%H:%M:%S')
self.flash_colours = colors
self.current_colour = 0
self.change_color()
self.root.mainloop()
def change_color(self):
self.newtime = time.strftime('%H:%M:%S')
if self.newtime != self.curtime:
if not self.current_colour:
self.current_colour = 1
else:
self.current_colour = 0
self.curtime = time.strftime('%H:%M:%S')
self.flashing_text.config(foreground="{0}".format(self.flash_colours[self.current_colour]))
self.clock.config(text=self.curtime)
self.clock.after(200, self.change_color)
if __name__ == '__main__':
clock = Clock(('black', 'red'))

RaspberryPi Stopwatch (TKinter) with Trigger

i would like to build a stopwatch for my carrera track which i built a little bit bigger.
So I have bought a Raspberry Pi 3 with an additional 7 "touch screen and the individual modules for triggering.
Everything works fine individually.
Now I found a great stopwatch on the net which I also used. Works really great.
In another script, that I've written by myself, the triggering with the gpios works also fantastic.
Now I want to combine both and fail.
Does anyone have an idea or suggested solution where my mistake is?
Here is my code
#!/usr/bin/python
import tkinter as tk
import RPi.GPIO as GPIO
import time
GPIO_TRIGGER_PIN = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(GPIO_TRIGGER_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setwarnings(False)
def update_timeText():
if (state):
global timer
timer[2] += 1
if (timer[2] >= 100):
timer[2] = 0
timer[1] += 1
if (timer[1] >= 60):
timer[0] += 1
timer[1] = 0
timeString = pattern.format(timer[0], timer[1], timer[2])
timeText.configure(text=timeString)
root.after(10, update_timeText)
def start():
global state
state = True
print('Pressed Start')
def stop():
global state
state = False
print('Pressed Stop')
def reset():
global timer
timer = [0, 0, 0]
timeText.configure(text='00:00:00')
print('Pressed Reset')
while GPIO.input(GPIO_TRIGGER_PIN) == True:
if GPIO.input(GPIO_TRIGGER_PIN):
print('CAR DETECTED')
time.sleep(0.1)
state = False
# BULDING TKinter GUI
root = tk.Tk()
root.wm_title('Stopwatch')
timer = [0, 0, 0]
pattern = '{0:02d}:{1:02d}:{2:02d}'
timeText = tk.Label(root, text="00:00:00", font=("Helvetica", 150))
timeText.pack()
startButton = tk.Button(root, text='Start', command=start)
startButton.pack()
stopButton = tk.Button(root, text='Stop', command=stop)
stopButton.pack()
resetButton = tk.Button(root, text='Reset', command=reset)
resetButton.pack()
update_timeText()
root.mainloop()
Currently I get the trigger in the console as output "CAR DETECTED". However, I do not get the TKinter panel.
If I remove
while GPIO.input(GPIO_TRIGGER_PIN) == True:
if GPIO.input(GPIO_TRIGGER_PIN):
print('CAR DETECTED')
time.sleep(0.1)
then the display appears and works. Without triggering.
If I put it all down, I get also the panel but it also triggers nothing more.
Any ideas ?
Thanks for help
Not really my area of expertise, but since nobody else answered, I'll give it a shot.
I think you want to use GPIO.add_event_callback()* to add a callback that gets called when an event happens.
# Callback function
def on_trigger(channel_number):
# Just update the flag
global state
state = False
# Set the callback
GPIO.add_event_callback(GPIO_TRIGGER_PIN , callback=on_trigger)
Then, delete the while loop.
*According to the docs, you need the darksidesync library to use this function. Instead of the callback, you could also create a separate thread. Personally, I would prefer the callback.
from threading import Thread
def thread_function(arg):
global state
# Listen for state change
while GPIO.input(GPIO_TRIGGER_PIN) == True:
if GPIO.input(GPIO_TRIGGER_PIN):
# Update var
state = False
print('CAR DETECTED')
time.sleep(0.1)
thread = Thread(target = thread_function)
thread.start()
Hope this helps.

Updating progress bar in Tkinter simultaneously while recording audio Python3

My goal for this simple program is to be able to show a progress bar of time the mic has been recording(range from 0 - 30 seconds). I am using the Tkinter library, Python 3.6, and PyAudio to record.
I finished writing the code for the individual parts(recording + updating progress bar) but I don't know how to change the code so that they happen at the same time(not one after the other). Also, I've realized that while I am recording, the GUI freezes(I can't click or resize buttons), I am also wondering about how to fix this issue. After doing some more research, I have a feeling that the answer lies with multi-threading or root.update() but I am not sure.
I am looking for a code example of recording audio while I simultaneously update the progress bar.
Here is an example:
from tkinter import *
from tkinter import ttk
import tkinter as tk
import pyaudio
import numpy as np
np.random.seed(123) # for reproducibility
import matplotlib
matplotlib.use('Agg') # No pictures displayed
class Test_GUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
root = self
label_title = Label(root, text="Progress Bar Recording Sample")
label_title.pack(fil="x")
topFrame = Frame(root)
topFrame.pack(side=TOP)
self.recordButton = Button(topFrame, text="Record", command=self.start)
self.recordButton.pack(side=LEFT)
self.progress = ttk.Progressbar(topFrame, orient="horizontal",
length=200, mode="determinate")
self.min = 0
self.maxMin = 0
self.progress.pack(side=RIGHT)
# recording attributes.
self.CHUNK = 1024
self.FORMAT = pyaudio.paInt16
self.CHANNELS = 2
self.RATE = 44100
self.RECORD_SECONDS = 15
self.WAVE_OUTPUT_FILENAME = "/Users/Sree/Desktop/PythonPrograms/audio_output_test.wav"
def start(self):
print("here")
self.progress["value"] = 0
self.maxMin = 30
self.progress["maximum"] = 30
'''
I need the following 2 lines to happen at the same time
the progress bar needs to update from 1-30 seconds(which is the time I am recording)
'''
self.record()
self.update_progress() #The progress bar is from 0 sec to 30 sec indicating what percentage of the time the mic has been recording for.
def update_progress(self):
self.min += 0.1
self.progress["value"] = self.min
if self.min < self.maxMin:
# update after 100 ms
self.after(100, self.update_progress)
def record(self):
p = pyaudio.PyAudio()
stream = p.open(format=self.FORMAT,
channels=self.CHANNELS,
rate=self.RATE,
input=True,
frames_per_buffer=self.CHUNK)
print("* recording")
frames = []
for i in range(0, int(self.RATE / self.CHUNK * self.RECORD_SECONDS)):
data = stream.read(self.CHUNK)
frames.append(data)
print("* done recording")
stream.stop_stream()
stream.close()
p.terminate()
wf = wave.open(self.WAVE_OUTPUT_FILENAME, 'wb')
wf.setnchannels(self.CHANNELS)
wf.setsampwidth(p.get_sample_size(self.FORMAT))
wf.setframerate(self.RATE)
wf.writeframes(b''.join(frames))
wf.close()
app = Test_GUI()
app.mainloop()
To prevent the GUI from freezing while recording, you need to launch the recording in a separate thread. To do so, you can use the threading module for instance and replace self.record() in your start method by:
record_thread = threading.Thread(target=self.record, daemon=True)
record_thread.start()

program crashes when press button - Number guess game - Tkinter

I have no idea why this is not working, I have been looking and I cant see whats wrong I have messed with it for a while all I want is for it to work.
gets input outputs numbers equal to randomly generated if full number equals the random then you win but it just crashes when I press the button
from tkinter import *
from tkinter import ttk
import random
master = Tk()
master.title('Guess The Number!')
global answer
global guess_entry
global guess_display
answer = str(random.randint(1000,9999))
guess_counter = 0
def callback():
print('Button pressed')
counter = 0
correct = []
while counter < 4:
if guess_entry.get() == answer:
correct.append('Well done, that is correct')
break
elif guess_entry.get()[counter] == answer[counter]:
correct.append(guess_entry.get[counter])
counter += 1
guess_display['text'] = ' '.join(str(correct))
def Help():
win = Toplevel()
win.title('Help')
l = Label(win, text="Guess a 4 digit number and I will tell you\n what you got right,\n keep trying until you get it in the \ncorrect order with correct numbers")
l.grid(row=0, column=0)
b = Button(win, text="Okay", command=win.destroy)
b.grid(row=1, column=0)
guess_entry = Entry(master)
guess_check = Button(master, text='Guess', command=callback)
guess_display = Label(master,text='_ _ _ _')
help_button = ttk.Button(master, text="?", command=Help,width=3)
guess_entry.grid(row=0,column=2)
guess_check.grid(row=1,column=2)
guess_display.grid(row=2,column=1)
help_button.grid(row=0,column=4)
master.mainloop()
If the numbers aren't equal the first time through the loop, they won't be equal any other time through the loop since the user won't have a chance to change their answer while the loop is running.
You can see this by adding a print statement:
while counter < 4:
print("counter:", counter, "guess:", guess_entry.get())
...

How to create GUI objects one by one with Tkinter

I like to create an object per second and make the show up one by one. However, the code below wait for 3 seconds and show them all at the same time.
from tkinter import *
import time
def create():
for i in range(3):
r4=Radiobutton(root, text="Option 1"+str(i), value=1)
r4.pack( anchor = W )
time.sleep(1)
root = Tk()
create()
root.mainloop()
Your code, as is, creates a one object per second as you desired it, but this objects need to be shown, and they're shown when code flow reaches the mainloop. Hence, for observer, it looks like there're no objects at all after one second.
Of course, you can use sleep and update, but beware - sleeping leads to unresponsive window, so it's OK option (to be honest - not OK at all), if your application isn't drawn and you're outside of mainloop, but if it's not - prepare for a "frozen" window, because GUI can redraw himself only in mainloop (an event loop, you can reach it with update as well) and sleep blocks this behaviour.
But there's a good alternative, the after method, take a look on it!
And there's a snippet, so you can see the difference:
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
import time
def create_with_sleep():
for _ in range(3):
tk.Radiobutton(frame_for_sleep, text="Sleep Option").pack(anchor='w')
time.sleep(int(time_entry.get()))
root.update()
def create_with_after(times=3):
if times != 0:
tk.Radiobutton(frame_for_after, text="After Option").pack(anchor='w')
times -= 1
root.after(int(time_entry.get()) * 1000, lambda: create_with_after(times))
root = tk.Tk()
test_yard_frame = tk.Frame(root)
frame_for_sleep = tk.Frame(test_yard_frame)
frame_for_after = tk.Frame(test_yard_frame)
test_yard_frame.pack()
frame_for_sleep.pack(side='left')
frame_for_after.pack(side='left')
button_frame = tk.Frame(root)
button_for_sleep = tk.Button(button_frame, text='Create 3 radiobuttons with sleep+update', command=create_with_sleep)
button_for_after = tk.Button(button_frame, text='Create 3 radiobuttons with after', command=create_with_after)
button_frame.pack()
button_for_sleep.pack(side='left')
button_for_after.pack(side='left')
time_label = tk.Label(root, text='Time delay in seconds:')
time_label.pack(fill='x')
time_entry = tk.Entry(root)
time_entry.insert(0, 1)
time_entry.pack(fill='x')
root.mainloop()
With 1 seconds delay there's no much difference, but you can try to increase delay to understand why after option is preferable in general.
You can use update to call a refresh on your objects.
In use, you would have to add the line root.update() in your for loop.

Resources