Text Animations with Tkinter - python

I'm designing a GUI to decrease the time which my colleagues spend for reporting tests. But I stuck in the animation of opening screen. I mixed the code that I found on the internet which writes the chosen text letter one by one with a second for loop to enlarge the text but the first loop worked only for the last word of the list. Also, I tried the same code with a while loop both with giving count or just writing "True" but they didn't solve my problem either.
I want to see the chosen texts (now, only a few words but later on I will write my tests) written on the screen one by one and letter by letter. How can I solve this?
My sketch code is as follows:
import tkinter as tk
import random
root = tk.Tk()
root.configure(bg="white")
Words=["Canvas", "Import", "Index", "Random", "Tkinter"]
canvas=tk.Canvas(root, width=400, height=375, bg="white")
canvas.pack()
for word in Words:
x=random.randint(0, 250)
y=random.randint(0, 225)
canvas_text=canvas.create_text(x, y, text=word)
delta=500
delay=0
for i in range(len(word)+1):
s=word[:i]
update_text=lambda s=s: canvas.itemconfigure(canvas_text, text=s)
canvas.after(delay, update_text)
delay+=delta
x=random.randint(0, 250)
y=random.randint(0, 225)
root.mainloop()

There are several issues in your code:
With canvas_text = canvas.create_text(x, y, text=word) you immediately write the full word, it would make more sense to start with an empty string, canvas_text = canvas.create_text(x, y, text=""), then add the letters one by one. Additionally, you can add the option anchor="w" so that adding the letters will not move the word (otherwise the default anchor is the center of the object).
delay is reset to 0 for each word so the letters will appear one by one but at the same time for all words.
update_text is defined as lambda s=s: canvas.itemconfigure(canvas_text, text=s) and therefore canvas_text refers to the last created canvas object (see What do (lambda) function closures capture?). As a consquence, only the last word is animated. To avoid this, you can use an additional argument in the function: lambda s=s, c=canvas_text: canvas.itemconfigure(c, text=s)
A few additional remarks:
delta=500 can be moved out of the for loop
The x = ... and y = ... in the nested for loop are useless and can be removed
update_text=lambda s=s: canvas.itemconfigure(canvas_text, text=s): lambda is typically used to avoid having to give the function a name, so one typically either defines a named function with def or uses a lambda directly, e.g.
canvas.after(delay, lambda c=canvas_text, s=word[:i]: canvas.itemconfigure(c, text=s))
Here is the full code:
import tkinter as tk
import random
root = tk.Tk()
root.configure(bg="white")
Words = ["Canvas", "Import", "Index", "Random", "Tkinter"]
canvas = tk.Canvas(root, width=400, height=375, bg="white")
canvas.pack()
delay = 0
delta = 500
for word in Words:
x = random.randint(0, 250)
y = random.randint(0, 225)
canvas_text = canvas.create_text(x, y, text="", anchor="w")
for i in range(len(word)+1):
canvas.after(delay, lambda c=canvas_text, s=word[:i]: canvas.itemconfigure(c, text=s))
delay += delta
root.mainloop()

Related

Placing Images in loop in Tkinter

