Keyup handler in Tkinter? - python

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)

Related

Is there a Python tkinter function that makes a drawing’s coords on a certain ratio on the canvas widget?

I’m a first timer at tkinter(python) and what I want to do is to make a line of text stay on the same coords ratio on the canvas. For example, I want a line of text to stay in the middle. Is there any tkinter text parameters that make it stay in a certain ratio without running a while loop? I want minimal time complexity.
Your GUI can have a function bound to the Canvas <Configure> event that fires whenever the Canvas changes size. A simple example below.
There is also a Canvas.scale method which will change the location and size of canvas objects. Text may move but will not change size.
import tkinter as tk
root = tk.Tk()
# Create a canvas that will change size with the root window.
canvas = tk.Canvas( root, width = 600, height = 400, borderwidth = 2,
relief = tk.SOLID )
canvas.grid( sticky = 'nsew', padx = 5, pady = 5 )
root.grid_columnconfigure( 0, weight = 1 )
root.grid_rowconfigure( 0, weight = 1 )
text = canvas.create_text( 50, 50, text = 'Test Text' )
def on_canvas_config( event ):
""" This is bound to the canvas <Configure> event.
It executes when the canvas changes size.
The event is an object that contains the new width and height.
"""
x = max( 25, event.width // 2 ) # x >= 25
y = max( 12, event.height // 8 ) # y >= 12
canvas.coords( text, x, y ) # Set the canvas coordinates
canvas.bind( '<Configure>', on_canvas_config )
root.mainloop()
The on_canvas_configure function can be written to amend the coords of any objects in the Canvas to meet your requirements. I've never tried to use the Canvas.scale method but this may be worth exploring if there are many objects to reposition on the canvas.

Tkinter: Button in a rectangle on a canvas of frame in Python 3.

I have been trying to create a moving oval that is more like a button. As of now I don't intend to perform any function on clicking the button, so I have created a pass function.
The scenario is as follows:
The frame in the tkinter window is covered by a canvas in magenta color, with a circle (in yellow), at a designated position. This circle is actually a button, which when pressed opens a popup menu displaying some info (not essential right now). I have managed to create the circle but struggling to incorporate a button into the oval. The button and the circle must be together, because this pair is supposed to move in the canvas frame every 3 second (sounds like a GPS dot but the dot being a button).
But when i am trying to create the button, the canvas vanishes, and the frame resizes according to the width of the button.
Kindly help me in identifying the mistake and the correct code accordingly:
enter code here
from tkinter import *
import random
import time
def nothing():
pass
main = Tk()
frame_1 = Frame(main)
frame_1.grid(row=0, column=0)
main_canvas = Canvas(frame_1, width=200, height=200, bg='magenta')
oval = main_canvas.create_oval(20, 20, 40, 40, outline='black', fill='yellow')
main_canvas.pack()
frame_2 = Frame(main)
frame_2.grid(row=0, column=1)
'''
button2 = Button(main_canvas, text="Q", command=nothing, anchor=W)
button2.configure(width=3, activebackground="#33B5E5", relief=FLAT)
button2_window = main_canvas.create_window(10, 10, anchor=NW, window=button2)
button2.pack(side=TOP)
'''
label_f2_1 = Label(frame_2, text="")
label_f2_1.pack()
label_f2_2 = Label(frame_2, text="")
label_f2_2.pack()
x_current, y_current = 30, 30
for loops in range(86400):
x_new = random.randint(10, 190)
y_new = random.randint(10, 190)
main_canvas.move(oval, x_new-x_current, y_new-y_current)
x_current, y_current = x_new, y_new
main_canvas.update()
time.sleep(1)
now = str(time.ctime())
label_f2_2.configure(text=now[10:])
label_f2_1.configure(text=now[0:10])
# print(time.localtime())
main.mainloop()
Using PyEdu running on Python 3.7 interpreter.
The canvas vanishes because that's the behavior of pack -- it will cause a widget to grow or shrink to fit its children. The button is a child of the canvas, so the canvas tries to shrink to fit the canvas.
This solution is wrong in many ways, For example, to add a widget to a canvas you shouldn't use pack, place, or grid. Instead, you should use the canvas method create_window.
However, you don't need a button inside the oval at all. You can create a binding that will call a function whenever the user clicks directly on the oval.
For example, the following will cause the function showInfo to be called whenever the oval is clicked:
def showInfo(event):
print("you clicked on the oval")
main_canvas.tag_bind(oval, "<1>", showInfo)
HOWEVER, this won't work very well because of how you're doing your animation loop. Your code is sleeping more than anything else, which makes it very hard for tkinter to process events. Tkinter cannot process events while sleep is sleeping.
Instead of using a for loop, you need to write a function that does everything that you are doing inside the loop (ie: it draws one frame of animation). This function can use after to call itself again in the future, setting up a perpetual loop.
For example:
def doOneIteration():
newx = random.randint(10, 190)
newy = random.randint(10, 190)
main_canvas.coords(oval, newx, newy, newx+20, newy+20)
now = str(time.ctime())
label_f2_2.configure(text=now[10:])
label_f2_1.configure(text=now[0:10])
main.after(1000, doOneIteration)
doOneIteration()
The first time you call doOneIteration, it will do everything that was in the body of loop. Then, when it's finished it will cause itself to be called again in one second. The next time it's called, it will do the work and then again cause itself to be called in one second. It will do this for as long as the program runs.
The advantage here is that at no time are you causing the program to go to sleep. This insures that tkinter is able to process a steady stream of events without interruption.

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()

Tkinter canvas flickering

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.

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