I'm trying to print with delay (one character appears, a few milliseconds pass and then the next one appears) on the same widget multiple times one after the other, being something like >text appears with delay > a second passes> more text appears with delay... and so on. time.sleep() doesn't seems to work and i don't know how to properly use .after()
Here's the code i'm using
from tkinter import *
def insert_slow(widget, string):
if len(string) > 0:
widget.insert(END, string[0])
if len(string) > 1:
widget.after(50, insert_slow, widget, string[1:])
root=Tk()
tx=Text(root)
tx.pack()
insert_slow(tx, "this is a testing piece of text\n")
tx.after(3000)
loop=insert_slow(tx, "this is another testing piece of text")
root.mainloop()
The problem with your code is that after(3000) is almost exactly the same as time.sleep -- it freezes the whole UI.
The solution is pretty simple: use after to call your second insert_slow.
insert_slow(tx, "this is a testing piece of text\n")
tx.after(3000, insert_slow, tx, "this is another testing piece of text")
However, you need to be aware that after is relative to the time that you call after. Since the second line of code in the above example runs just a millisecond after the first, the second invocation won't happen 3 seconds after the first string appears, it will happen 3 seconds after it starts to appear.
If you want to wait for the first one to finish and then wait three seconds, you'll either have to do the math yourself (add 50ms times the number of characters to the starting value), or add some other mechanism. You could pass multiple strings to insert_slow, and it could automatically wait three seconds between each string.
Your code is executing the both texts in parallel so you get this output:
text1 = 'Hi to you'
text2 = 'Hi to me'
OUTPUT:
HHii tt oo ymoeu
Your insert_slow behaves well but you dont need to use after() again if you try to run text in two separate lines.
And if so, this should be on a different new text widget.
This code works if you want to output text on the same widget:
from tkinter import *
def insert_slow(widget, string):
if len(string) > 0:
widget.insert(END, string[0])
if len(string) > 1:
widget.after(50, insert_slow, widget, string[1:])
root=Tk()
tx=Text(root)
tx.pack()
text_body = "this is a testing piece of text\n" \
"this is another testing piece of text"
insert_slow(tx, text_body)
root.mainloop()
if you want the text lines to insert slow together, you can use this also:
from tkinter import *
def insert_slow(widget, string):
if len(string) > 0:
widget.insert(END, string[0])
if len(string) > 1:
widget.after(50, insert_slow, widget, string[1:])
root=Tk()
tx1=Text(root)
tx2=Text(root)
tx1.pack()
tx2.pack()
text_body1 = "this is a testing piece of text\n"
text_body2 = "this is another testing piece of text"
insert_slow(tx1, text_body1)
insert_slow(tx2, text_body2)
root.mainloop()
Related
I am trying to allow a user to input multiple answers but only within an allocated amount of time. The problem is I have it running but the program will not interrupt the input. The program will only stop the user from inputing if the user inputs an answer after the time ends. Any ideas? Is what I am trying to do even possible in python?
I have tried using threading and the signal module however they both result in the same issue.
Using Signal:
import signal
def handler(signum, frame):
raise Exception
def answer_loop():
score = 0
while True:
answer = input("Please input your answer")
signal.signal(signal.SIGALRM, handler)
signal.alarm(5)
try:
answer_loop()
except Exception:
print("end")
signal.alarm(0)
Using Threading:
from threading import Timer
def end():
print("Time is up")
def answer_loop():
score = 0
while True:
answer = input("Please input your answer")
time_limit = 5
t = Timer(time_limit, end)
t.start()
answer_loop()
t.cancel()
Your problem is that builtin input does not have a timeout parameter and, AFAIK, threads cannot be terminated by other threads. I suggest instead that you use a GUI with events to finely control user interaction. Here is a bare bones tkinter example.
import tkinter as tk
root = tk.Tk()
label = tk.Label(root, text='answer')
entry = tk.Entry(root)
label.pack()
entry.pack()
def timesup():
ans = entry.get()
entry.destroy()
label['text'] = f"Time is up. You answered {ans}"
root.after(5000, timesup)
root.mainloop()
I would like a label to be .grid() then the program to wait 3 seconds and then .grid_forget(). I am very confused at the point .grid is executed. For example:
def remove_choice(self):
while True:
try:
get = int(self.entry_remove_choice.get())
except ValueError:
self.label_error_remove.grid(row=10,column=6) #A
time.sleep(3)
self.label_error_remove.grid_forget() #B
#Empty entry box
break
else:
#continue code
break
Once the button is pressed and remove_choice is executed, the button is displayed to be pressed in for three seconds then #A and #B are executed in one go and nothing is displayed.
If #B is removed then the error message is displayed after three seconds.
If #A and #B are swapped for print to terminal then program works how you would think, with one message, a wait of three seconds, then another message.
If you do a very sloppy solution (which Im not that bothered about for this program) and do this:
def remove_choice(self):
while True:
try:
get = int(self.entry_remove_choice.get())
except ValueError:
self.label_error_remove.grid(row=10,column=6) #A
for n in range (1,1000):
print("abc")
self.label_error_remove.grid_forget()
break
else:
#continue code
break
When executed "abc" is printed 1000 times taking around 1.5 seconds and then after this the program displays the grid.
Any suggestions to how to make TKinter wait please.
Also can someone explain why grid works like this, thanks.
Rather than trying 'forgetting' the label each time, why not just clear the error message text?
My example below will wait for the user to press the button and display the error message for 3 seconds. I'm using the .after method to schedule the hideError method 3 seconds (3000 ms) after the error message is displayed.
try:
import tkinter as tk
except:
import Tkinter as tk
import time
class App(tk.Frame):
def __init__(self,master=None,**kw):
tk.Frame.__init__(self,master=master,**kw)
self.errorMessage = tk.StringVar()
self.ErrorLabel = tk.Label(textvar=self.errorMessage)
self.ErrorLabel.grid()
self.button = tk.Button(text="Press Me",command=self.showError)
self.button.grid()
def showError(self):
# Disable the button and show the error message
self.button['state'] = tk.DISABLED
self.errorMessage.set("Error Message!!!!")
self.after(3000,self.hideError)
def hideError(self):
#Enable the button and clear the error message.
self.button['state'] = tk.NORMAL
self.errorMessage.set("")
if __name__ == '__main__':
root = tk.Tk()
App(root).grid()
root.mainloop()
It is considered bad practice to use while True loops or time.sleep inside GUI applications. They prevent the GUI from updating so in your code both actions appear to happen at the same time because the time.sleep operation is blocking the GUI and preventing the screen from being redrawn.
EDIT: Passing arguments from callbacks.
Current problem is that the after method expects to receive a reference to a function. self.hideError(3) returns NoneType not reference to a function call. We can solve this using anonymous functions and lambda.
I've started to use this snippet of code to help, its from guizero
def with_args( func_name, *args):
"""Helper function to make lambda functions easier
Thanks to guizero"""
return lambda: func_name(*args)
Then in your main section of code the line would look like this.
self.after(3000,with_args(self.hideError,3))
EDIT: There is an even simpler way. The .after method can take arguments itself.
self.after(3000,self.hideError,3)
How do you make a simple dialog box that asks for an integer input in python 3?
The functionality should be similar to input() where it prompts the user, saves it to a variable, and does not continue the program until the input is received.
It can use any imports, as long as they are compatible with python 3.
I've written this code, I hope it does what you want, otherwise add comment the problem, and I'll try to fix it.
from tkinter import *
def inp():
global inpt
number=inpt.get()
try:
int(number)
info.configure(text=number)
except ValueError:
info.configure(text="Please enter an integer")
root.update()
root=Tk()
root.geometry("300x100-0+0")
Label(root,text="Input ", height=1, width=7).grid(row=0)
inpt=Entry(root, width=35)
inpt.grid(row=0, column=1)
info=Label(root,text="", height=1)
info.grid(row=3, column=1)
get=Button(root, text="Input", command=inp)
get.grid(row=2, column=1)
mainloop()
I would like to write text to a file multiple times (appending).
Currently I am using: In this scenario I captured the system time in endtime and starttime.
File.open("c:\\temp\\myfile.txt", "w").write("My first input: #{endtime-starttime} seconds \n")
I would like to continue this multiple times in various places in my script but it does not seem to be working correctly. The text file seems to be writing over itself. Is there a way in a script to write text in different rows?
Thanks,
Scott
Here's a better example
#------------------------------------------------------------------------------------------------ ---------------------
#Log into System
#---------------------------------------------------------------------------------------------------------------------
starttime=Time.new
LoginButton = driver.find_element(:xpath, "/html/body/div[3]/div/div[2]/ul/li[3]/a")
LoginButton.click
option = driver.find_element(:xpath, "/html/body/div/div[1]/div/div[3]/div[1]/form/div[3]/ul/li[1]/input")
option.send_keys"blah"
option = driver.find_element(:id, "password")
option.send_keys"blah"
option = driver.find_element(:xpath, "/html/body/div/div[1]/div/div[3]/div[1]/form/div[3]/ul/li[3]/input")
option.click
endtime=Time.new
puts"Login: #{endtime-starttime} seconds"
File.open("c:\\temp\\myfile.txt", "w").write("Login: #{endtime-starttime} seconds \n")
puts"Login Done"
#---------------------------------------------------------------------------------------------------------------------
#Performance Test Course
#---------------------------------------------------------------------------------------------------------------------
starttime=Time.new
driver.switch_to.frame "contentFrame" #Sets focus on the "UR Courses Online Module"
option = driver.find_element(:link_text, "Performance Test Course")
option.click
endtime=Time.new
puts"Performance Test Course Link: #{endtime-starttime} seconds"
File.open("c:\\temp\\myfile.txt", "w").write("Performance Test Course Link: #{endtime-starttime} seconds \n")
puts"Performance Test Course Done"
You probably want to store the open file object in a variable so that when you write a new line to your file it is written to the end. Your previous code would open the given file write the line at position 0 in the file then close the file.
file = File.open("text.txt", "w")
file.write "hello world"
Or you can use the "a" flag which opens a file for write at the end of the file.
File.open("c:\\temp\\myfile.txt", "a+").write("My first input: #{endtime-starttime} seconds \n")
You are using w which truncates the file before writing to it.
Use:
File.open("c:\\temp\\myfile.txt", "a+").write("My first input: #{endtime-starttime} seconds \n")
I have a simple GUI which run various scripts from another python file, everything works fine until the GUI is running a function which includes a while loop, at which point the GUI seems to crash and become in-active. Does anybody have any ideas as to how this can be overcome, as I believe this is something to do with the GUI being updated,Thanks. Below is a simplified version of my GUI.
GUI
#!/usr/bin/env python
# Python 3
from tkinter import *
from tkinter import ttk
from Entry import ConstrainedEntry
import tkinter.messagebox
import functions
AlarmCode = "2222"
root = Tk()
root.title("Simple Interface")
mainframe = ttk.Frame(root, padding="3 3 12 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)
ttk.Button(mainframe, width=12,text="ButtonTest",
command=lambda: functions.test()).grid(
column=5, row=5, sticky=SE)
for child in mainframe.winfo_children():
child.grid_configure(padx=5, pady=5)
root.mainloop()
functions
def test():
period = 0
while True:
if (period) <=100:
time.sleep(1)
period +=1
print(period)
else:
print("100 seconds has passed")
break
What will happen in the above is that when the loop is running the application will crash. If I insert a break in the else statement after the period has elapsed, everything will work fine. I want users to be able to click when in loops as this GUI will run a number of different functions.
Don't use time.sleep in the same thread than your Tkinter code: it freezes the GUI until the execution of test is finished. To avoid this, you should use after widget method:
# GUI
ttk.Button(mainframe, width=12,text="ButtonTest",
command=lambda: functions.test(root))
.grid(column=5, row=5, sticky=SE)
# functions
def test(root, period=0):
if period <= 100:
period += 1
print(period)
root.after(1000, lambda: test(root, period))
else:
print("100 seconds has passed")
Update:
In your comment you also add that your code won't use time.sleep, so your original example may not be the most appropiate. In that case, you can create a new thread to run your intensive code.
Note that I posted the alternative of after first because multithreading should be used only if it is completely necessary - it adds overhead to your applicacion, as well as more difficulties to debug your code.
from threading import Thread
ttk.Button(mainframe, width=12,text="ButtonTest",
command=lambda: Thread(target=functions.test).start())
.grid(column=5, row=5, sticky=SE)
# functions
def test():
for x in range(100):
time.sleep(1) # Simulate intense task (not real code!)
print(x)
print("100 seconds has passed")