Delaying parts of text being inserted into Scroll Text Box (scrolledtext) - python

I have a button which inserts text into a 'scrolltext' box when it is clicked.
I want to delay parts of the text being inserted into the text box. As in, one line of text is insert, there a 3 second delay, the next line of text is inserted and so on...
The I attempted using 'time' to make this work. However, this just delays all the text being inserted by the combined value and then all the text is inserted at once. Is there a way to make it work how I wish? And is it possible to delay it, so that each letter is inserted one at a time?
This is a much simplified version of what I've tried:
import tkinter as tk
from tkinter import *
from tkinter import scrolledtext
import time
# This is the GUI
trialGUI = Tk()
trialGUI.geometry('710x320')
trialGUI.title("Test GUI")
#This is the text that should be inserted when the button is pressed
def insertText():
trialBox.insert(tk.INSERT, 'This line should be inserted first.\n')
time.sleep(1)
trialBox.insert(tk.INSERT, 'This line should be inserted after a 1 second delay.\n')
time.sleep(3)
trialBox.insert(tk.INSERT, 'This line should be inserted after a 3 second delay.\n')
time.sleep(3)
trialBox.insert(tk.INSERT, 'This line should be inserted after a 3 second delay.\n')
#This is the scrolling text box
trialBox = scrolledtext.ScrolledText(trialGUI, wrap = tk.WORD, width = 42, height = 10, font=(14))
trialBox.grid(row = 0, column = 0, columnspan = 4, pady = 3)
#This button runs the code to insert the text
trialButton = Button(trialGUI, text = "Run Code", command = insertText)
trialButton.grid(row = 1)
trialGUI.mainloop()

Here's a solution using the .after() method:
def insertText():
global previousDelay
previousDelay = 0
delayedInsert('This line should be inserted first.\n',0)
delayedInsert('This line should be inserted after a 1 second delay.\n',1)
delayedInsert('This line should be inserted after a 3 second delay.\n',3)
delayedInsert('This line should be inserted after a 3 second delay.\n',3)
def delayedInsert(text, delay):
global previousDelay
trialGUI.after((delay + previousDelay) * 1000, lambda: trialBox.insert(tk.INSERT,text))
previousDelay += delay
It uses a delayedInsert function which takes the text and delay in seconds and the global variable previousDelay to make the delays appear asynchronous (they are still happening at the same time but the delays are changed to make it appear like they are not). If the delays were not changed, each delay would start at the same time, not one after the other. The delayedInsert function waits for the delay specified plus the previous delay before inserting the text. This gives the same effect as time.sleep() but it works with Tkinter.

Related

Why are multiple functions being called for this tkinter event binding? (solved)

