i was wondering if it would be possible to output text slowly using tkinter and a label
the main problem is I don't know how to get it to flush. I press the button, and then it waits 9 seconds, then it prints the whole thing.
How do i get this to work with a flush
i've tried label1.flush(), but it gives me an error
also sys.stdout.flush() does't work, but it makes sense because this is to the console
def clicked():
txt = "hello world"
a = txt[0]
label1 = tk.Label(window,text=a)
label1.place(x=60,y=30)
time.sleep(9)
b = txt[1]
c = a+b
label1 = tk.Label(window,text=c)
label1.place(x=60,y=30)
Here is a way to do this using .after loops:
from tkinter import Tk, Label
def print_slow(widget: Label, text, delay: 'in miliseconds', index=1, start_index=0):
widget.config(text=text[start_index: index])
index += 1
return root.after(delay, print_slow, widget, text, delay, index) if index <= len(text) else None
root = Tk()
label = Label(root, text='')
label.pack()
print_slow(label, 'hello world', 2000)
root.mainloop()
So pretty simple:
First import only what You need, don't use wildcard (*)
Second define the function, it will take arguments such as the widget to which it is needed to print slowly, then the text that has to be printed, then delay in milliseconds between characters appearing on the screen, then the end index and start index.
Then simple tkinter stuff:
.config() the widget's text attribute to the text from start_index to the end index
Then increment the end index by one
Then use .after() to schedule the same function (loop) after the given delay and pass the incremented index argument
Then the simple tkinter stuff and also remember to initially call the function
EDIT: updated the function to actually stop when finished, used some ternary conditions to return None (basically stop in this case) if the index is bigger than the lenght of the text
You should not use time.sleep with tkinter as it blocks the GUI processing. This causes it to print the whole thing in one go. Flushing is not possible because this is a Tkinter GUI, not a console, so there is no stdout to flush. Instead, you have to do something like this:
import tkinter as tk
def clicked():
n = 0
txt = "hello world"
showChar(n, txt)
def showChar(n, txt):
n += 1
label.config(text = txt[:n])
if n < len(txt):
root.after(9000, lambda: showChar(n, txt))
root = tk.Tk()
label = tk.Label(root, text = "")
label.pack()
button = tk.Button(root, text = "Start", command = clicked)
button.pack()
root.mainloop()
When the button is clicked, the counter is initialised (set to 0) and the text is set. Then showChar is called. This function increments the counter, then updates the label text to show an extra character. If the string hasn't been full displayed yet, it waits 9000ms (or 9 seconds) until it runs itself again, using the tkinter method root.after. This then works as you want it to.
Related
So, I'm trying to create a basic Tkinter program which, when I press a button, updates the text on a label field, waits X amount of seconds and then update the label again.
For example:
I click the button, the label clears immediately after pressing it, then the program waits 3 seconds and shows "Hello" on screen.
The code shown below does not do what I want it to do because when I press the button, it remains pressed for X amount of time and then the text is updated inmediately. I want to press the button, clear the label, wait for 3 seconds and then show "Hello" on screen.
from tkinter import *
class Origin:
def __init__(self):
self.root = Tk()
self.root.geometry('800x600')
self.root.config(bg="black")
self.v = StringVar()
self.v.set('O R I G I N')
self.main_label = Label(self.root, textvariable=self.v, font="Arial 40", fg="white", bg="black")
self.main_label.place(x=240, y=150)
self.clear = Button(self.root, text='Clear', command=self.clear)
self.clear.place(x=400, y=400)
self.root.mainloop()
def clear(self):
#just to clear the string
self.v.set('')
self.root.after(3000, self.v.set('Hello'))
def main():
App = Origin()
if __name__ == '__main__':
main()
after needs callback - it means function's name without () and arguments. If you have to use function with argument then use `lambda
after(3000, lambda:self.v.set('Hello'))
or create function which doesn't need arguments
def callback():
self.v.set('Hello')
self.root.after(3000, callback)
Your current code works like
result = self.v.set('Hello')
self.root.after(3000, result)
It executes function self.v.set('Hello') at once and uses its result as callback in after().
EDIT: as #acw1668 said in comment you can also run function with arguments this way
self.root.after(3000, self.v.set, 'Hello')
I've been trying to create a piece of code that would take a integer as a argument and create that number of tkinter entry fields. With a submit button at the end that would retrieve the data from the fields add these data to a list then close the window.
I have been able to get it working however I cant find a way to convert this to a callable function; a requirement to use it with the rest of my program.
This is the code I have produced so far, thanks:
import tkinter as tk
b = input("Enter: ")
b = int(b)
root = tk.Tk()
newdict = dict()
outputs = list()
for i in range(b):
newdict["entry" + str(i)] = tk.Entry(root)
newdict["entry" + str(i)].pack()
button1 = tk.Button(root, text="Submit", command=lambda: Get(newdict))
button1.pack()
def Get(newdict):
for j in range(b):
outputs.append(newdict["entry" + str(j)].get())
root.quit()
root.mainloop()
print(outputs)
The basic idea is to create a window, then use the wait_window method to wait for the window to be destroyed. Once it has been destroyed you can return some value.
The problem is that the values you want to fetch must not be attributes of the window, since it will have been destroyed by the time you are ready to fetch them. You need to set up your code to save the values before the window is destroyed.
A simple way is to provide an "OK" button which gets the values and then destroys the window. Another way would be to put a trace on variables associated with each entry, and save the values immediately as they are edited.
Which method you choose depends on what behavior you want when the user clicks the window control to close the window (eg: the red circle on OSX, the [x] button on windows, etc). Do you want to return what they had input, or do you treat that as a cancel action and return nothing?
Here's a simple example using an OK button. This example assumes that you aren't already running a GUI, and that this is to be run as part of a non-GUI application.
import tkinter as tk
class Dialog(object):
def show(self, num_fields):
self.num_fields = num_fields
self.root = tk.Tk()
self.entries = []
for i in range(num_fields):
entry = tk.Entry(self.root)
entry.pack(fill="x")
self.entries.append(entry)
ok = tk.Button(self.root, text="OK", command=self.ok)
ok.pack(side="bottom", anchor="e", pady=(10,0), padx=10)
# wait for the window to be destroyed, then
# return the values. If the user clicks the OK button
# the values will be set; if they cancel the dialog
# this will return None.
self.values = None
self.root.wait_window()
return self.values
def ok(self):
# save all the values, then destroy the window
self.values = []
for i in range(self.num_fields):
self.values.append(self.entries[i].get())
self.root.destroy()
Assuming you're running a non-gui program, here's an example of how you would use this class:
b = input("Enter: ")
b = int(b)
result = Dialog().show(b)
print("result:", result)
Im trying to make a little program that endlessly prints out numbers inside GUI window, I can not find a way to print the out put of the function in a text box inside the GUI window instead of the python shell, please help, here is my code so far...
import sys
from tkinter import *
root = Tk()
def number(event):
x = 420
while True:
x +=420
print(x^70)
button_1 = Button(root, text="Start...")
button_1.bind("<Button-1>", number)
button_1.pack()
root.mainloop()
Thanks Harvey
You'll find it hard to constantly insert a value into a widget. The widget does not update with each insert. You can think of it has having a temporary variable for it. It can be accessed during the loop (as shown with print). However you'll notice that the widget itself doesn't update until the loop is over. So if you have while True then your widget will never update, and so you won't have the numbers streaming into the widget.
import sys
from tkinter import *
root = Tk()
def number():
x = 420
while x < 8400: # Limit for example
x +=420
textbox.insert(END, str(x^70)+'\n')
print(textbox.get(1.0, END)) # Print current contents
button_1 = Button(root, text="Start...", command=number) #Changed bind to command, bind is not really needed with a button
button_1.pack()
textbox = Text(root)
textbox.pack()
root.mainloop()
I want to introduce some delay in my code. I am using text.after() to get the delay but my tkinter window opens after that specified delay and the ouput is already printed on it.
Basically I am trying to replicate the functionality of sleep(), that is the lines of code should get executed before the timer starts. Then the timer should introduce 4 secs of delay and then the following lines of code should execute. I want to automate some test equipment and I want the next equipment to turn on after 4 secs of delay after the previous equipment was turned on.
To explain this situation, I will use a very simple code. I want the "Start Timer" to appear first. Then I want the numbers 1,2,3,4 appear on the tkinter GUI after 1 sec interval, like a timer going on from 1 to 4. Then I want "Stop Timer to appear". In this way the execution of the code is delayed by 4sec in between.
Is there a way to do this ?
Here is my updated example code:
from Tkinter import *
root = Tk()
text = Text(root)
text.pack()
def append_number(n):
text.insert(END, str(n) + "\n")
text.see(END)
# add one, and run again in one second
if n > 0:
root.after(1000, append_number, n-1)
# start the auto-running function
text.insert(END, "Start Timer")
append_number(5)
text.insert(END, "Stop Timer")
root.mainloop()
You can use after to cause a function to run in the future. A simple solution is to call that immediately in the loop and schedule all of the updates:
from Tkinter import *
root = Tk()
text = Text(root)
text.pack()
def append_number(n):
text.insert(END, str(n) + "\n")
text.see(END)
for i in range(1, 5, 1):
root.after(i*1000, append_number, i)
root.mainloop()
Another common pattern is to have the function automatically reschedule itself, and then quit after a certain condition. For example:
from Tkinter import *
root = Tk()
text = Text(root)
text.pack()
def append_number(n):
text.insert(END, str(n) + "\n")
text.see(END)
# add one, and run again in one second
if n < 4:
root.after(1000, append_number, n+1)
# start the auto-running function
append_number(1)
root.mainloop()
Change the loop into a function that you can call and don't run a loop inside of it, instead keep track of the values as they change:
from Tkinter import *
root = Tk()
text = Text(root)
text.pack()
def updater(i, j):
if i <= j:
text.insert(END, i)
text.insert(END, "\n")
text.yview_pickplace("end")
i += 1
text.after(1000, updater, *[i, j])
root.after(1000, updater, *[0, 10])
root.mainloop()
This of course is a very general example that you will need to form to fit your own application.
I am trying to set the text of an Entry widget using a button in a GUI using the tkinter module.
This GUI is to help me classify thousands of words into five categories. Each of the categories has a button. I was hoping that using a button would significantly speed me up and I want to double check the words every time otherwise I would just use the button and have the GUI process the current word and bring the next word.
The command buttons for some reason are not behaving like I want them to. This is an example:
import tkinter as tk
from tkinter import ttk
win = tk.Tk()
v = tk.StringVar()
def setText(word):
v.set(word)
a = ttk.Button(win, text="plant", command=setText("plant"))
a.pack()
b = ttk.Button(win, text="animal", command=setText("animal"))
b.pack()
c = ttk.Entry(win, textvariable=v)
c.pack()
win.mainloop()
So far, when I am able to compile, the click does nothing.
You might want to use insert method. You can find the documentation for the Tkinter Entry Widget here.
This script inserts a text into Entry. The inserted text can be changed in command parameter of the Button.
from tkinter import *
def set_text(text):
e.delete(0,END)
e.insert(0,text)
return
win = Tk()
e = Entry(win,width=10)
e.pack()
b1 = Button(win,text="animal",command=lambda:set_text("animal"))
b1.pack()
b2 = Button(win,text="plant",command=lambda:set_text("plant"))
b2.pack()
win.mainloop()
If you use a "text variable" tk.StringVar(), you can just set() that.
No need to use the Entry delete and insert. Moreover, those functions don't work when the Entry is disabled or readonly! The text variable method, however, does work under those conditions as well.
import Tkinter as tk
...
entry_text = tk.StringVar()
entry = tk.Entry( master, textvariable=entry_text )
entry_text.set( "Hello World" )
You can choose between the following two methods to set the text of an Entry widget. For the examples, assume imported library import tkinter as tk and root window root = tk.Tk().
Method A: Use delete and insert
Widget Entry provides methods delete and insert which can be used to set its text to a new value. First, you'll have to remove any former, old text from Entry with delete which needs the positions where to start and end the deletion. Since we want to remove the full old text, we start at 0 and end at wherever the end currently is. We can access that value via END. Afterwards the Entry is empty and we can insert new_text at position 0.
entry = tk.Entry(root)
new_text = "Example text"
entry.delete(0, tk.END)
entry.insert(0, new_text)
Method B: Use StringVar
You have to create a new StringVar object called entry_text in the example. Also, your Entry widget has to be created with keyword argument textvariable. Afterwards, every time you change entry_text with set, the text will automatically show up in the Entry widget.
entry_text = tk.StringVar()
entry = tk.Entry(root, textvariable=entry_text)
new_text = "Example text"
entry_text.set(new_text)
Complete working example which contains both methods to set the text via Button:
This window
is generated by the following complete working example:
import tkinter as tk
def button_1_click():
# define new text (you can modify this to your needs!)
new_text = "Button 1 clicked!"
# delete content from position 0 to end
entry.delete(0, tk.END)
# insert new_text at position 0
entry.insert(0, new_text)
def button_2_click():
# define new text (you can modify this to your needs!)
new_text = "Button 2 clicked!"
# set connected text variable to new_text
entry_text.set(new_text)
root = tk.Tk()
entry_text = tk.StringVar()
entry = tk.Entry(root, textvariable=entry_text)
button_1 = tk.Button(root, text="Button 1", command=button_1_click)
button_2 = tk.Button(root, text="Button 2", command=button_2_click)
entry.pack(side=tk.TOP)
button_1.pack(side=tk.LEFT)
button_2.pack(side=tk.LEFT)
root.mainloop()
Your problem is that when you do this:
a = Button(win, text="plant", command=setText("plant"))
it tries to evaluate what to set for the command. So when instantiating the Button object, it actually calls setText("plant"). This is wrong, because you don't want to call the setText method yet. Then it takes the return value of this call (which is None), and sets that to the command of the button. That's why clicking the button does nothing, because there is no command set for it.
If you do as Milan Skála suggested and use a lambda expression instead, then your code will work (assuming you fix the indentation and the parentheses).
Instead of command=setText("plant"), which actually calls the function, you can set command=lambda:setText("plant") which specifies something which will call the function later, when you want to call it.
If you don't like lambdas, another (slightly more cumbersome) way would be to define a pair of functions to do what you want:
def set_to_plant():
set_text("plant")
def set_to_animal():
set_text("animal")
and then you can use command=set_to_plant and command=set_to_animal - these will evaluate to the corresponding functions, but are definitely not the same as command=set_to_plant() which would of course evaluate to None again.
One way would be to inherit a new class,EntryWithSet, and defining set method that makes use of delete and insert methods of the Entry class objects:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
class EntryWithSet(tk.Entry):
"""
A subclass to Entry that has a set method for setting its text to
a given string, much like a Variable class.
"""
def __init__(self, master, *args, **kwargs):
tk.Entry.__init__(self, master, *args, **kwargs)
def set(self, text_string):
"""
Sets the object's text to text_string.
"""
self.delete('0', 'end')
self.insert('0', text_string)
def on_button_click():
import random, string
rand_str = ''.join(random.choice(string.ascii_letters) for _ in range(19))
entry.set(rand_str)
if __name__ == '__main__':
root = tk.Tk()
entry = EntryWithSet(root)
entry.pack()
tk.Button(root, text="Set", command=on_button_click).pack()
tk.mainloop()
e= StringVar()
def fileDialog():
filename = filedialog.askopenfilename(initialdir = "/",title = "Select A
File",filetype = (("jpeg","*.jpg"),("png","*.png"),("All Files","*.*")))
e.set(filename)
la = Entry(self,textvariable = e,width = 30).place(x=230,y=330)
butt=Button(self,text="Browse",width=7,command=fileDialog).place(x=430,y=328)