Tkinter Keyboard input slow - python

from tkinter import Tk, Label, PhotoImage, TkVersion, Frame, Canvas
def up(event):
w.move(car, 0, -10)
root = Tk()
root.geometry("800x800")
w = Canvas(root, width=800, height=800)
img = PhotoImage(file="track.png")
track = w.create_image(0, 0, image=img, anchor='nw')
img_2 = PhotoImage(file="car.png")
car = w.create_image(380, 380, image=img_2, anchor='nw')
w.pack()
root.bind("<Up>", up)
root.mainloop()
I have a simple tkinter program here that moves a car image up the screen when the up arrow key is pressed. However, when I run the program, I can visibly see the lag, as each frame is animated one by one. I never had this kind of issue in other graphics programming like SDL in C++ - the movement was always very smooth. What could be going on here?

Fixed using dictionary and after:
from tkinter import Tk, Label, PhotoImage, TkVersion, Frame, Canvas
keys = {
"U": False,
"L": False,
"D": False,
"R": False
}
def up(event):
keys["U"] = True
def noup(event):
keys["U"] = False
def left(event):
keys["L"] = True
def noleft(event):
keys["L"] = False
def down(event):
keys["D"] = True
def nodown(event):
keys["D"] = False
def right(event):
keys["R"] = True
def noright(event):
keys["R"] = False
def move_car():
if keys["U"]:
w.move(car, 0, -5)
if keys["L"]:
w.move(car, -5, 0)
if keys["D"]:
w.move(car, 0, 5)
if keys["R"]:
w.move(car, 5, 0)
root.after(10, move_car)
root = Tk()
root.geometry("800x800")
w = Canvas(root, width=800, height=800)
img = PhotoImage(file="track.png")
track = w.create_image(0, 0, image=img, anchor='nw')
img_2 = PhotoImage(file="car.png")
car = w.create_image(380, 380, image=img_2, anchor='nw')
w.pack()
root.bind("<KeyPress-Up>", up)
root.bind("<KeyRelease-Up>", noup)
root.bind("<KeyPress-Left>", left)
root.bind("<KeyRelease-Left>", noleft)
root.bind("<KeyPress-Down>", down)
root.bind("<KeyRelease-Down>", nodown)
root.bind("<KeyPress-Right>", right)
root.bind("<KeyRelease-Right>", noright)
root.after(0, move_car)
root.mainloop()

Related

Borderwidth attribute in Tkinter not working