The code snippet below creates two listboxes. I've written the code so that:
An item selected from the 'Available' list is transferred to the 'Selected' list upon clicking the item (controlled via the OnASelect method)
The index of the item that is transferred is conserved in the 'Available' list (it is replaced with an empty string)
An item selected from the 'Selected' list is transferred BACK to the 'Available' list upon clicking the item
Because the index of the 'Available' list is conserved, items are always transferred back in the original order regardless of what order they are clicked.
Here's my issue:
Despite the widget working as intended, I'm still getting an unnecessary function call to OnASelect upon transferring items BACK to the 'Available' list, which causes an Index Error. This widget is intended for a larger project I'm working on and I want to avoid this causing problems down the line.
For troubleshooting purposes, I've written my code so that when a transfer occurs, a print statement is sent out displaying either 'The A>S function was activated' or 'The S>A function was activated'.
Output upon transferring an item from 'Available' to 'Selected':
The A>S function was activated
Output upon transferring an item from 'Selected' to 'Available':
The S>A function was activated
The A>S function was activated
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\ProgramData\Anaconda3\lib\tkinter\__init__.py", line 1883, in __call__
return self.func(*args)
File "x:/Mouselight Data Management/GUI_Branch/GUI_Menu_Related/Test/test2.py", line 29, in OnASelect
AselectionIndex = int(event.widget.curselection()[0])
IndexError: tuple index out of range
PS X:\Mouselight Data Management\GUI_Branch> The A>S function was activate
I've wasted way too much time trying to figure out why it's doing this and I'm stumped. Any help with this issue would be much appreciated!
Ps. I know that adding an 'if event.widget.curselection():' at the start of the OnASelect method bypasses the error, but I want to prevent the multiple function call to begin with.
The full code:
from tkinter import *
class DependentLists(Tk):
def __init__(self):
Tk.__init__(self)
self.mainframe = Frame(self)
self.mainframe.place(relx=0.5, rely=0.25, anchor=CENTER)
completelabel = Label(self.mainframe, text = "Available:")
completelabel.grid(row=2, column = 0)
selectedlabel = Label(self.mainframe, text = "Selected:")
selectedlabel.grid(row=2, column = 1)
self.Available = Listbox(self.mainframe)
self.Available.grid(row=3, column = 0)
self.AList = []
for i in range(6):
self.Available.insert(END,i)
self.AList.append(i)
self.Available.bind('<<ListboxSelect>>', self.OnASelect)
self.Selected = Listbox(self.mainframe)
self.Selected.grid(row=3, column = 1)
self.Selected.bind('<<ListboxSelect>>', self.OnSSelect)
def OnASelect(self, event):
print('The A>S function was activated')
AselectionIndex = int(event.widget.curselection()[0])
Aselection = event.widget.get(AselectionIndex)
self.Available.delete(AselectionIndex)
self.Available.insert(AselectionIndex,'')
self.Selected.insert(END, Aselection)
def OnSSelect(self, event):
print('The S>A function was activated')
SselectionIndex = int(event.widget.curselection()[0])
Sselection = event.widget.get(SselectionIndex)
self.Selected.delete(ANCHOR)
self.Available.delete(self.AList.index(Sselection))
self.Available.insert(self.AList.index(Sselection), Sselection)
App = DependentLists()
screen_width = App.winfo_screenwidth()
screen_height = App.winfo_screenheight()
window_height = screen_height - 800
window_width = screen_width - 1400
x_cordinate = int((screen_width/2) - (window_width/2))
y_cordinate = int((screen_height/2) - (window_height/2))
App.geometry("{}x{}+{}+{}".format(window_width, window_height, x_cordinate, y_cordinate))
App.mainloop()
Edit: Solution is in BryanOakley's answer (thank you!) and the subsequent comments under that answer.
<<ListboxSelect>> is triggered whenever the selection changes. This can happen when the user clicks on an item, but it can also happen at other times, such as a user using the keyboard to traverse the listbox, or your code deleting something that was selected.
If your UI should only work on a mouse click, you should be binding to that rather than <<ListboxSelect>>.

Focusing on next entry box after typing a number in current entry box

I'm trying to see if there's a way to type a number between 1 and 4 into an entry box, then go to the next entry box (with the number entered into the box; the code below skips to the next entry without entering anything)
I'm creating a program that will take item-level data entry to be computed into different subscales. I have that part working in different code, but would prefer not to have to hit tab in between each text entry box since there will be a lot of them.
Basic code:
from tkinter import *
master = Tk()
root_menu = Menu(master)
master.config(menu = root_menu)
def nextentrybox(event):
event.widget.tk_focusNext().focus()
return('break')
Label(master, text='Q1',font=("Arial",8)).grid(row=0,column=0,sticky=E)
Q1=Entry(master, textvariable=StringVar)
Q1.grid(row=0,column=1)
Q1.bind('1',nextentrybox)
Q1.bind('2',nextentrybox)
Q1.bind('3',nextentrybox)
Q1.bind('4',nextentrybox)
Label(master, text='Q2',font=("Arial",8)).grid(row=1,column=0,sticky=E)
Q2=Entry(master, textvariable=StringVar)
Q2.grid(row=1,column=1)
Q2.bind('1',nextentrybox)
Q2.bind('2',nextentrybox)
Q2.bind('3',nextentrybox)
Q2.bind('4',nextentrybox)
### etc for rest of questions
### Scale sums, t-score lookups, and report generator to go here
file_menu = Menu(root_menu)
root_menu.add_cascade(label = "File", menu = file_menu)
file_menu.add_separator()
file_menu.add_command(label = "Quit", command = master.destroy)
mainloop()
Thanks for any help or pointers!
The simplest solution is to enter the event keysym before proceeding to the next field.
In the following example, notice how I added a call to event.widget.insert before moving the focus:
def nextentrybox(event):
event.widget.insert("end", event.keysym)
event.widget.tk_focusNext().focus()
return('break')

