I have created an image on a canvas in tkinter that responds to a button event. And, the object is created on position x and position y where that event took place. But the object changes shape constantly.
def leftclick(event):
canvas1=Canvas(play, height=hei, width=wid)
canvas1.grid(row=0, column=0, sticky=W)
canvas1.delete("all")
x=event.x
y=event.y
print(event.x, event.y)
bullet = canvas1.create_oval(x,y, 100,100, fill="red")
xspeed=random.randint(0, 50)
yspeed=random.randint(0,50)
This just draws ovals which are randomly shaped. Why is this happening and how do I fix it?
You should only create your canvas once, but that's not a problem.
The problem is that the tkinter tries to create an oval inside the rectangle. You've specified the 2 points of the rectangle: x,y and 100,100. Just use bullet = canvas1.create_oval(x-50,y-50, x+50,y+50, fill="red") or whatever number you pick instead of 50. Hope that's helpful!
Related
The following code:
from tkinter import *
root=Tk()
for x in range(10):
for y in range(10):
canvas=Canvas(root, width='15',height='15',highlightthickness=0,bg='red')
canvas.create_line(canvas.winfo_x(),canvas.winfo_y(),canvas.winfo_x()+15,canvas.winfo_y()+15,width=2,fill='black')
canvas.grid(row=y,column=x,sticky='NESW')
for x in range(10):
for y in range(10):
root.columnconfigure(x,weight=1)
root.rowconfigure(y,weight=1)
root.mainloop()
produces this, which is a 10 by 10 grid filled with canvases; there is a line extending from the top left to the bottom right corner of each canvas.
When I resize the window, the canvas widgets resize correctly, but the lines retain their shape like this. The lines need to adjust according to the window/widget size.
The core of the problem is that the lines are made using the coordinates of the top left corner of the widget, and are extended 15 pixels in each direction. Is there a way of getting the coordinates of the bottom right corner of the widget, so that the lines can change their shape dynamically, or some other way of keeping the lines shape, relative to the widget?
You can get the current width and height of any widget with the winfo_width and winfo_height methods. If you are binding to the <Configure> method to track when the canvas changes size, the event object has a width and height attribute.
For example:
from tkinter import *
def redraw_line(event):
width = event.width
height = event.height
canvas = event.widget
canvas.coords("diagonal", 0, 0, width, height)
root=Tk()
for x in range(10):
for y in range(10):
canvas=Canvas(root, width='15',height='15',highlightthickness=0,bg='red')
canvas.bind("<Configure>", redraw_line)
# coordinates are irrelevant; they will change as soon as
# the widget is mapped to the screen.
canvas.create_line(0,0,0,0, tags=("diagonal",))
canvas.grid(row=y,column=x,sticky='NESW')
for x in range(10):
for y in range(10):
root.columnconfigure(x,weight=1)
root.rowconfigure(y,weight=1)
root.mainloop()
I'm trying to implement a particle filter and I chose python for it because I kinda like python. By now i have written my gui using tkinter and python 3.4.
I use the tkinter.canvas object to display a map (png image loaded with PIL) and then i create dots for each particle like:
dot = canvas.create_oval(x, y, x + 1, y + 1)
When the robot moves I calculate the new position of each particle with the control command of the robot, the particles position and the particles alignment.
To move the particle tkinter.canvas has two methods:
canvas.move() canvas.coords()
But both methods seem to update the gui immediately which is OK when there are about 100 particles but not if there are 200 - 5000 (what I actually should have in the beginning for the global localization). So my problem is the performance of the gui.
So my actual question is: Is there a way in tkinter to stop the canvas from updating the gui, then change the gui and then update the gui again? Or can you recommend me a module that is better than tkinter for my use-case?
Your observation is incorrect. The canvas is not updated immediately. The oval isn't redrawn until the event loop is able to process events. It is quite possible to update thousands of objects before the canvas is redrawn. Though, the canvas isn't a high performance tool so moving thousands of objects at a high frame rate will be difficult.
If you are seeing the object being updated immediately it's likely because somewhere in your code you are either calling update, update_idletasks, or you are otherwise allowing the event loop to run.
The specific answer to your question, then, is to make sure that you don't call update or update_idletasks, or let the event loop process events, until you've changed the coordinates of all of your particles.
Following is a short example. When it runs, notice that all of the particles move at once in one second intervals. This is because all of the calculations are done before allowing the event loop to redraw the items on the canvas.
import Tkinter as tk
import random
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.canvas = tk.Canvas(self, width=500, height=500, background="black")
self.canvas.pack(fill="both", expand=True)
self.particles = []
for i in range(1000):
x = random.randint(1, 499)
y = random.randint(1, 499)
particle = self.canvas.create_oval(x,y,x+4,y+4,
outline="white", fill="white")
self.particles.append(particle)
self.animate()
def animate(self):
for i, particle in enumerate(self.particles):
deltay = (2,4,8)[i%3]
deltax = random.randint(-2,2)
self.canvas.move(particle, deltax, deltay)
self.after(30, self.animate)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
I am new to Python and Tkinter and I am needing to move a button.
I have been using button1.pack() to place the button.
I am not able to move the button from its original position at the bottom of the screen.
Absolute position
button1.place(x=some_value, y=some_value)
or relative position
button1.pack(side=RIGHT, padx=some_value, pady=some_value)
padx, pady - paddings
button1.grid(row = 0, column = 0, padx = 0, pady = 0)
But this cannot be used together with pack(), you need to stick to either one.
And this only orders objects relatively, so if you have only one object and you set the row and column to 40 and 50, respectively, the object will still be on the top left corner.
animated movement:
import time, tkinter
X,Y=,0,0
for i in range(pixelsToMove):
button.place(x=X,y=Y)
X=X+1
Y=Y+1
time.sleep(0.1)
it will move to the bottom right corner slowly here is how to adjust it:
to move to the right ONLY at a set Y then at the y=Y part do y=set_y and for x do the same thing to move left change X=X+1 do X=X-1 and at X,Y = 0,0 do X,Y = start_x,0 same for y.
How can I get the size of a button object?
If I do:
quitButton = Button(self, text="Quit", command=self.quit)
_x = quitButton.winfo_width()
_y = quitButton.winfo_height()
print _x, _y
It prints 1 1.
What am I doing wrong?
The size will be 1x1 until it is actually drawn on the screen, since the size is partly controlled by how it is managed (pack, grid, etc).
You can call self.update() after you've put it on the screen (pack, grid, etc) to cause it to be drawn. Once drawn, the winfo_width and winfo_height commands will work.
I am drawing a lot of moving particles in a stationary box with Tkinter. My box is always there and does not change as time goes by, whereas the particles need to be updated.
My first intuition is to delete ALL the things (both particles and the box) and then redraw everything.
canvas.delete(ALL)
It indeed works, but the frame updates get extremely slow. This is because my box is of an irregular shape, which implies that I have to draw the box dot by dot. So this delete-everything-and-redraw-everything method is unsatisfactory.
I wish that the box is drawn only once, and only the particles get deleted and redrawn (updated). How should I do this?
Suppose you have a rectangle on canvas:
canvas.create_rectangle(x0, y0, x1, y1)
This would return a handle, so if you keep track of it,
myRectangle = canvas.create_rectangle(x0, y0, x1, y1)
canvas.delete(myRectangle)
This will delete only the myRectangle object.
Another way of doing it is to use tags.
canvas.create_rectangle(x0, y0, x1, y1, tags="myRectangle")
canvas.delete("myRectangle")
What you need to do is assign the drawings to variables, and then delete those. The below script demonstrates this:
from Tkinter import Button, Canvas, Tk
root = Tk()
canvas = Canvas()
canvas.grid()
drawing1 = canvas.create_oval((10,50,20,60), fill="red")
drawing2 = canvas.create_oval((30,70,40,80), fill="blue")
Button(text="Kill 1", command=lambda: canvas.delete(drawing1)).grid()
Button(text="Kill 2", command=lambda: canvas.delete(drawing2)).grid()
root.mainloop()
In addition to ALL, the delete method can also accept a specific drawing.