I am trying to use borderwidth attribute to highlight only the "New Game" part of the png image, however, it isn't working, how do I solve this error?
from tkinter import *
from PIL import ImageTk, Image
root = Tk()
root.iconbitmap('unnamed.ico')
root.title('2048')
bg = ImageTk.PhotoImage(Image.open("welcome.png"))
new_game_btn = ImageTk.PhotoImage(Image.open("image_40.png"))
my_canvas = Canvas(root, width=780, height=550)
my_canvas.pack()
my_canvas.create_image(0, 0, image=bg, anchor=NW)
button1 = Button(root, image=new_game_btn, borderwidth=00, relief=FLAT)
button1_window = my_canvas.create_window(100, 100, anchor=NW, window=button1)
root.mainloop()
Your only option seems to be to draw the image to the canvas instead of creating a button (because widgets in Tkinter do not support transparency).
The drawback is, that you will have to check the mouse coordinates on the Canvas in order to see if your "Button" has been clicked.
The code would look like this:
from PIL import ImageTk, Image
root = Tk()
root.iconbitmap('unnamed.ico')
root.title('2048')
bg = ImageTk.PhotoImage(Image.open("welcome.png"))
new_game_btn = ImageTk.PhotoImage(Image.open("image_40.png"))
my_canvas = Canvas(root, width=780, height=550)
my_canvas.pack()
my_canvas.create_image(0, 0, image=bg, anchor=NW)
my_canvas.create_image(100, 100, image=new_game_btn, anchor=NW)
root.mainloop()
This results in that (don't mind the typo...):
If you want to check if that part of the canvas was clicked, I would just check for the bounding box like this:
from tkinter import *
from PIL import ImageTk, Image
x_pos = 100
y_pos = 100
width = 100
height = 100
def callback(event):
print("Canvas clicked at", event.x, event.y)
if (event.x > x_pos and event.x < x_pos + width):
if(event.y > y_pos and event.y < y_pos + height):
print("The button was clicked (approximately).")
root = Tk()
root.iconbitmap('unnamed.ico')
root.title('2048')
bg = ImageTk.PhotoImage(Image.open("welcome.png"))
new_game_btn = ImageTk.PhotoImage(Image.open("image_40.png"))
my_canvas = Canvas(root, width=780, height=550)
my_canvas.pack()
my_canvas.create_image(0, 0, image=bg, anchor=NW)
my_canvas.create_image(100, 100, image=new_game_btn, anchor=NW)
my_canvas.bind("<Button-1>", callback)
root.mainloop()

A _tkinter.TclError occured while trying to make a scrollable frame in python

So I have been trying to make a scrollable frame. I've had been searching and these 3 had the most impact:
https://stackoverflow.com/questions/16188420/tkinter-scrollbar-for-frame\
https://stackoverflow.com/questions/3085696/adding-a-scrollbar-to-a-group-of-widgets-in-tkinter\
How could I get a Frame with a scrollbar in Tkinter?
And I have come up with the following code:
from tkinter import *
class Test_tk_stuff():
def __init__(self):
self.screen = Tk()
self.screen.geometry("500x500")
self.structure()
self.screen.mainloop()
def scroller(self, canvas):
canvas.configure(scrollregion = canvas.bbox("all"))
def frame_expander(self, event):
canvas_width = event.width
self.canvas.itemconfig(self.frame, width = canvas_width)
def structure(self):
parent = Frame(self.screen)
parent.pack(expand = True, fill = BOTH)
self.canvas = Canvas(parent, bg = "green", highlightthickness = 0, relief = RAISED)
self.frame = Frame(self.canvas, bg = "blue")
myscrollbar = Scrollbar(parent, orient = "vertical", command = self.canvas.yview)
self.canvas.configure(yscrollcommand = myscrollbar.set)
myscrollbar.pack(side = RIGHT, fill = Y)
self.canvas.pack(side = LEFT, fill = BOTH, expand = True)
self.canvas.create_window((0, 0), window = self.frame)
# can't do: self.frame.pack(expand = True, fill = BOTH) because it will become unscrollable
# Event Bind
self.frame.bind("<Configure>", lambda event, canvas = self.canvas: self.scroller(self.canvas))
self.canvas.bind("<Configure>", self.frame_expander)
# initialize number of minimum columns
for num_columns in range(3):
self.frame.columnconfigure(num_columns, weight = 1)
a = "Button Text!"
# fill it to - 1.) test scrollbar 2.) actually using the frame inside
for place2 in range(10):
Button(self.frame, text = a, bg = "black", fg = "white").grid(row = place2, column = 1, sticky = "NSEW", ipady = 15)
if __name__ == "__main__":
Test_tk_stuff()
But somehow when I run it, it shows a _tkinter.TclError. I tried searching what that is and how to fix it, but, as you can see, I wasn't able to fix it.
Is there something wrong with my implementation?
Thanks in advance.
canvas.itemconfig() is used on canvas item returned by canvas.create_xxxxx() functions, not on tkinter widget (self.frame).
Save the canvas item ID for the self.frame and use it in canvas.itemconfig():
def frame_expander(self, event):
canvas_width = event.width
self.canvas.itemconfig(self.frame_item, width=canvas_width)
def structure(self):
...
self.frame_item = self.canvas.create_window((0, 0), window = self.frame)
...
use self.frame.config(width=canvas_width) instead of canvas.itemconfig()

zooming on a tkinter canvas with lines created by live data

I am new to Tkinter and trying to figure out how to be able to zoom in and out on a canvas without messing up the drawn lines created by live data. Every second a new line is drawn and is supposed to be connected to the previous one, but if I zoom in on the canvas the next line that will be drawn is not connected to the previous one. It seems like all the lines are drawn based on some window coordinates (which are consistent no matter how much I zoom in or out), instead of being based on the canvas itself. I want to be able to both zoom in and zoom out as much as I want and still see a line being drawn from the upper left corner of the canvas to the bottom right corner of the canvas. I have tried to provide some code to make it easier to understand.
import tkinter as tk
import threading
import time
root = tk.Tk()
pressed = False
flag_run = False
GRID_W = 500
GRID_H = 500
def thread_entry(name):
print("<starting thread>")
i = 0
j = 0
while flag_run:
time.sleep(1)
canvas.create_line(i, j, i+1, j+1, fill="red")
i += 1
j += 1
print("<ending thread>")
def start():
global flag_run
flag_run = True
global thread
thread = threading.Thread(target=thread_entry, args=(1,))
thread.start()
def stop():
global flag_run
if flag_run is True:
flag_run = False
global thread
thread.join(timeout=0.1)
# move
def move_start(event):
canvas.scan_mark(event.x, event.y)
def move_move(event):
canvas.scan_dragto(event.x, event.y, gain=1)
# move
def pressed2(event):
global pressed
pressed = not pressed
canvas.scan_mark(event.x, event.y)
def move_move2(event):
if pressed:
canvas.scan_dragto(event.x, event.y, gain=1)
# windows zoom
def zoomer(event):
if (event.delta > 0):
canvas.scale("all", event.x, event.y, 1.1, 1.1)
elif (event.delta < 0):
canvas.scale("all", event.x, event.y, 0.9, 0.9)
canvas.configure(scrollregion = canvas.bbox("all"))
def zooming_in(event):
canvas.scale("all", event.x, event.y, 1.1, 1.1)
def zooming_out(event):
canvas.scale("all", event.x, event.y, 0.9, 0.9)
canvas = tk.Canvas(root, width=GRID_W, height=GRID_H, background="white")
canvas.grid(row=0, column=0, sticky="nsew")
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
for i in range(GRID_W//10 + 1):
canvas.create_line(i*10, 0, i*10, GRID_H, fill="#c9c9c9")
for j in range(GRID_H//10 + 1):
canvas.create_line(0, j*10, GRID_W, j*10, fill="#c9c9c9")
canvas.create_line(GRID_W // 2, 0, GRID_W // 2, GRID_H, fill="black", width=2)
canvas.create_line(0, GRID_H // 2, GRID_W, GRID_H // 2, fill="black", width=2)
canvas.bind("<ButtonPress-1>", move_start)
canvas.bind("<B1-Motion>", move_move)
canvas.bind("<ButtonPress-2>", pressed2)
canvas.bind("<Motion>", move_move2)
zoom_in = tk.Button(root, text="+")
zoom_in.grid(row=1, column=0)
zoom_in.bind("<Button-1>", zooming_in)
zoom_out = tk.Button(root, text="-")
zoom_out.grid(row=1, column=1)
zoom_out.bind("<Button-1>", zooming_out)
button = tk.Button(root, text="Start", command = start)
button.grid(row=2, column=0)
button_s = tk.Button(root, text="Stopp", command = stop)
button_s.grid(row=2, column=1)
root.mainloop()
I appreciate all the help and guidance I can get!
try stopping the flag_run while you hit the start button (zooming_in(event)) and continue when done zooming.
try remembering where the line was, and keep logging the data while you zoom in and out so you are not missing any values as you currently do.

Python RawTurtle not following goto() command

I am trying to create a program where you click on a Tkinter canvas and a RawTurtle moves to the mouse, but my code is not working. The canvas has a Button-1 event binded to it to tell the program the coordinates of the mouse.
But, when you click on the canvas, instead of the turtle going to the mouse, it kind of mirrors what you would expect it to do (moves away from the mosue as if another mosue is being mirrored). Both the event and the turtle position coordinates are the same when they are printed out.
Code:
import turtle
from tkinter import *
def move(event):
global t
x = event.x
y = event.y
t.setpos(x,y)
print(t.pos())
print(event)
def penState(event):
global penDown,t
if penDown == True:
t.penup()
penDown = False
else:
t.pendown()
penDown = True
def changeWidth(w):
t.pensize(w)
def changeColour(e=None):
global colourBox
t.color(colourBox.get())
colourBox.configure(fg=colourBox.get())
def doCircle():
global checkFillIsTrue,circleSizeBox
if checkFillIsTrue.get() == 1:
begin_fill()
t.circle(int(circleSizeBox.get()))
end_fill()
else:
circle(int(circleSizeBox.get()))
window = Tk('Paint')
window.title('onionPaint')
root = Frame(window)
root.pack(side=LEFT)
cv = Canvas(window,width=500,height=500)
t = turtle.RawTurtle(cv)
t.resizemode('user')
cv.bind('<Button-1>',move)
cv.bind('<Button-2>',penState)
cv.pack(side=RIGHT)
checkFillIsTrue=IntVar()
penDown = True
#Pen width box
sizeLabel = Label(root, text="Pen Width")
sizeLabel.grid()
sizeScale = Scale( root, variable = \
'var',orient=HORIZONTAL,command=changeWidth )
sizeScale.grid()
#Colour box
colourLabel = Label(root, text="Color(HEX or name):")
colourLabel.grid()
colourFrame = Frame(root)
colourFrame.grid()
colourBox = Entry(colourFrame, bd=1)
colourBox.pack(side=LEFT)
colourSubmit = Button(colourFrame,text="OK",command=changeColour)
colourSubmit.pack(side=RIGHT)
#Fill
fillLabel = Label(root,text='Fill')
fillLabel.grid()
fillFrame = Frame(root)
fillFrame.grid()
beginFill = Button(fillFrame,text='Begin Fill',command=t.begin_fill)
endFill = Button(fillFrame,text='End Fill',command=t.end_fill)
beginFill.pack(side=LEFT)
endFill.pack(side=RIGHT)
#Mmore shapes
Label(root,text='Shapes').grid()
#Circle form
Label(root,text='Circle',font=('Heveltica',8)).grid()
circleSize = Frame(root)
circleSize.grid()
circleSizeBox = Entry(circleSize,bd=1)
circleSizeBox.insert(0,'Radius')
circleSizeBox.pack(side=LEFT)
fillCheck =
Checkbutton(circleSize,text='Fill',variable=checkFillIsTrue).pack(side=LEFT)
circleSizeSubmit =
Button(circleSize,text='Draw',command=doCircle).pack(side=RIGHT)
#Text form
Label(root,text='Text',font=('Heveltica',8)).grid()
textFrame = Frame(root)
textFrame.grid()
window.mainloop()
Any help with this problem would be greatly appreciated.
Your code is a disaster. You should specifically read up on the 'global' keyword in Python and when it must be used. And Python code style in general. I believe the key fix to your program is to introduce a TurtleScreen overlay on the canvas and then switch to turtle event handing rather than tkinter event handing.
I've reworked your code below, fixing as many problems as I could:
import turtle
from tkinter import *
FONT = ('Helvetica', 8)
def move(x, y):
terrapin.setpos(x, y)
print(terrapin.pos())
def penState(x, y):
global penDown
if penDown:
terrapin.penup()
penDown = False
else:
terrapin.pendown()
penDown = True
def changeWidth(w):
terrapin.pensize(w)
def changeColour():
color = colourBox.get()
terrapin.color(color)
colourBox.configure(fg=color)
def doCircle():
radius = int(circleSizeBox.get())
if checkFillIsTrue.get():
terrapin.begin_fill()
terrapin.circle(radius)
terrapin.end_fill()
else:
terrapin.circle(radius)
window = Tk('Paint')
window.title('onionPaint')
root = Frame(window)
root.pack(side=LEFT)
canvas = Canvas(window, width=500, height=500)
screen = turtle.TurtleScreen(canvas)
terrapin = turtle.RawTurtle(screen)
screen.onclick(move, btn=1)
screen.onclick(penState, btn=2)
canvas.pack(side=RIGHT)
checkFillIsTrue = BooleanVar()
penDown = True
# Pen width box
sizeLabel = Label(root, text="Pen Width")
sizeLabel.grid()
sizeScale = Scale(root, variable='var', orient=HORIZONTAL, command=changeWidth)
sizeScale.grid()
# Colour box
colourLabel = Label(root, text="Color(HEX or name):")
colourLabel.grid()
colourFrame = Frame(root)
colourFrame.grid()
colourBox = Entry(colourFrame, bd=1)
colourBox.pack(side=LEFT)
colourSubmit = Button(colourFrame, text="OK", command=changeColour)
colourSubmit.pack(side=RIGHT)
# Fill
fillLabel = Label(root, text='Fill')
fillLabel.grid()
fillFrame = Frame(root)
fillFrame.grid()
beginFill = Button(fillFrame, text='Begin Fill', command=terrapin.begin_fill)
endFill = Button(fillFrame, text='End Fill', command=terrapin.end_fill)
beginFill.pack(side=LEFT)
endFill.pack(side=RIGHT)
# More shapes
Label(root, text='Shapes').grid()
# Circle form
Label(root, text='Circle', font=FONT).grid()
circleSize = Frame(root)
circleSize.grid()
circleSizeBox = Entry(circleSize, bd=1)
circleSizeBox.insert(0, 'Radius')
circleSizeBox.pack(side=LEFT)
fillCheck = Checkbutton(circleSize, text='Fill', variable=checkFillIsTrue).pack(side=LEFT)
circleSizeSubmit = Button(circleSize, text='Draw', command=doCircle).pack(side=RIGHT)
# Text form
Label(root, text='Text', font=FONT).grid()
textFrame = Frame(root)
textFrame.grid()
window.mainloop()

How to update an image on a Canvas?

This is the essence of the code I'm having trouble with:
camelot = Canvas(main, width = 400, height = 300)
camelot.grid(row = 0, column = 0, rowspan = 11, columnspan = 3)
MyImage = PhotoImage(file = "sample1.gif")
camelot.create_image(0, 0, anchor = NW, image = MyImage)
This is run at the beginning. What I want to do on later in another function is replace "sample1.gif" by "sample2.gif", and, possibly later on, replace that in turn by "sample3.gif". I'm stuck and nothing I've been trying has worked so far.
Adding image to canvas:
self.image_on_canvas = self.canvas.create_image(0, 0, image = ...)
Changing image on canvas:
self.canvas.itemconfig(self.image_on_canvas, image = ...)
Full example:
from Tkinter import *
#----------------------------------------------------------------------
class MainWindow():
#----------------
def __init__(self, main):
# canvas for image
self.canvas = Canvas(main, width=60, height=60)
self.canvas.grid(row=0, column=0)
# images
self.my_images = []
self.my_images.append(PhotoImage(file="ball1.gif"))
self.my_images.append(PhotoImage(file="ball2.gif"))
self.my_images.append(PhotoImage(file="ball3.gif"))
self.my_image_number = 0
# set first image on canvas
self.image_on_canvas = self.canvas.create_image(0, 0, anchor='nw', image=self.my_images[self.my_image_number])
# button to change image
self.button = Button(main, text="Change", command=self.onButton)
self.button.grid(row=1, column=0)
#----------------
def onButton(self):
# next image
self.my_image_number += 1
# return to first image
if self.my_image_number == len(self.my_images):
self.my_image_number = 0
# change image
self.canvas.itemconfig(self.image_on_canvas, image=self.my_images[self.my_image_number])
#----------------------------------------------------------------------
root = Tk()
MainWindow(root)
root.mainloop()
Images used in example:
ball1.gif ball2.gif ball3.gif
Result:
MyImage = PhotoImage(file = "sample1.gif")
labelorbuttontodisplayit.image = MyImage
labelorbuttontodisplayit.configure(image=MyImage)
:P that should do it. I only tried to use that code on label or buttons, never as a Canvas, but i guess you can adapt that piece of code a bit.

Categories