I'm trying to place a set of images in a horizontal row in Tkinter. I do this by looping over a list, and loading the corresponding images from the working directory.
I place the images according to the index multiplied with a certain amount of spacing. However, when i actually place the images, they all get placed on top of each other in the last spot, instead of spaced out. The count value works fine, and when i print(spacing*(count+1)) it outputs the correct values but when placing they all get bunched up in the last place.
Does this have something to do with the Label() class?
for count, mood in enumerate(mood_options):
mood_img = Image.open(f"img/{mood}.png")
mood_img_copy = mood_img.resize(img_size, Image.ANTIALIAS)
mood_img_resized = ImageTk.PhotoImage(mood_img_copy)
mood_img_label = Label(root, image=mood_img_resized).place(relx=spacing*(count+1), rely=0.35)
print(spacing * (count + 1))
EDIT: I have used this exact method for placing buttons, see below:
for count, mood in enumerate(mood_options):
mood_btn = Button(root, text=mood.capitalize(), command=lambda mood=mood: register_mood(mood), width=7) \
.place(relx=(count + 1) * spacing, rely=0.5)
This works flawlessly, which makes me wonder why it wouldn't work with images, instead of buttons.
If the same logic works for buttons, then the problem might be the image is garbage collected, in this case you cannot store the image as an attribute of some class to hold reference, because over each iteration each image will be overwritten, holding refence to the last image only. Same happens with global as well. So the way to go here would be appending to a list. Try something like:
lst = []
for count, mood in enumerate(mood_options):
mood_img = Image.open(f"img/{mood}.png")
mood_img_copy = mood_img.resize(img_size, Image.ANTIALIAS)
mood_img_resized = ImageTk.PhotoImage(mood_img_copy)
lst.append(mood_img_resized)
Label(root, image=mood_img_resized).place(relx=spacing*(count+1), rely=0.35)
print(spacing * (count + 1))
Though it would be much better to place stuff using grid to place it in a grid like manner. If your worried about responsiveness of the window then look at weight:
from tkinter import *
root = Tk()
mood_options = ['a', 'b', 'c']
for count, mood in enumerate(mood_options):
Label(root, text=mood).grid(row=0,column=count,padx=50)
root.grid_columnconfigure(count,weight=1)
root.grid_rowconfigure(0,weight=1)
root.mainloop()
Is your spacing the actual amount of space in pixels?
If so, leave away the relx and only use x. relx has to be a float between 0.0 and 1.0.

Recreate left-to-right scrolling text in tkinter

I want to create a fast typing effect through packing a label in tkinter. When I run this code, it prints left to right as I want it to, but the letters are spaced far apart and the spaces print {} brackets instead.
How can I remove the brackets and just show a space? Is there also a cleaner and easier way to do the scrolling effect other than the list method I used?
root = Tk()
delay = 50
label_var = StringVar()
label = Label(root, textvariable=label_var, height=10)
num = 0
def scroll():
global num
roll_text = list(message) # Edit: deleted this line
num = num + 1
label_var.set(roll_text[1:num]) # Edit: changed roll_text to message
root.after(delay, scroll)
message = ' This message should be scrolling left to right. '
scroll()
label.pack()
root.mainloop()
The brackets are appearing because you're converting the string to a list. When tkinter is given a list where it expects a string it uses Tcl's rules for converting the list back to a string. Those rules include using curly braces to preserve the original data.
The solution is simple: don't pass a list to label_var.set.
As for the alignment, because you don't provide any alignment options, tkinter will try to center the widget. A simple solution for this specific case is to pass side='left' to the pack command.

Why am I getting brackets in between text when trying to print some text in the Tkinter canvas?

