Tkinter canvas flickering - python

I'm attempting to implement a simple Pong game in Python using Tkinter, but unfortunately I'm having some major issues with flickering. I'm using a Canvas widget that covers the entire window and I'm drawing rectangles on said canvas many times per second. When I do this all drawn rectangles flicker regularly while the game is running, disappearing for a fraction of a second before appearing again.
A simple example of the logic I use for drawing in my game can be seen below, by running it you should be able to see the flickering in action:
from tkinter import *
import threading, time
def loop():
FRAME_TIME = 1 / 60
while True:
render()
time.sleep(FRAME_TIME)
def render():
canvas.delete(ALL)
canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill='black')
WIDTH = 800
HEIGHT = 600
root = Tk()
canvas = Canvas(root, width=800, height=600)
canvas.pack()
threading.Thread(target=loop, daemon=True).start()
root.mainloop()
Any ideas as to what is causing it?

The threading is totally not needed for your script (The Tkinter doesn't love the threading).
You should use the widget.after() instead of infinite for loop.
I guess you should define the canvas and the rectangle on the canvas and in a function you should move the other widgets. In this case you shouldn't delete/recreate the widgets.
The black "background" (rectangle) is static and it is not updated during the script running. An oval widget has been created on the canvas (create_oval()) and this widget moves in the render function (randomly change the X-Y positions between -3 and 3).
The canvas.after(10, render) means to call again-and-again the render function in every 10 secs. So actually it is an animation and the oval will move always on your canvas.
Example:
from tkinter import *
import random
def render():
canvas.move(oval, random.randint(-3, 3), random.randint(-3, 3))
canvas.after(10, render)
WIDTH = 800
HEIGHT = 600
root = Tk()
canvas = Canvas(root, width=800, height=600)
canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill="black")
oval = canvas.create_oval(400, 400, 410, 410, fill="red")
canvas.pack()
render()
root.mainloop()
GUI:
I think it is a good starting point for you to implement the complete pong game.

Related

is it possible to find the canvas shape clicked in a mouse button press event in tkinter?

I have a Tkinter canvas and bound a mouse button press event to it.
I was wondering if I could figure out on what specific shape a user clicked when bound to a canvas:
def callback(event):
pass
canvas = Canvas(root, width=100, height=100)
canvas.create_rectangle(10,50,40,90, tags="tile")
canvas.bind("<Button-1>", callback)
I know I can bind it to the the rectangle, the problem is that there might be a another shape overlaying the rectangle and then the click event doesn't work anymore.
I was thinking of using the find_overlapping method:
def callback(event):
canvas.find_overlapping(event.x, event.y,event.x, event.y)
but was wondering if there is an easier way?
You can use the special item tag current to refer to the object under the cursor. According to the official documentation:
The tag current is managed automatically by Tk; it applies to the current item, which is the topmost item whose drawn area covers the position of the mouse cursor (different item types interpret this in varying ways; see the individual item type documentation for details). If the mouse is not in the canvas widget or is not over an item, then no item has the current tag.
Here is a simple example. It draws a bunch of colored rectangles, and then sets the color to white when you click on them.
import tkinter as tk
import random
def click_handler(event):
event.widget.itemconfigure("current", fill="white")
root = tk.Tk()
canvas = tk.Canvas(root, bg="bisque", width=400, height=400)
canvas.pack(fill="both", expand=True)
canvas.bind("<1>", click_handler)
for i in range(100):
x = random.randint(0, 350)
y = random.randint(0, 350)
color = random.choice(("red", "orange", "green", "blue"))
width = random.randint(25, 50)
height = random.randint(25, 50)
canvas.create_rectangle(x, y, x+width, y+height, fill=color)
root.mainloop()

How to speed up the turtle while in a tkinter canvas

I'm trying to make the turtle move faster, which I would normally do using
import turtle as t
t.speed(0)
t.tracer(0,0)
But when I have it in a canvas using RawTurtle(), I'm not sure how to do this.
root = tk.Tk() #create root window
#create turtle canvas
canvas = tk.Canvas(root,width=500,height=500)
canvas.pack()
t = turtle.RawTurtle(canvas)
t.ht()
Here's my code. Anyone know how?
First, either do one or the other of these, not both:
t.speed(0)
t.tracer(0,0)
The speed(0) aka speed('fastest') gets you the fastest drawing animation. The tracer(0) eliminates drawing animation altogether. They don't add together.
You can get maximum speed, i.e. eliminate drawing animation, in turtle embedded in a Canvas by using turtle's TurtleScreen() wrapper:
import tkinter as tk
from turtle import TurtleScreen, RawTurtle
root = tk.Tk()
canvas = tk.Canvas(root, width=500, height=500)
canvas.pack()
screen = TurtleScreen(canvas)
screen.tracer(False)
turtle = RawTurtle(screen)
turtle.circle(100)
screen.tracer(True)
screen.mainloop()
I made a few edits to your code. For starters, you need to work all the commands after the t = turtle.RawTurtle(canvas) line. Then you need to add a .mainloop() function at the end.
This would be your final code:
import tkinter as tk
root = tk.Tk() #create root window
import turtle
#create turtle canvas
canvas = tk.Canvas(root,width=500,height=500)
canvas.pack()
t = turtle.RawTurtle(canvas)
t.speed(1)
t.forward(100)
t.ht()
root.mainloop()
When the speed is set to 1, this is the output:
When the speed is set to 0, this is the output:
Sorry about the gifs, and hope this helps!

