Tkinter Canvas create_window function does not show the desired widgets - python

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

Related

How to show only a portion of the tkinter canvas by cropping the tkinter window?

I want to be able to zoom into my tkinter canvas. My tkinter canvas is 500x500px, and I only want my window to display the center 200x200px portion of this canvas. How do I do this? I know that I can just specify my window size as 200x200px using root.geometry("200x200+0+0"), but this causes my window to display the top left corner of my canvas, and not the center. Before I do anything, my entire canvas looks like this:
Ultimately, I want my window to look like this, with the canvas centered within the window:
This is my code:
import tkinter
root = tkinter.Tk()
root.title("")
root.geometry("200x200+0+0")
canvas = tkinter.Canvas(master = root, width = 500, height = 500)
canvas.create_oval(200, 200, 300, 300, outline = "black", fill = "blue")
canvas.pack()
which returns:
As you can see, the canvas is not centered, and the window is showing the upper left hand corner at the moment. Does anyone have any suggestions?
Ok, thanks to this stackoverflow post, I found out there is an option when creating a tkinter canvas called scrollregion. The format of the argument is "x0 y0 x1 y1" for anyone that is wondering, where (x0, y0) is the upper-left corner of the area of the canvas I want to show and (x1, y1) is the bottom-right corner of the same area. My code should be fixed to this:
canvas = tkinter.Canvas(master = root, width = 500, height = 500, scrollregion = "150 150 350 350")
Be wary that these coordinates do not account for a scrollbar...I'm still working on figuring that out. Much thanks to this stackoverflow post as well, specifically the following words:
I don't see any difference between putting the y-scrollbar to the bottom or putting the canvas view to the bottom because the two are linked.

How can I draw a point with Canvas in Tkinter?

I want to draw a point in Tkinter,Now I'm using Canvas to make it,but I didn't find such method to draw a point in Canvas class.Canvas provides a method called crete_line(x1,y1,x2,y2),so i tried to set x1=x2,y1=y2 to draw a point, but it doesn't work.
So anyone can tell me how to make it,it will be better if use Canvas can make it,other solution will be also accepted.Thanks!
There is no method to directly put a point on Canvas. The method below shows points using create_oval method.
Try this:
from Tkinter import *
canvas_width = 500
canvas_height = 150
def paint(event):
python_green = "#476042"
x1, y1 = (event.x - 1), (event.y - 1)
x2, y2 = (event.x + 1), (event.y + 1)
w.create_oval(x1, y1, x2, y2, fill=python_green)
master = Tk()
master.title("Points")
w = Canvas(master,
width=canvas_width,
height=canvas_height)
w.pack(expand=YES, fill=BOTH)
w.bind("<B1-Motion>", paint)
message = Label(master, text="Press and Drag the mouse to draw")
message.pack(side=BOTTOM)
mainloop()
The provided above solution doesn't seem to work for me when I'm trying to put a sequence of a few pixels in a row.
I've found another solution -- reducing the border width of the oval to 0:
canvas.create_oval(x, y, x, y, width = 0, fill = 'white')
With create_line you have other possible workaround solution:
canvas.create_line(x, y, x+1, y, fill="#ff0000")
It overwrites only a single pixel (x,y to red)
Since an update of the Tk lib (somewhere between Tk 8.6.0 and 8.6.9) the behavior of create_line had changed.
To create a one pixel dot at (x, y) in 8.6.0 you add to write
canvas.create_line(x, y, x+1, y, fill=color)
Now on 8.6.9 you have to use:
canvas.create_line(x, y, x, y, fill=color)
Note that Debian 9 use 8.6.0 and Archlinux (in early 2019) use 8.6.9 so portability is compromised for a few years.

How to get the size of a tkinter Button?

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.

How do I selectively delete drawings in Tkinter?

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.

Dynamic sizing Tkinter canvas text

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.

Categories

Resources