I'm using tkinter to animate a gif using guidance from the following pages:
Tkinter animation will not work
However I cannot get the suggestions to work. Right now I have something like this
import tkinter as tk
root = tk.Tk()
frames = [tk.PhotoImage(file = '/home/quantik/Pictures/dice.gif',format="gif -index %i" %(i)) for i in range(5)]
def animate(n):
if (n < len(frames)):
label.configure(image=frames[n])
n+=1
root.after(300, animate(n))
label = tk.Label(root)
label.pack()
root.after(0, animate(0))
root.mainloop()
However it just displays the last image in frames I've tried several methods but I keep getting the same result. Does anyone have any suggestions as to why this may be happening?
This code:
root.after(300, animate(n))
is exactly the same as this code:
result = animate(n)
root.after(300, result)
Notice what is happening? You don't want to call the function, you want to tell after to call it later. You do that like this:
root.after(300, animate, n)
That tells root.after to call the animate function after 300 milliseconds, and to pass the value of n as an argument to the function.
Related
New to ktinker and I've met a problem that I can't find a solution to.
My goal is to animate a shape and allow it to move using a function inside a while loop, and the function generates the shape while the while loop deletes and refreshes the canvas.
My code look something like this:
def shape():
global a
a = screen.create_rectangle(x,100,x+50,200,fill = 'white')
while True:
shape(x,y)
x+=10
screen.update()
screen.delete(a)
time.sleep(0.03)
the code successfully creates a rectangle and it moves, but the code isn't deleting the rectangles. However, the code works fine and deletes the rectangles if I'm not using a function.
The proper way to animate objects on a tk.Canvas is different from pygame and other GUI frameworks.
tk.Canvas does not require a "blitting" process; the items do not need to be deleted, and recreated each frame.
The proper approach is to move existing items, either using the tk.Canvas.move method, or the tk.Canvas.coord method. The first moves the item by a provided δx and δy, whereas the second re-positions the item to the new coordinates passed to it.
Here is an example with tk.Canvas.move:
import tkinter as tk
def shape(x):
return screen.create_rectangle(x, 100, x+50 , 200, fill='white')
def animate(rect, dx=0, dy=0):
screen.move(rect, dx, dy)
screen.after(100, animate, rect, dx)
if __name__ == '__main__':
root = tk.Tk()
screen = tk.Canvas(root, width=400, height=400, bg='cyan')
screen.pack()
rect = shape(50)
animate(rect, dx=10)
root.mainloop()
Notice that we make use of the tk.mainloop provided by the framework, instead of a clumsy while True loop. The tk.after method is the correct approach to call a function (here animate) at regular intervals.
We also avoid the use of time.sleep which always results to problems and blocking the GUI.
Try updating the screen after you delete the shape.
def shape():
global a
a = screen.create_rectangle(x,100,x+50,200,fill = 'white')
while True:
shape(x,y)
x+=10
screen.update()
screen.delete(a)
screen.update()
time.sleep(0.03)
I'm trying to place widgets using the .place() method. I understand that x and y are measured from the top left side of the page, but I'm trying to change it, so it can measure it from the top right.
My reason being, I would like the label to always be a constant distance from the right side of the page. I've tried relx and rely but unfortunately it didn't work for me, and for some reason neither is the anchor.
Can anyone help with this?
Here's my code:
from tkinter import *
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.geometry('500x500')
root.configure(bg='blue')
label = Label(root,text='Hello',anchor='nw')
label.place(x=50,y=50)
root.mainloop()
Try giving x and y co-ordinates to widgets, something like this,
import tkinter as tk
root=tk.Tk()
root.geometry('300x300')
b1=tk.Button(root,text="b1",width=12)
b1.place(relx = 1, x =-2, y = 2, anchor = 'ne')
b2=tk.Button(root,text="b2",width=12)
b2.place(relx = 1, x =-2, y = 30, anchor = 'ne')
root.mainloop()
hope this helps you!
I'm trying to create a tkinter gui, which starts with a multivariate time series and uses a tkinter gui to allow the user to select and plot certain channel readings in a sliding window created using plotly express. I'm just starting to learn about tkinter and am using Python 3.7 with IDLE on a Mac OS 10.15.4.
In my example below, my time series has seven labelled channels with 10000 time recordings each. The channel labels are 'a','b','c','d','e','f','g' and the series is stored in a 1000-by-7 array, X. Because I won't always know ahead of time the number of channels and their labels in future implementations of this script, I used a loop to create the labelled checkboxes here. The problem occurs when I try to create the data frame:
import numpy as np
import plotly.express as px
from tkinter import Tk,Button, Label, Checkbutton,BooleanVar
import pandas as pd
window = Tk()
window.title('My Window')
window.geometry('500x500')
np.random.seed(123)
X = np.random.randn(10000,7)
channels=['a','b','c','d','e','f','g']
num_channels=len(channels)
'''Checkbuttons for channels, appearing in one row. Each initially set true'''
channel_vars=[]
channel_buttons=[]
for i in range(num_channels):
channel_vars.append(BooleanVar())
channel_vars[i].set(True)
channel_buttons.append(Checkbutton(window,text=channels[i],var=channel_vars[i]))
channel_buttons[i].grid(row=0,column=i)
'''Determine selected indices'''
def _selected_indices():
indices=[i for i in range(num_channels) if channel_vars[i].get()]
print(indices)
selected_indices_btn = Button(window, text="Select",command=_selected_indices)
selected_indices_btn.grid(row=1, column=0)
''' Create data frame using only the selected channels'''
df=pd.DataFrame(X[:,indices],columns=channels[indices])
df['x']=df.index
'''Plot selected channels'''
def _plot_selected():
df_melt = pd.melt(df, id_vars="x", value_vars=df.columns[:-1])
fig=px.line(df_melt, x="x", y="value",color="variable",labels = {'x':'time
(sec)','variable':'channel'})
fig.update_layout(xaxis=dict(rangeslider=dict(visible=True),type="linear"))
fig.show()
plot_button = Button(master=window, text="Plot", command=_plot_selected)
plot_button.grid(row=2, column=0)
'''Quit button'''
def _quit():
window.quit()
window.destroy()
quit_button = Button(master=window, text="Quit", command=_quit)
quit_button.grid(row=3, column=0)
window.mainloop()
The error message tells me that "indices" is unknown when I try to construct my data frame, df, a result that shows I'm missing something basic in terms of callbacks and/or organizing my gui window.
Not to do with tkinter, it's just that you are assigning the indices values inside the _selected_indices function.
'''Determine selected indices'''
def _selected_indices():
indices=[i for i in range(num_channels) if channel_vars[i].get()]
print(indices)
You are then trying to use your indices outside the scope of the function.
''' Create data frame using only the selected channels'''
df=pd.DataFrame(X[:,indices],columns=channels[indices])
The bigger problem here is that returning a value from a function to use somewhere else in the program from a tkinter callback isnt ideal.
There is a question here with answers that will help better than I could: Python - returning from a Tkinter callback
Hope this helps answer your question.
I am still a beginner and I have yet to understand how to write a function, that works.
import tkinter
root = tkinter.Tk()
root.title("Shuffle Colors")
# The list of colors
colors = ['red','yellow', 'pink', 'purple','green', 'blue', 'orange','Brown']
#looping each color
for c in colors:
#Buttons
button1 = tkinter.Button(root, text=c, bg=c)
button1.pack(fill = tkinter.X)
root.mainloop()
shuffle works in-place so:
import random
def my_shuffle(colors):
random.shuffle(colors)
return colors
new_colors = my_shuffle(colors)
but you dont really need a function - just do the shuffle as you need it - unless you need the original list to be unmodified
I have an issue in that I can compact this code (I've been told I can) but did not receive any help in doing so and don't have a clue how to do it.
Tried putting this into a for loop, but I want a 3x3 grid of buttons with the middle one being a listbox instead, dead centre of the buttons.
I have looked around, and after an hour, I got no answer.
Here I tried appending each button to a list and packing them in a for loop, but is it possible to do them each in a for loop and the Listbox done and packed separately after?
class MatchGame(Toplevel):
def __init__(self,master):
self.fr=Toplevel(master)
self.GameFrame=Frame(self.fr)
self.AllButtons=[]
self.AllButtons.append(Button(self.GameFrame,bg="red",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="green",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="dark blue",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="turquoise",height=5,width=15,text=""))
self.AllButtons.append(Listbox(self.GameFrame,bg="grey",height=5,width=15))
self.AllButtons.append(Button(self.GameFrame,bg="yellow",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="pink",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="orange",height=5,width=15,text=""))
self.AllButtons.append(Button(self.GameFrame,bg="purple",height=5,width=15,text=""))
for x in range(0,len(self.AllButtons)):
AllButtons[x].grid(row=int(round(((x+1)/3)+0.5)),column=x%3)
self.GameFrame.grid(row=0,column=0)
Quit=Button(self.fr, text="Destroy This Game", bg="orange",command=self.fr.destroy)
Quit.grid(row=1,column=0)
It needs to have individual colours, same size and all that, but I don't know what to do. I'm fairly new to classes, and I can't work out for the life of me how to make this window with compact code (not 9 lines for each object, then packing them all.)
If you want to dynamically create a 3x3 grid of buttons. Then a nested for-loop seems to be your best option.
Example:
import tkinter as tk
root = tk.Tk()
# List of your colours
COLOURS = [['red', 'green', 'dark blue'],
['turquoise', 'grey', 'yellow'],
['pink', 'orange', 'purple']]
# Nested for-loop for a 3x3 grid
for x in range(3):
for y in range(3):
if x == 1 and y == 1: # If statement for the Listbox
tk.Listbox(root, bg = COLOURS[x][y], height=5, width=15).grid(row = x, column = y)
else:
tk.Button(root, bg = COLOURS[x][y], height=5, width=15).grid(row = x, column = y)
root.mainloop()