I am trying to print some text on the Tkinter canvas along with an image which is doing fine. But unfortunately, some curly braces are also being printed on the screen without using them anywhere in the print statement. I am fetching some part of the text from a dataframe and storing it in a variable before printing it on the screen.
My code is as follows:
best_batsmen = dataset.loc[dataset.loc[dataset['Innings']>=15,'Average'].idxmax(),'Names']
message = ("The best Batsman of the Tournament could possibly be: ",best_batsmen)
canvas_width = 500
canvas_height = 500
root = Toplevel()
root.geometry("700x600")
root.title("New Window")
canvas = Canvas(root, width=canvas_width, height=canvas_height)
canvas.create_text(1, 10, anchor=W, text=message)
img = ImageTk.PhotoImage(Image.open("virat.jpeg"))
canvas.create_image(0, 20, anchor=NW, image=img)
canvas.image = img
canvas.pack()
root.mainloop()
When I run the above code it's printing {The best Batsmen of the Tournament could possibly be:} {Virat Kohli} instead of The best Batsmen of the Tournament could possible be: Virat Kohli. Those curly braces are looking quite odd. Can anyone please help me to solve this error?
It's either in a set or a dictionary in your dataset. Just convert it to a string before displaying:
string = ''.join(str(l) for l in list(name))
This will do the trick for any amount of elements in a set.
This code sets message to a tuple:
message = ("The best Batsman of the Tournament could possibly be: ",best_batsmen)
This uses the tuple as the value of the text attribute without converting it to a string:
canvas.create_text(1, 10, anchor=W, text=message)
This value gets passed down to the underlying tcl interepreter as a list (from Tcl's perspective). When tcl converts a list to a string, which it must do before adding it to the canvas, it adds curly braces in order to retain it's list-like properties.
The solution is simple: don't pass lists or tuples to tkinter functions. Explicitly convert them to a string first:
message = " ".join(message)
canvas.create_text(1, 10, anchor=W, text=message)

Tkinter Canvas items stored cleared and recalled

I am a beginner messing around in python 3.6 with tkinter canvas and have built a function that generates a solar system at random using elipses and the random method.
I would like to be able to save these items and all their attributes like their tags and bindings so that I can clear the canvas and draw a new random system. I would then like to go back to the first generated system if I need to.
I do not want to save the canvas as an image as I am binding the items to functions. Any ideas on how to achieve this?
As far as I know, you only get the items "id" as a handle to that item, and while you can delete an item with a given ID, you can not recreate it just with that ID.
What you could do is to given all those elements a common tag and use tag_lower and tag_raise to hide and show the items below and above the "background" pane. The objects are still on the canvas, but can not be seen and do not react to e.g. mouse events.
import tkinter, random
root = tkinter.Tk()
canvas = tkinter.Canvas(root)
canvas.pack()
space = canvas.create_rectangle(0, 0, 200, 200, fill="#000000")
for _ in range(10):
x, y = random.randint(0, 200), random.randint(0, 200)
s = canvas.create_oval(x, y, x+10, y+10, fill="#ffff00", tags="star")
canvas.tag_bind(s, "<Button>", lambda e: print("twinkle"))
root.after(3000, lambda: canvas.tag_lower("star", space))
root.after(6000, lambda: canvas.tag_raise("star", space))
root.mainloop()
Update: As suggested by OP in comments, one can also set the items' state to hidden, being probably the clearer option and not needing some obscure (-ing) background item.
root.after(3000, lambda: canvas.itemconfig("star", state="hidden"))
root.after(6000, lambda: canvas.itemconfig("star", state="normal"))

Python tkinter create buttons with loop and edit their parameters

I'm making a program with Tkinter where I'd need to use a "for i in range" loop, in order to create 81 Text Widgets named like :
Text1
Text2
Text3
...
and dispose them in a square (actually to make a grill for a sudoku game of a 9*9 size). After I created these 81 Text Widgets I need to place them (using .place() ) and entering their position parameters.
After this, I will need to collect the values that the user entered in these Text Widgets.
I'm a newbie and I don't really know how to code this.
Here is my actual code but the problem is I can't modify the parameters once the dictionnary is created and i don't know how to access to the Text Widgets parameters. Maybe using a dictionnary is not the appropriate solution to do what I want.
d = {}
Ypos = 100
for i in range(9):
Xpos = 100
for j in range(9):
d["Text{0}".format(i)]= Text(window, height=1, width=1,relief = FLAT,font=("Calibri",16))
d["Text{0}".format(i)].place(x = Xpos+,y = Ypos)
Xpos += 35
yPos += 35
Thanks for helping
Don't use a complex key for the dictionary, it makes the code more complex without adding any benefit.
Since you're creating a grid, use row and column rather than i and j. This will make your code easier to understand. Also, don't use place if you're creating a grid. Tkinter has a geometry manager specifically for creating a grid.
Since you're creating a text widget with a height of one line, it makes more sense to use an entry widget.
Here's an example:
import Tkinter as tk
root = tk.Tk()
d = {}
window = tk.Frame(root, borderwidth=2, relief="groove")
window.pack(fill="both", expand=True)
for row in range(9):
for column in range(9):
entry = tk.Entry(window, width=1, relief="flat", font=("Calibri",16))
entry.grid(row=row, column=column, sticky="nsew")
d[(row,column)] = entry
root.mainloop()
Whenever you need to access the data in a cell, you can use the row and column easily:
value = d[(3,4)].get()
print("row 3, column 4 = %s" % value)

Categories

Resources