Tkinter: How to make characters appear on text widget with a time delay?

def write_text(widget, message, enter_number, slow_type=True):
widget.config(state="normal")
if slow_type:
if len(message) > 0:
widget.insert("insert", message[0])
if len(message) > 1:
widget.after(100, UI.write_text, widget, message[1:], 0)
else:
widget.insert("insert", message)
for i in range(enter_number):
widget.insert("insert", "\n")
widget.config(state="disabled")
widget.see("end")
This is my code to write each characters show up in time delay
But I have a problem:
If I call this method like this (I have a widget named text1).
write_text(text1, ">>>Invalid Input", 1)
write_text(text1, ">>>Try Again...", 2)
Messages blend together something like this >>>>>>ITnrvya lAigda iInn.p.u.t.
I want it to type messages when typing the previous message is over.
What can I do?
P.S. Sorry for my bad English...
Here's something runnable that does what I think you want. The "blended" output you're getting was because the second call you have to write_text() occurs before all the (delayed) processing of the first one has completed, so the text widget effectively gets updates by two separate callback processes.
The code below avoids this issue by putting the characters of the string (and what line they go on) in a Queue which allow them to be retrieved in the same order that they were added.
DELAY = 100 # ms between widget updates
def update_widget(widget):
try:
line_number, text = widget._text_queue.get_nowait()
except queue.Empty:
return # Nothing further to do.
widget.insert('%s.end' % line_number, text)
if widget._text_queue.qsize(): # Anything more to process?
widget.after(DELAY, update_widget, widget)
def write_text(widget, message, line_number, slow_type=True):
if not slow_type:
widget.insert('%s.0' % line_number, message)
else:
for ch in message: # Add each character of message to queue.
widget._text_queue.put((line_number, ch))
update_widget(widget) # Start (or continue) update processing.
def add_text(widget):
widget.delete('1.0', tk.END) # Delete widget's current contents.
# Note: Tkinter always adds a newline at the very end of text widgets, so
# we need one less newline. This makes it possible to insert text onto
# any possible line of the widget -- which could fail if it was empty.
widget.insert('1.0', (widget['height']-1) * '\n') # Init with blank lines.
write_text(widget, ">>>Invalid Input", 1)
write_text(widget, ">>>Try Again...", 2)
if __name__ == '__main__':
root = tk.Tk()
text1 = tk.Text(root, width=40, height=3, bg='skyblue')
text1._text_queue = queue.Queue() # Add a Queue to it for delayed updates.
text1.grid()
button = tk.Button(root, text='Run Test', command=lambda w=text1: add_text(w))
button.grid()
root.mainloop()

raw_input stops GUI from appearing

