Program was working fine until I added the Start button on the bottom. Program seems to be running properly except that the timer doesn't update. I would like the counter to start counting down as soon as the "Start" button is clicked in my program.
My code:
import time
import tkinter as tk
def DisplayCountdown():
# Time
m = 0
s = 3
print('Running DisplayCountdown function')
if m == 0 and s == 0:
count_down_display.configure(text='Stand up!')
elif s == 0:
s=59
m-=1
count_down_display.after(1000, DisplayCountdown)
else:
s-=1
countdown_display.configure(text='%s minutes %s seconds' % (m,s))
countdown_display.after(1000, DisplayCountdown)
# Window
window = tk.Tk()
window.title('Stand Up Timer')
window.configure(bg = 'black')
# Information label
start_label = tk.Label(window,
font = 'ariel 40',
bg = 'black',
fg = 'red',
text = 'Click the start button to begin the timer.')
start_label.grid(row = 0, column = 0)
# Display Countdown
countdown_display = tk.Label(window,
font = 'ariel 40',
bg = 'black',
fg = 'red',
text = 'Countdown displayed here!')
countdown_display.grid(row = 1, column = 0)
# Start button
start_button = tk.Button(window,
text='Start',
command=DisplayCountdown)
start_button.grid(row = 2, column = 0)
# Window main loop
window.mainloop()
You have a few things to correct here.
First these 2 lines:
m = 0
s = 3
They cannot be in the function like this. Basically every loop will always start off a m=0 s=3. So instead pass them into the function as arguments.
Next Update your command and after statements to use a lambda to pass the initial and new time variables.
Next get rid of all those comments. They are redundant. Good code does not need comments as it is very obvious what is going on. Comments are normally need for complex sections of code that may need explaining for your self or other down the road.
I don't like going to new lines after every argument for widgets. Its just messy IMO the way you have it now. I write everything on a single line unless we overreach PEP8 recommended line length then find a good spot to go to new line.
Working example:
import tkinter as tk
def display_countdown(m, s):
print('Running DisplayCountdown function')
if m == 0 and s == 0:
countdown_display.configure(text='Stand up!')
elif s == 0:
s = 59
m -= 1
countdown_display.after(1000, lambda: display_countdown(m, s))
else:
s -= 1
countdown_display.configure(text='%s minutes %s seconds' % (m, s))
countdown_display.after(1000, lambda: display_countdown(m, s))
window = tk.Tk()
window.title('Stand Up Timer')
window.configure(bg='black')
start_label = tk.Label(window, font='ariel 40', bg='black', fg='red',
text='Click the start button to begin the timer.')
countdown_display = tk.Label(window, font='ariel 40', bg='black', fg='red',
text='Countdown displayed here!')
start_button = tk.Button(window, text='Start',
command=lambda: display_countdown(0, 3))
start_label.grid(row=0, column=0)
countdown_display.grid(row=1, column=0)
start_button.grid(row=2, column=0)
window.mainloop()
Related
So I'm working on a function does something (turns a motor) every second. For counting the second I use time.sleep(1) because I don't know any other way to wait.
def wash_loop(wash_time):
count = 0
dist = 0
global error_flag
error_flag = 0
while (count < wash_time):
if(count%2==0):#going forward
GPIO.output(Motor_IN1,GPIO.HIGH)
GPIO.output(Motor_IN2,GPIO.LOW)
GPIO.output(Motor_EN,GPIO.HIGH)
print(count)
time.sleep(MOTOR_SLEEP)
else:#going backwards
GPIO.output(Motor_IN1,GPIO.LOW)
GPIO.output(Motor_IN2,GPIO.HIGH)
GPIO.output(Motor_EN,GPIO.HIGH)
print(wash_time - count) #want to display this on a Tkinter window instead
time.sleep(MOTOR_SLEEP)
count+=1
dist = distance_check()
if(dist < CRITICAL_DIS):
error_alert()
break
if(count>=wash_time):
GPIO.output(Motor_EN, GPIO.LOW)
break
The Tkinter function that I'm trying to do this in looks like this:
def wash_window():
#create the main window for GUI control applcation
window2 = TK.Tk()
temp = TK.IntVar()
window2.geometry(WINDOW_GEOMETRY)
window2.title("Wash Cycle ")
#create the frame that will hold instructions
frame0 = TK.Frame(window2)
frame0.pack(side=TK.TOP)
TK.Label(frame0, text="You've selected custom wash cycle").grid(row=0,column=0)
TK.Label(frame0, text="Please select the water temperature for the wash.")\
.grid(row=1,column=0)
frame1 = TK.Frame(window2)
frame1.pack(side=TK.TOP)
temp.get()
hot_button = TK.Radiobutton(frame1, text="HOT", variable=temp, value=1 ).grid(row=0, column=0)
cold_button = TK.Radiobutton(frame1, text="COLD", variable=temp, value=2).grid(row=0,column=1)
warm_button = TK.Radiobutton(frame1, text="WARM", variable=temp, value=3).grid(row=0,column=2)
#create the frame that will hold control entry in the window
frame2 = TK.Frame(window2)
frame2.pack(side=TK.TOP)
TK.Label(frame2, text = "Please enter the time below for the wash cycle").grid(row=0,column=0)
user_entry = TK.Entry(frame2)
user_entry.grid(row=1,column=0)
frame3= TK.Frame(window2)
frame3.pack(side=TK.TOP)
start_button = TK.Button(frame3, text="START", command = lambda: wash_cycle(user_entry,temp)).grid(row=0, column=0)
# stop_button = TK.Button(frame3, text="STOP", command = lambda: wash_cycle(user_entry,temp)).grid(row=0, column=1)
quit_button = TK.Button(frame3, text="QUIT", command = window2.destroy).grid(row=0, column=2)
What I'm trying to do is display the countdown (from the entered time to 0) on this Tkinter window as soon as the person presses start in the window. This is different from using the after() function because I want to show a value from a function which is only executing once, only with the exception that it has a loop.
Thank You!
So what you could do is work with threading.
You Need to:
from threading Import Timer
Then you create a timer
your_timer = Timer(the_seconds_you_want_to_wait, the_function_to_call)
You are calling a function with this and in that function you can display anything in your tkinter window.
What I recommend you to do here is create a Label and then change the properties of it.
For example if you want to show a number on a label:
L1 = Label(root, text="your text here")
L1.pack()
Now if you want to edit that later:
L1.configure(text="your new text here")
your_timer.start()
And at the end of your function you Need to Start the timer in order to the create a cycle or just create a Loop for it.
I have a program that works with an user interface : I start it using a button and I would like to have the possibility to stop it during the process using the user interface:
The problem is : When I start the program, I cannot click on any buttons because when I drag the mouse in the tkinter window I have the "looping" / "waiting" cursor and it doesn't allow me to click on any buttons inside the interface.
I tried to start the infinit loop function in a new thread, but I don't know if it is really a good way to solve my problem... Any of you have any solutions ?
I try to simulate my problem with a simple code :
from tkinter import *
import loop
window = Tk()
window.title("test")
start = Button(window, text="start", bg ="green", command=loop.start)
stop = Button(window, text="stop", bg ="green", command=loop.stop)
thread = Button(window, text="threads", bg ="green", command=loop.how_many_threads)
start.grid(column = 1, row = 1)
stop.grid(column = 2, row = 1)
thread.grid(column = 3 , row = 1)
window.mainloop()
and few functions :
import threading
continu = True
def infinit(x):
cpt = 0
while(x):
cpt += 1
#print(cpt)
print("loop is over !")
def start():
threadSimulation = threading.Thread(target= infinit(continu)).start()
def stop():
self.continu = False
def how_many_threads():
for t in threading.enumerate():
print("thread names : ",t.getName())
Here's how to fix your implementation. The biggest error is that you have to provide arguments to the thread in the args part, not like a normal function call.
import tkinter as tk
import threading
import time
def infinit(x):
cpt = 0
while(continu):
cpt += 1
print(cpt)
time.sleep(1)
print("loop is over !")
def start():
threadSimulation = threading.Thread(target= infinit, args=(continu,))
threadSimulation.daemon = True
threadSimulation.start()
def stop():
global continu
continu = False
def how_many_threads():
for t in threading.enumerate():
print("thread names : ",t.getName())
continu = True
window = tk.Tk()
window.title("test")
start = tk.Button(window, text="start", bg ="green", command=start)
stop = tk.Button(window, text="stop", bg ="green", command=stop)
thread = tk.Button(window, text="threads", bg ="green", command=how_many_threads)
start.grid(column = 1, row = 1)
stop.grid(column = 2, row = 1)
thread.grid(column = 3 , row = 1)
window.mainloop()
If you want to close the program, you can use "window.quit" in the command section of your button like this:
stop = Button(window, text="stop", bg ="green", command=window.quit)
I am trying to populate my screen with some data that refreshes every given time interval. I am using Python3, themed tkinter. Every time my screen updates I see grey flickerings on screen for every label. Is there a way to avoid this?
P.S : I am calling the 'after' method to refresh data.
UPDATE: Here is some example code:
def button1Click(self):
self.top = Toplevel(width=600,height=400)
self.top.title("XYZ ORGANIZATION")
self.frame1 = Frame(self.top,bg='#009999')
self.frame1.pack()
self.noOfEmp = Label(self.frame1,text = "Number_Of_Employees : ", font =('Verdana',9, 'bold'),bg='#009999',fg = '#000000')
self.noOfEmp.grid(row=1,column=0,sticky=W,padx=0,pady=5)
self.TeamLabel = Label(self.frame1,text = "Team Name : ", font =('Verdana',9, 'bold'),bg='#009999',fg = '#000000')
self.TeamLabel.grid(row=2,column=0,sticky=W,padx=0,pady=5)
self.text = Text(self.frame1, bg='#009999')
self.text.grid(row=8,columnspan=17)
self.old_emp = 0
self.EFile = open('/abc','r').readlines()
for line in self.EFile:
if line.startswith('EmpTotal:'):
self.Tot_Emp = int(line.split()[1])
break
t1 = threading.Thread(target=self.__make_layout, args = ())
t1.daemon = True
t1.start()
t2 = threading.Thread(target=self.ProcEmp,args = ())
t2.daemon = True
t2.start()
def self.__make_layout:
self.CLabelVal = Label(self.frame1,text = CSpace, font=('Verdana',9),bg='#009999',fg = '#000000')
self.MLabelVal = Label(self.frame1,text = MSpace , font =('Verdana',9),bg='#009999',fg = '#000000')
self.Label1Val.grid(row=4,column=1,sticky=W+E+N+S,padx=5,pady=5)
self.Label2Val.grid(row=5,column=1,sticky=W+E+N+S,padx=5,pady=5)
self.frame1.after(5000,self.__make_layout)
Part of the problem is that you keep stacking more and more widgets on top of each other. You should create the labels exactly once, and then change what they display every five seconds, instead of creating new widgets every five seconds.
There's also the problem that you're creating the labels in a thread. Tkinter isn't thread safe. Any code that creates or modifies a widget needs to be in the main thread. To update the labels you don't need threads, though you can use a thread to change what actually gets displayed.
def __make_layout(self):
self.CLabelVal = Label(...,text = CSpace, ...)
self.MLabelVal = Label(...,text = MSpace, ...)
self.Label1Val.grid(...)
self.Label2Val.grid(...)
def __update_layout(self):
self.CLabelVal.configure(text=CSpace)
self.MLabelVal.configure(text=MSpace)
self.after(5000, self.__update_layout)
I made a small program based on what you provided.
This is what i got. I chose 500ms instead because i did not want to wait that long. I ran two internet videos at the same time and had no issues.
So my best guess you have a slow video card or over loaded computer.
from tkinter import *
class MyClass:
frame1 = Tk()
poll = 0
def __init__(self):
self.frame1.after(500, self.__make_layout)
def __make_layout(self):
self.poll += 1
CSpace = "Poll count = "*20
MSpace = str(self.poll)
self.CLabelVal = Label(self.frame1, text=CSpace, font=('Verdana', 9), bg='#009999', fg='#000000')
self.MLabelVal = Label(self.frame1, text=MSpace, font=('Verdana', 9), bg='#009999', fg='#000000')
self.CLabelVal.grid(row=4, column=1, sticky=W+E+N+S, padx=5, pady=5)
self.MLabelVal.grid(row=5, column=1, sticky=W+E+N+S, padx=5, pady=5)
print(CSpace, MSpace)
return self.frame1.after(500, self.__make_layout)
MyClass()
mainloop()
This doesn't create more labels and uses the "textvariable" update feature.
from tkinter import *
class MyClass:
frame1 = Tk()
poll = 0
textstg = StringVar()
CSpace = "Poll count"
def __init__(self):
self.frame1.after(500, self.__make_layout)
self.CLabelVal = Label(self.frame1, text=self.CSpace, font=('Verdana', 9), bg='#009999', fg='#000000')
self.MLabelVal = Label(self.frame1, textvariable=self.textstg, font=('Verdana', 9), bg='#009999', fg='#000000')
self.CLabelVal.grid(row=4, column=1, sticky=W+E+N+S, padx=5, pady=5)
self.MLabelVal.grid(row=5, column=1, sticky=W+E+N+S, padx=5, pady=5)
def __make_layout(self):
self.poll += 1
self.textstg.set(str(self.poll))
return self.frame1.after(50, self.__make_layout)
MyClass()
mainloop()
So I am making a stopwatch in python with tkinter, I have the loop for updating the time working, but I have it so the loop clears the text box, and then updates the text box with the new number.
Although it doesnt work, for some reason it just doesnt clear it, it just keeps adding numbers to the box.
Here is the code that I have used, if someone would be able to have a look at this for me I would appriciate it a lot :)
import time
from tkinter import *
root = Tk()
root.title("StopWatch")
#textbox for the screen
screen = Text(root, height = 1, width = 20, bd=10)
screen.grid(row=0, column=0)
#Active variable
global stopwatch_active
stopwatch_active = False
stop_time = 0
stop_minutes = 0
#command for starting the stopwatch
def start_com():
stop_btn.config(state=NORMAL)
stopwatch_active = True
start_btn.config(state=DISABLED)
global stop_time
stop_time += 1
screen.insert(END, stop_time)
root.after(1000, start_com)
#button for starting the stopwatch
start_btn = Button(root, text = "Start", width = 10, bd = 5, command = start_com)
start_btn.grid(row=1, column=0, sticky=W)
#button for stopping the stopwatch
stop_btn = Button(root, text = "Stop", width = 10, bd = 5)
stop_btn.grid(row=1, column=0, sticky=E)
stop_btn.config(state=DISABLED)
Add:
screen.delete("1.0", END)
Before you do:
screen.insert(END, stop_time)
This will clear all the text from the text box. Effbot has more information if your interested. This will produce something similar to:
First I created a window with some buttons and I defined their commands. It all works fine until I add the while loop to check if any button was pressed and then move to the next step. But then the window doesn't show up and the loop is running forever. I would also like to know if there is a better alternative to my code.
from tkinter import *
Round = 0
def blackC():
global Round
print ('0')
x = 0
Round += 1
def brownC():
global Round
print ('1')
x = 1
Round +=1
def redC():
global Round
print ('2')
x = 2
Round += 2
def win():
window = Tk()
window.geometry ('500x500')
window.title('HELLO')
blackB = Button(text = 'BLACK', command=blackC, width=7, height=3, bd=5)
blackB.place(x=1, y=1)
brownB = Button(text = 'BROWN', command=brownC, width=7, height=3, bd=5)
brownB.place(x=86, y=1)
redB = Button(text = 'RED', command=redC, width=7, height=3, bd=5)
redB.place(x=172, y=1)
window.mainloop()
while (Round == 0):
win()
while (Round < 3):
if (Round == 1):
y = x * 10
print ('y')
elif (Round == 2):
y += x
print ('y')
I don't know what exactly you mean by moving to the next step, but you definitely misunderstand how tkninter works. You are missing the parenthesis in the mainloop window.mainloop().
And you don't want to call it in the cycle, because mainloop is a function which is cycle. So you just run it once a time a then it runs infinitely. So your code have to just run once a time function win().
from tkinter import *
Round=0
def button(type):
global Round
print (str(type))
x = type
Round += type
def win():
window = Tk()
window.geometry ('500x500')
window.title('HELLO')
blackB = Button(text = 'BLACK', command=lambda: button(0), width=7, height=3, bd=5)
blackB.place(x=1, y=1)
brownB = Button(text = 'BROWN', command=lambda: button(1), width=7, height=3, bd=5)
brownB.place(x=86, y=1)
redB = Button(text = 'RED', command=lambda: button(2), width=7, height=3, bd=5)
redB.place(x=172, y=1)
window.mainloop
win()
You have asked for better code, so I have rewritten your buttons func for a one, which just takes an parametr of type and call it as a lambda function (take a look: http://www.diveintopython.net/power_of_introspection/lambda_functions.html.
For a larger projects it is better to have tkinter window as a class, but in this is it enough.