Animate the color of rectangle - python-3.x

I want to change the color of rectangle after a certain period of time.
I tried root.after() method but it is not working.
import time
from tkinter import *
def task():
canvas= Canvas()
canvas.create_rectangle(0,0,100,100,fill='red')
canvas.pack()
time.sleep(2)
canvas.create_rectangle(0,0,100,100,fill='blue')
canvas.pack()
time.sleep(2)
print("Testing...")
root = Tk()
canvas = Canvas(root)
while(1):
task()
root.mainloop()
Given code running continuously and root windows get hang for certain time. Rectangle color should change after 2 seconds of delay.

Using time.sleep() hangs the window as well as while loop. To use time.sleep
in tkinter we use after(ms) (ms-milliseconds) in the functions so the GUI won't hang.
While does the same, so we use after(ms, callback, args) function.
Here is an example of what you are trying to achieve. The rectangle will change his color every 1000ms (1 sec) from red to blue - blue to red so on. Also in your code you were creating a new canvas and rectangle every 4 secs. What I did is, I defined one canvas and one rectangle outside the task() function and gave the rectangle a tag (tag='rect') for the reference through which edit that one rectangle's color without creating unnecessary items. I hope this helped you.
Example:
from tkinter import *
root = Tk()
canvas = Canvas(root)
canvas.pack()
canvas.create_rectangle(0,0,100,100,fill='red', tag='rect')
def task():
l = root.after(1000, task)
if int(l.split('#')[1]) % 2 == 0:
canvas.itemconfig('rect', fill='blue')
else:
canvas.itemconfig('rect', fill='red')
task()
root.mainloop()

Related

Python tkinter overrideredirect - How to make my window movable again

After I run the code it makes my window unmovable, and I was wondering how would i make it interact with
MOUSE1
EDIT1: So this is what i came up with, but it doesnt work like it should, it just "teleports" the window around.
from tkinter import*
root = Tk()
root.overrideredirect(True)
#takes x, y coordinates of your mouse when u realese MOUSE1
def move_window(event):
global root
x_coord = event.x
y_coord = event.y
y_coord = str(event.y)
x_coord = str(event.x)
root.geometry('+'+x_coord+'+'+y_coord)
root.bind('<ButtonRelease-1>', move_window)
root.mainloop()
EDIT2: SOLVED! I realised the position of the mouse in the code above takes the position x,y INSIDE the tkinter window, not on the screen, I will use the module
pyautogui for that

Is there any command in turtle which stops turtle from further drawing?

I am creating a tkinter based program which used turtle graphics to draw graphs. I have a button which draws a square in the screen. But when I press that button while turtle is drawing something else, the turtle start to draw square and after finishing it again continue its previous task. I don't want it to continue it previous function. I want to directly stop it wherever it is and just do its new job.
This is the sample code but this is not actually what I need it for. I have tried to simplify the problem.
import tkinter as tk
from turtle import RawTurtle
def square():
t.up()
t.home()
t.down()
for i in range(4):
t.fd(100)
t.rt(90)
def drawseries():
l = 2
for i in range(100):
t.fd(l)
t.lt(90)
l += 2
root = tk.Tk()
button = tk.Button(root,command = square, text = 'Draw Square')
button.pack()
canvas = tk.Canvas(root, width = 500, height = 500)
canvas.pack()
t = RawTurtle(canvas)
drawseries()
You are always calling the drawseries() command at the end of the file. When the for loop is done, it calls drawseries() again.

Scheduling order of events with tkinter OOP

I am using tkinter in python 3.6 to display a blue square in the middle of a window. At each click the blue square should disappear and reappear after 2 seconds on a different random location. When running the following code, the blue square (referred as stimulus) does not disappear. Everything else seem to work properly.
This is the code:
import tkinter as TK
import random as RAN
class THR:
def __init__(self, root):
self.root = root
self.root.config(background='black')
self.screenYpixels = 600
self.screenXpixels = 1024
self.ITI = 2000
self.background = TK.Canvas(root, width=1024, height=600, bg='black',
bd=0, highlightthickness=0, relief='ridge')
self.background.pack()
self.newtrial()
def newtrial(self):
self.xpos = RAN.randrange(200, 1000)
self.ypos = RAN.randrange(100, 500)
self.stimulus = TK.Canvas(root,width=100,height=100,bg='blue', bd=0,
highlightthickness=0, relief='ridge')
self.stimulus.place(x=self.xpos, y=self.ypos, anchor="c")
self.stimulus.bind("<Button-1>", self.response)
self.exitbutton()
def response(self, event):
self.stimulus.place_forget()
self.intertrialinterval()
def intertrialinterval(self, *args):
self.root.after(self.ITI,self.newtrial())
def exitbutton(self):
self.exitButton = TK.Button(self.root, bg="green")
self.exitButton.place(relx=0.99, rely=0.01, anchor="c")
self.exitButton.bind("<Button-1>", self.exitprogram)
def exitprogram(self, root):
self.root.quit()
root = TK.Tk()
THR(root)
root.mainloop()
Here a list of things I tried but that did not work
Using time.sleep instead of root.after
Changing the way a newtrial() is called
Putting the sleep or after in different places
The last couple of hours searching the web for similar problems / solutions did not really help. I would like to fix this and at the same time understand what am I doing wrong.
As it turns out the list of links on the right of this webpage provided me with the solution just 3 minutes after posting the question.
This is the original thread Python Tkinter after event OOPS implementation
and here I write the solution:
dropping the parenthesis in the function called with the after method. So this self.root.after(self.ITI,self.newtrial()) is self.root.after(self.ITI,self.newtrial)
Unfortunately I still do not understand how that fixed the problem..