I have written a program in Python that allow me to change the names of many files all at once. I have one issue that is quite odd.
When I use raw_input to get my desired extension, the GUI will not launch. I don't get any errors, but the window will never appear.
I tried using raw_input as a way of getting a file extension from the user to build the file list. This program will works correctly when raw_input is not used.The section of code that I am referring to is in my globList function. For some reason when raw_imput is used the window will not launch.
import os
import Tkinter
import glob
from Tkinter import *
def changeNames(dynamic_entry_list, filelist):
for index in range(len(dynamic_entry_list)):
if(dynamic_entry_list[index].get() != filelist[index]):
os.rename(filelist[index], dynamic_entry_list[index].get())
print "The files have been updated!"
def drawWindow(filelist):
dynamic_entry_list = []
my_row = 0
my_column = 0
for name in filelist:
my_column = 0
label = Tkinter.Label(window, text = name, justify = RIGHT)
label.grid(row = my_row, column = my_column)
my_column = 1
entry = Entry(window, width = 50)
dynamic_entry_list.append(entry)
entry.insert(0, name)
entry.grid(row = my_row, column = my_column)
my_row += 1
return dynamic_entry_list
def globList(filelist):
#ext = raw_input("Enter the file extension:")
ext = ""
desired = '*' + ext
for name in glob.glob(desired):
filelist.append(name)
filelist = []
globList(filelist)
window = Tkinter.Tk()
user_input = drawWindow(filelist)
button = Button(window, text = "Change File Names", command = (lambda e=user_input: changeNames(e, filelist)))
button.grid(row = len(filelist) + 1 , column = 1)
window.mainloop()
Is this a problem with raw_input?
What would be a good solution to the problem?
This is how tkinter is defined to work. It is single threaded, so while it's waiting for user input it's truly waiting. mainloop must be running so that the GUI can respond to events, including internal events such as requests to draw the window on the screen.
Generally speaking, you shouldn't be mixing a GUI with reading input from stdin. If you're creating a GUI, get the input from the user via an entry widget. Or, get the user input before creating the GUI.
A decent tutorial on popup dialogs can be found on the effbot site: http://effbot.org/tkinterbook/tkinter-dialog-windows.htm

file.open with "w" not overwriting file in Python tKinter button method

So I'm writing a tKinter GUI for this project I'm working on, and I've run into a problem with one of my button methods. In the method for this button, the code prints a list of coordinates to a text file. It works great the first time, but if I press the button again before closing the root tKinter window, it doesn't truncate the file - it just adds the next set off coordinates to the end. Here is my code:
#print to file
reportFile = open('gridCenters.txt','w')
reportFile.write('In movement order:\n')
for x in xrange(0,len(coordinates)):
reportFile.write('%s\n' % str(coordinates[x]))
reportFile.close()
Now, this is within a button method, so to my understanding it should execute every time the button is pressed. The really strange part is that in the output after pressing the button again, it prints JUST the loop values. For some reason it skips over the "In movement order" part.
It won't let me upload images but here's an idea of how it looks:
In movement order:
(0,1)
(0,2.5)
(0.3.5)
(0,4.5)
Then if I press the button again before closing the root window:
In movement order:
(0,1)
(0,2.5)
(0.3.5)
(0,4.5)
(0,1)
(0,2.5)
(0.3.5)
(0,4.5)
(Those blocks aren't code, just text output)
I'm just really confused. My understanding is that every time I press the button, it should overwrite the file, then close it.
Thanks for the help.
In when your button re-opens the file it doesn't print the "In movement order:" a second time.
This looks like you aren't clearing your variable coordinates. You should make sure that you are starting with a clean variable before adding to it to get the data you are looking for.
You could reset it after the file closes unless you need to retain it for use un the GUI at that point.
I am not shure why it doesnt works for you but here is what i had wrote.
from Tkinter import *
def wtf(coordinates):
reportFile = open('gridCenters.txt','w')
reportFile.write('In movement order:\n')
for x in xrange(0,len(coordinates)):
reportFile.write('%s\n' % str(coordinates[x]))
reportFile.close()
def main():
coordinates = [(0,1),(0,2.5),(0,3.5),(0,4.5)]
root = Tk()
btn = Button(root,text='click me',command = lambda:wtf(coordinates))
btn.pack()
root.mainloop()
main()
in wtf function if 'w' is flag (reportFile = open('gridCenters.txt','w')) the gridCenters.txt is rewritten every time,but if the flag is 'a' instead of 'w' than result is just appending one below another.I hope this is what u want.
from Tkinter import *
coords = [1, 2, 3, 4, 5]
def write():
global coords
fileName = "testButton.txt"
fileObj = open(fileName, 'w')
fileObj.write("Some words\n")
for i in xrange(0, len(coords)):
fileObj.write("%d\n" %coords[i])
fileObj.close()
for i in range(5):
coords[i] += 1
root = Tk()
f = Frame(root).pack()
b = Button(root, text = "OK", command = write).pack(side = LEFT)
root.mainloop()
This works for me, overwriting the file every time and the values are updated every time as well. Something must be going on elsewhere in your program.

Categories

Resources