Canvas resizing with window - good way to do it?

i created a window with a canvas inside. The canvas contains an rectangle. Both change their size through a callback together with the window size.
So my beginner question is: It works fine, but is this a good or common way to do this? Or is there a more efficient/common way?
from tkinter import* #
#creating instance of tkinter
obj = Tk()
#Set title of our window form
obj.title("MyFirst Window - WOW")
#Set dimension of form
x_size = 1200
y_size = 600
obj.geometry(str(x_size)+"x"+str(y_size))
obj.update()
w = Canvas(obj, width=x_size, height=y_size)
w.place(x=0,y=obj.winfo_height()-100)
w.create_rectangle(0, 0, obj.winfo_width(), 100, fill="#476042")
def callback(event):
print(str(obj.winfo_width())+'x'+str(obj.winfo_height()))
w.config(width=obj.winfo_width(),height=obj.winfo_height())
w.place(x=0,y=obj.winfo_height()-100)
w.create_rectangle(0, 0, obj.winfo_width(), 100, fill="#476042")
window = obj
window.bind("<Configure>", callback)
obj.mainloop()
No, this is not a good way to have the canvas resize. You should almost never use place. grid and pack make it much easier to create widgets that automatically resize.
For example, if you want the canvas to always be 100 pixels tall and fill the full width of the window, you can add it to obj like this:
w = Canvas(obj, width=x_size, height=100)
w.pack(side="bottom", fill="x")
As for the green rectangle, you have no choice but to use a binding on <Configure> if you want the rectangle to grow and shrink along with the canvas.
However, your callback creates a new rectangle every time it's called instead of modifying the coordinates of the existing rectangle. This is a memory leak, because your program will use more and more memory the longer it runs and the more often the window is resized. w.create_rectangle will return an identifier; you can use that identifier to later modify the rectangle.
Here's a simplified version of your code. I've changed the variable names to make it a bit easier to comprehend.
from tkinter import *
window = Tk()
window.title("MyFirst Window - WOW")
x_size = 1200
y_size = 600
window.geometry(str(x_size)+"x"+str(y_size))
window.update_idletasks()
canvas = Canvas(window, width=x_size, height=100, background="bisque")
canvas.pack(side="bottom", fill="x")
rect = canvas.create_rectangle(0, 0, window.winfo_width(), 100, fill="#476042")
def callback(event):
canvas.coords(rect, 0, 0, canvas.winfo_width(), 100)
canvas.bind("<Configure>", callback)
window.mainloop()

Keyup handler in Tkinter?

The title says it all. Is there a something in Tkinter I can call which will let me monitor specific key releases and let me link it to a function? I want to use it to let me end a timer that I am using to move my item. Here is the code:
from Tkinter import *
master = Tk()
master.wm_title("Ball movement")
width = 1000
height = 600
circle = [width / 2, height / 2, width / 2 + 50, height / 2 + 50]
canvas = Canvas(master, width = width, height = height, bg = "White")
canvas.pack()
canvas.create_oval(circle, tag = "ball", fill = "Red")
while True:
canvas.update()
def move_left(key):
#This is where my timer will go for movement
canvas.move("ball", -10, 0)
canvas.update()
def move_right(key):
#This is where my other timer will go
canvas.move("ball", 10, 0)
canvas.update()
frame = Frame(master, width=100, height=100)
frame.bind("<Right>", move_right)
frame.bind("<Left>", move_left)
frame.focus_set()
frame.pack()
mainloop()
You can define events prefixed with KeyRelease, such as <KeyRelease-a>. For example:
canvas.bind("<KeyRelease-a>", do_something)
Note: you need to remove your while loop. You should never create an infinite loop inside a GUI program, and you definitely don't want to be creating a frame every iteration -- you'll end up with thousands of frames in only a second or two!
You already have an infinite loop running, mainloop. If you want to do animation, use after to run a function every few milliseconds. For example, the following will cause a ball to move 10 pixels every 10th of a second. Of course, you'll want to handle the case where it moves off screen or bounces or whatever. The point is, you write a function that draws one frame of animation, and then have that function be called periodically.
def animate():
canvas.move("ball", 10, 0)
canvas.after(100, animate)