Performing ImageGrab on tkinter Screen

I am trying to save a tkinter screen to a file (to later convert into a video).
I can't get the correct position of the canvas for using ImageGrab.
My relevant imports are:
import tkinter
import pyscreenshot as ImageGrab
I am trying to save the screen using (after drawing the screen):
grab = ImageGrab.grab(bbox=canvas.bbox())
ImageGrab.grab_to_file(fileName,grab)
I don't know how to get the canvas position for using "ImageGrab.grab".
Is there any way to get the bounding box for the whole canvas, so as to later use ImageGrab to store a screenshot?
---Edit------------------------------------------------------------------
Solution:
box = (canvas.winfo_rootx(),canvas.winfo_rooty(),canvas.winfo_rootx()+canvas.winfo_width(),canvas.winfo_rooty() + canvas.winfo_height())
grab = ImageGrab.grab(bbox = box)
grab.save(file_path)
You have methods like winfo_x(), winfo_y() to get position inside parent widget (it doesn't have to be main window), and winfo_rootx(), winfo_rooty() to get position on screen.
Effbot.org: Basic Widget Methods
Code displays canvas' position on screen and inside parent Frame
import tkinter as tk
def callback():
print(' root.geometry:', root.winfo_geometry())
print('canvas.geometry:', canvas.winfo_geometry())
print('canvas.width :', canvas.winfo_width())
print('canvas.height:', canvas.winfo_height())
print('canvas.x:', canvas.winfo_x())
print('canvas.y:', canvas.winfo_y())
print('canvas.rootx:', canvas.winfo_rootx())
print('canvas.rooty:', canvas.winfo_rooty())
root = tk.Tk()
tk.Label(root, text='SOME WIDGETS IN ROOT').pack()
frame = tk.Frame(root)
frame.pack()
tk.Label(frame, text='SOME WIDGETS IN FRAME').pack()
canvas = tk.Canvas(frame, bg='green')
canvas.pack()
print('\n--- before mainloop start---\n')
callback()
print('\n--- after mainloop start ---\n')
root.after(100, callback)
root.mainloop()
Example result
--- before mainloop start ---
root.geometry: 1x1+0+0
canvas.geometry: 1x1+0+0
canvas.width : 1
canvas.height: 1
canvas.x: 0
canvas.y: 0
canvas.rootx: 0
canvas.rooty: 0
--- after mainloop start ---
root.geometry: 380x303+770+462
canvas.geometry: 380x267+0+18
canvas.width : 380
canvas.height: 267
canvas.x: 0
canvas.y: 18
canvas.rootx: 770
canvas.rooty: 498

Tkinter w.destroy() blocks/resets window resize

I have a Python 3.5 app using tkinter and create/remove widgets in a canvas on the fly. I noticed that when a widget is being destroyed using w.destroy() during the callback of a window resize, the callback finished correctly, but the window is being reset to its original size.
I've tried to delay the w.destroy() using self.after(1000, self.resize) and that kind off works but when dragging/holding the cursor down while the callback is being fired, the resize jumps back anyway.
Anyone having a suggestion to remove widgets on the fly and release memory?
UPDATE:
As a workaround I mark widgets to be deleted with w.delete=True and in an 'after' callback delete the widgets which are marked for deletion.
Here is a stripped down sample to illustrate the issue:
UPDATE: Simplified code to bare minimum to reproduce issue.
from tkinter import ALL, N, E, W, S, Canvas, Tk, ttk
class Resize(Tk):
def __init__(self):
super().__init__()
self.init = True
self.canvas = None
self.position_window()
self.create_canvas()
def resize(self):
if self.init:
self.init = False
return
# delete(ALL) will remove the widget from the display, but widget
# remains in memory as child of canvas. Resizing works ok.
self.canvas.delete(ALL)
# widget.destroy() will release memory of widget, but blocks/resets resizing.
# Window gets resized by 1-2 pixels and jumps back to its original size.
[child.destroy() for child in self.canvas.winfo_children()]
def create_canvas(self):
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.canvas = Canvas(self)
self.canvas.grid(row=0, column=0, sticky=(N, S, W, E))
self.canvas.bind('<Configure>', lambda *args: self.resize())
entry = ttk.Label(self.canvas, text='Some dummy text')
self.canvas.create_window(20, 20, window=entry, anchor='w')
def position_window(self):
self.resizable(1, 1)
sceenwidth = self.winfo_screenwidth()
screenheight = self.winfo_screenheight()
self.update_idletasks()
width = 600
height = 300
left = sceenwidth / 2 - width / 2
top = (screenheight / 2 - height / 2) / 3
self.geometry("%dx%d%+d%+d" % (width, height, left, top))
if __name__ == '__main__':
Resize().mainloop()
I did not try your code, but I had the same problem with my own project.
The window wasn't just resizing/resetting on destroy(); the <Configure> event was seemingly interrupted.
The window always reverted back to the very first <Configure> call back no matter how much or how little I resized. Because of that, or along with that, Button-1 was automatically releasing, preventing the window resize grip from following the mouse.
In other words, the window edge was "slipping out" from under the mouse, much like a scrollbar in Windows will reset mid-scroll when the mouse moves too far from it.
Anyways, a solution is to use ttk.Sizegrip and prohibit resizing the top level window with the window borders.
https://stackoverflow.com/a/65605907/7281120
I answered this somewhere else. I ended up polling
win32api.GetKeyState(0x01) >= 0
and storing a list of things to destroy when the mouse was released and it was safe to do so.

Resources