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.
Related
I've tried to make a customized container function for my application, but when I pass the list of widgets, they does not get visible. The container is like the LabelFrame but with rounded corners. I tried a lot of things, but nothing seems to work. Please let me know what I'm doing wrong.
Here is my code and some comments for it:
parent -> this is the top level window
lbl_text -> this will be the little text like the LabelFrame has on top
widgets -> this is the list of the widgets I would like to place in my rounded frame
def rndframe(parent, lbl_text, widgets
# content_positioner and max_width is for calculate the requiered size of the canvas to be big
# enough for all the passed widgets, and to determine the coordinates for create_window function.
# It works perfectly and I get all the information I want.
content_positioner = [14]
max_width = 1
for i, item in enumerate(widgets):
content_positioner.append(content_positioner[i]+widgets[i].winfo_reqheight())
if widgets[i].winfo_reqwidth() > max_width:
max_width = widgets[i].winfo_reqwidth()
height = max(content_positioner)+10
width = max_width+10
# Here I create the canvas which will contain everything
canv = TK.Canvas(parent, height=height, width=width, bg='green', bd=0, highlightthickness=0)
# Here I draw the rounded frame
radius = 10
x1 = 2
y1 = 5
x2 = width-2
y2 = height-2
points = [x1+radius, y1, x2-radius, y1, x2, y1, x2, y1+radius, x2, y2-radius, x2, y2,
x2-radius, y2, x1+radius, y2, x1, y2, x1, y2-radius, x1, y1+radius, x1, y1]
canv.create_polygon(points, smooth=True, outline=DS.menu_bg, width=3, fill='')
# Here I put the litle label in the frmae
label = TK.Label(parent, text=lbl_text, bg=DS.main_bg, padx=3, pady=0, fg=DS.main_textcolor)
canv.create_window(18, 5, anchor='w', window=label)
# And thats the part where I would like to place all the passed widgets based on the coordinate
# list I preveously created but nothing appear just the frame polygon and the liitle label on it.
for w, widget in enumerate(widgets):
canv.create_window(width/2, content_positioner[w], anchor='n', window=widgets[w])
return canv
And finally the way I've tried to use:
id_num = TK.Label(result_area_leaf, text='123-4567')
id_num2 = TK.Label(result_area_leaf, text='123-4567')
id_holder = rndframe(result_area_leaf, lbl_text='id', widgets=[id_num, id_num2])
id_holder.grid()
There are many problems with your code, but the root of the issue is that the widgets you're adding to the canvas have a lower stacking order than the canvas so they are behind or "under" the canvas. This is because the default stacking order is initially determined by the order that the widgets are created. You create the widgets before the canvas so they are lower in the stacking order.
The simplest solution to the stacking order problem is to raise the widgets above the canvas, which you can do with the lift method.
for w, widget in enumerate(widgets):
canv.create_window(width/2, content_positioner[w], anchor='n', window=widgets[w])
widgets[w].lift()
When I make the above modification, along with fixing all of the other problems in the code, the labels appear in the canvas.
Finally I got it work. First of all sorry that I didn't posted a standalone runable code firstly. When I put the code in a file and make the necessary changes about the missing variables the suggested .lift() method worked. And the reason was that in that single file I used the root window as parent.
In my copied code the two Lables (id_num and id_num2) have a Canvas for parent (result_area_leaf) and that was the problem. If I made my root window as parent for the passed widgets, the widgets are shown. The .lift() actually does not needed after all.
Thanks guys ;)
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!
I want to make a circle move and avoid obstacles with collision detection. Here's the code I have.
from Tkinter import *
window = Tk()
canvas = Canvas(window,width=800, height=500, bg='pink')
canvas.pack()
finishline = canvas.create_oval(700, 300, 700+50, 300+50,fill='green')
robot = canvas.create_oval(20,200,20+45,200+45,fill='yellow')#(x1, y1, x2, y2)
ob1 = canvas.create_rectangle(200,400,200+50,200+1,fill='black')
canvas.update()
ob1 = canvas.create_rectangle(500,200,150+400,300+100,fill='blue')
canvas.update()
You could always use the canvas.find_overlapping method (found here: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.find_overlapping-method), and if it ever returns a value other than your own little circle, you can make the circle go in the other direction or something.
It's kind of hard to give a specific answer with such unspecific requirements.
I am talking specific to python Tkinter, I have text along with a button in-lined and I am using pixel coordinates. Now my text string is changing dynamically, but if the text string is long then it overflows.
So I want if there any way if I can change the coordinates based on text length
For example:
canvas.create_text(20, 30, anchor=W, font="Purisa",
text="Most relationships seem so transitory")
If I use something like this
canvas.create_text(20+len(text), 30, anchor=W, font="Purisa",
text="Most relationships seem so transitory")
I am very new to tkinter and got a code to debug which is very tight, so I cannot change it dynamically in first place
You can calculate the coordinates based on the size of the text, but you need to find out the size of the text in the given font in pixels. This can be done in Tkinter by first using a scratch canvas and the bbox method. Create the text item and capture the id, then use the bbox method to get its size.
scratch = Canvas()
id = scratch.create_text((0, 0), text=text, <options>)
size = scratch.bbox(id)
# size is a tuple: (x1, y1, x2, y2)
# since x1 and y1 will be 0, x2 and y2 give the string width and height
Then you can calculate your x and y coordinates based on the results and draw it on your actual canvas. There is likely also a more efficient way to do this, but I don't know of it yet.
Or maybe you just want the x position to change based on the text size, in other words, making it right justified. In Tkinter this is most easily done by using the "anchor=E" option and giving the right edge of the text area for the x coordinate:
canvas.create_text(ButtonX - 10, 30, anchor=E, ...)
You can also use "width=200" for example to wrap the text in a 200 pixel wide box, in addition to anchor and any other options.
You can pass the "width" in create_text to avoid overflow.
width=Maximum line length. Lines longer than this value are wrapped. Default is 0 (no wrapping).
so it will be something like this
canvas.create_text(20, 30, anchor=W, font="Purisa",
text="Most relationships seem so transitory", width=0)
you can calculate the width based on the text size or make it fix, then if it is longer than it will be wrapped and there won't be any overflow.
I am currently writing a fsm editor with tkinter. But, I stuck on connecting two states. I have two questions:
1) How can make the transition arrow growable according to mouse movement?
2) How can I stick the starting point of the arrow on a state and the end point of the arrow on another state?
PS. Do you think the documentation of tkinter is good enough?
Here's an example that shows the concept. In a nutshell, use tags to associate lines with boxes, and simply adjust the coordinates appropriately when the user moves the mouse.
Run the example, then click and drag from within the beige box.
Of course, for production code you need to make a more general solution, but hopefully this shows you how easy it is to create a box with arrows that adjust as you move the box around.
from Tkinter import *
class CanvasDemo(Frame):
def __init__(self, width=200, height=200):
Frame.__init__(self, root)
self.canvas = Canvas(self)
self.canvas.pack(fill="both", expand="1")
self.canvas.create_rectangle(50, 25, 150, 75, fill="bisque", tags="r1")
self.canvas.create_line(0,0, 50, 25, arrow="last", tags="to_r1")
self.canvas.bind("<B1-Motion>", self.move_box)
self.canvas.bind("<ButtonPress-1>", self.start_move)
def move_box(self, event):
deltax = event.x - self.x
deltay = event.y - self.y
self.canvas.move("r1", deltax, deltay)
coords = self.canvas.coords("to_r1")
coords[2] += deltax
coords[3] += deltay
self.canvas.coords("to_r1", *coords)
self.x = event.x
self.y = event.y
def start_move(self, event):
self.x = event.x
self.y = event.y
root = Tk()
canvas = CanvasDemo(root)
canvas.pack()
mainloop()
Tkinter is perfectly fine for this sort of application. In the past I've worked on tools that were boxes connected with arrows that stayed connected as you move the boxes around (which is what I think you are asking about). Don't let people who don't know much about Tkinter sway you -- it's a perfectly fine toolkit and the canvas is very flexible.
The solution to your problem is simple math. You merely need to compute the coordinates of the edges or corners of boxes to know where to anchor your arrows. To make it "grow" as you say, simply make a binding on mouse movements and update the coordinates appropriately.
To make the line growable all you have to do is adjust the coordinates of the line each time the mouse moves. The easiest thing to do is make liberal use of canvas tags. With the tags you can know which arrows connect to which boxes so that when you move the box you adjust the coordinates of any arrows that point to or from it.