Getting Tkinter pack_forget to work only on unique objects

Ubuntu Linux 11.04 / Python 2.7 / Tkinter
I'm a novice at Python GUIs and I'm having trouble making single objects disappear and reappear. pack_forget kind of works for me, but the way I'm doing it makes all the objects blink in and out, when I only want one at a time. In the boiled-down example below, I'm trying to only make object "a" appear and then disappear and then do the same for object "b".
I would be very grateful for any help, plus some better docs on this would be cool too. The stuff I've been able to find has been light on examples.
from Tkinter import *
import time
class Box:
def __init__(self, canvas):
self.canvas = canvas
def type(self, type):
if type == "1":
self.if = self.canvas.create_rectangle(0, 0, 50, 50, fill="red")
elif type == "2":
self.if = self.canvas.create_rectangle(50, 50, 100, 100, fill="blue")
def hide(self):
self.canvas.pack_forget()
self.canvas.update_idletasks()
root.update()
def unhide(self):
self.canvas.pack()
self.canvas.update_idletasks()
root.update()
root = Tk()
frame = Frame(root)
frame.pack()
canvas = Canvas(frame, width=500, height=200)
canvas.pack()
root.update()
a = Box(canvas)
a.type("1")
b = Box(canvas)
b.type("2")
a.unhide()
time.sleep(.5)
a.hide()
time.sleep(1)
b.unhide()
time.sleep(.5)
b.hide()
time.sleep(1)
There are many things wrong or curious about your code. For one, you should never call sleep in a program that has an event loop. When you call that, your whole program will freeze. If you want something to happen after a fixed period of time, use the after method to schedule a function to run in the future.
Second, there is no point in calling root.update after calling self.canvas.update_idletasks. For one, assume it is a rule written in stone that you should never call update. It's OK to call update if you know the ramifications of that, but since you are just learning and don't know, assume it's unsafe to call it. Plus, update does the same work as update_idletasks and more, so if you do choose to call update, calling update_idletasks is unnecessary.
As to your real problem. You seem to be wanting to create two distinct "Box" objects, and want to hide and show them independently (?). However, both boxes are rectangles that are drawn on the same canvas. When you call pack_forget, that affects the whole canvas so both of these objects will disappear and then reappear.
It isn't clear what your intention is. If each instance of "Box" is just a rectangle on the canvas, you don't want to use pack_forget because that works on widgets, not canvas objects.
If you just want the rectangles to appear and disappear you have several choices. You can destroy and recreate them each time, or you can use the canvas move or coords method to move the item to a non-visible portion of the canvas. You could also manipulate the stacking order to raise or lower an object above or below any or all other objects.
Here's a quick example that uses the trick of moving the items outside the visible area of the canvas.
from Tkinter import *
import time
class Box:
def __init__(self, canvas, type):
self.canvas = canvas
if type == "1":
self.rect = canvas.create_rectangle(0, 0, 50, 50, fill="red")
elif type == "2":
self.rect = canvas.create_rectangle(50, 50, 100, 100, fill="blue")
self.coords = canvas.coords(canvas, self.rect)
def hide(self):
# remember where this object was
self.coords = canvas.coords(self.rect)
# move it to an invisible part of the canvas
self.canvas.move(self.rect, -1000, -1000)
def unhide(self):
# restore it to where it was
self.canvas.coords(self.rect, *self.coords)
root = Tk()
frame = Frame(root)
frame.pack()
canvas = Canvas(frame, width=500, height=200)
canvas.pack()
a = Box(canvas, type="1")
b = Box(canvas, type="2")
root.after(1000, a.hide)
root.after(2000, a.unhide)
root.after(3000, b.hide)
root.after(4000, b.unhide)
root.mainloop()
Finally, if what you really want is for an object to blink continuously, you can have the hide method automatically call the unhide method and visa versa:
def hide(self):
# remember where this object was
self.coords = canvas.coords(self.rect)
# move it to an invisible part of the canvas
self.canvas.move(self.rect, -1000, -1000)
# unhide after a second
self.canvas.after(1000, self.unhide)
def unhide(self):
# restore it to where it was
self.canvas.coords(self.rect, *self.coords)
# re-hide after a second
self.canvas.after(1000, self.hide)

Categories