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.
Related
I cannot seem to get the scroll on this text to work. The text will scroll to a certain extent, but then not appear afterwards. I believe that the height of the text widget is not what I want. For instance, the image below shows only about a half of what the actual result is for 遺伝子 (which I can find out by attempting to drag from a piece of text in the middle of the frame to the bottom). The width also is not the same size of the frame I would like it to be: the number 23 was just something that appeared to work.
If I do
txt = tk.Text(self.SEARCH_RESULTS_TEXT_FRAME,width=self.SEARCH_RESULTS_FRAME_WIDTH,height=20,background='#d9d9d9',relief=RIDGE)
... the width becomes much too large. I would have thought that it would only take up the size of the frame, which is 240.
Note the SEARCH_RESULTS_TEXT_FRAME is the frame in red.
Relevant code:
#search text frame
self.SEARCH_RESULTS_FRAME_HEIGHT = 240 #240
self.SEARCH_RESULTS_FRAME_WIDTH = self.TITLE_FRAME_WIDTH - 23
self.SEARCH_RESULTS_TEXT_FRAME = Frame(self.SEARCH_RESULTS_FRAME,height=self.SEARCH_RESULTS_FRAME_HEIGHT,width=self.SEARCH_RESULTS_FRAME_WIDTH)
self.SEARCH_RESULTS_TEXT_FRAME.place(x=10,y=10,anchor = NW)
self.SEARCH_RESULTS_TEXT_FRAME.config(background ="#adadad")
def print_dict_to_frame(self,results_list):
txt = tk.Text(self.SEARCH_RESULTS_TEXT_FRAME,width=34,height=20,background='#d9d9d9',relief=RIDGE)
txt.place(x=0,y=0)
txt.tag_configure('header',justify = 'center',font=("Meiryo",12,'bold'))
txt.tag_configure('entry',font=('Meiryo',8))
for r_list in results_list:
header = r_list[0]
entry = r_list[1]
txt.insert(tk.END, "{}\n".format(header),'header')
for single_result in entry:
txt.insert(tk.END,single_result+"\n",'entry')
txt.configure(state=DISABLED)
How can I get the text widget to only take up the width of the frame and the height of the frame, allowing for scrolling?
If I do txt = tk.Text(self.SEARCH_RESULTS_TEXT_FRAME,width=self.SEARCH_RESULTS_FRAME_WIDTH,height=20,background='#d9d9d9',relief=RIDGE) ... the width becomes much too large. I would have thought that it would only take up the size of the frame, which is 240.
The width option specifies a width in the number of characters, not pixels. If you use the value 240, the width will be 240 multiplied times the width of an average character in the font you are using.
How can I get the text widget to only take up the width of the frame and the height of the frame, allowing for scrolling?
Give the text widget a width and height of 1, and then let the geometry manager (pack, place, or grid) be responsible for stretching it to fit the frame.
Since you're using place, you can use the relwidth and relheight options to make it the full size of the frame:
txt.place(x=0,y=0, anchor="nw", relwidth=1.0, relheight=1.0)
Personally, I recommend using pack or grid in almost all cases. It's much easier to create a responsive UI with them than it is with place.
I want buttons/labels in the same column will share the same width with a specific/first button/label. The problem is winfo_width() seems not to return what I want. The return value of winfo_width() is multiple times the button.
I don't want to make width fixed by a number I select. Thus, I did not find a solution to my problem.
Here is part of my code:
button_1.update_idletasks()
print(button_1.winfo_width())
new_label = Label(frame_1, bg= "#8432C7", width = 30, height = 5)
new_label.grid(row = 2, column = 0)
Since I don't have 10 reputation to post images, here is the link for the generated interface:
If you could see the above image, you should find the lower label (width = 30) is larger than the upper button (width = 157 ?).
But, according to my attempts, 157 seems not to be the width of button_1. I feel confused about what exactly winfo_width() returns here. Thus, I want to know what winfo_width() returns (why winfo_width() return 157 which should be a smaller number than 30) and how to get the exact width of the button.
I am stuck here for an hour since I just started to learn Tkinter recently.
Thanks in advance for anyone who can give me suggestions.
Fun fact: you don't have to bother with this at all.
Just pass sticky when you grid your widgets:
import tkinter as tk
root = tk.Tk()
button_1 = tk.Button(root,text="button_1")
button_1.grid(row = 1, column = 0, sticky="ew")
new_label = tk.Label(root, bg= "#8432C7", height = 5)
new_label.grid(row = 2, column = 0, sticky="ew")
root.mainloop()
Then your columns will be auto-fit and scaled to the same size.
This is because the width used in the arguments to create a button are in different units than what tkinter uses.
From the Documentation of a Button in tkinter. Width is The width of the button. If the button displays text, the size is given in text units. If the button displays an image, the size is given in pixels (or screen units). If the size is omitted, or zero, it is calculated based on the button contents. (width/Width)
You will find that if you use tkinters .place() to set a size, the size that .winfo_width() returns will be the same.
For example:
button_1.update_idletasks()
print(button_1.winfo_width())
new_label = Label(frame_1, bg= "#8432C7")
new_label.place(x=40, y=0, width=157, height=20)
You will find that the new_label will now have the same width as the button
I'm trying to do some image manipulation with the python library Pillow (fork of PIL) and am coming across a weird problem. For some reason, when I try to draw a line and draw some text at the same y coordinate, they're not matching up. The text is a bit below the line, yet I have both graphics starting at the same point. Has anyone had this problem before and/or know how to solve it? Here's the code I'm using:
image = Image.open("../path_to_image/image.jpg")
draw = ImageDraw.Draw(image)
font = ImageFont.truetype("../fonts/Arial Bold.ttf", 180)
draw.line((0,2400, 500,2400), fill="#FFF", width=1)
draw.text((0, 2400), "Test Text", font=font)
image.save(os.path.join(root, "test1.jpg"), "JPEG", quality=100)
return
I get something similar (with sizes 10 times smaller):
This is happening because the (x,y) coordinates given to ImageDraw.text() are the top left corner of the text:
PIL.ImageDraw.Draw.text(xy, text, fill=None, font=None, anchor=None)
Draws the string at the given position.
Parameters:
xy – Top left corner of the text.
text – Text to be drawn.
font – An ImageFont instance.
fill – Color to use for the text.
This is confirmed in the code: the text is turned into a bitmap and then drawn at xy.
For those with a similar problem, I ended up creating a helper function that manually adjusts the font size until font.getsize(text)[1] returns the correctly sized text. Here's a snippet:
def adjust_font_size_to_line_height(font_location, desired_point_size, text):
adjusted_points = 1
while True:
font = ImageFont.truetype(font_location, adjusted_points)
height = font.getsize(text)[1]
if height != desired_point_size:
adjusted_points += 1
else:
break
return adjusted_points
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.
I am drawing a table on reportlab canvas. While drawing, we need to pass bottom left coords of the table to the drawOn method. The height of my table is dynamic and therefore it overlaps on the elements above the table. I couldnot find any method that returns the height of a table that is to be drawn. Is there an alternate way to do that?
This is such a simple thing that is passively demonstrated but not explicitly addressed in the reportlab documentation:
t = Table(tableData, style=tStyle)
t.canv = myCanvas
w, h = t.wrap(0, 0)
The variables w and h will then store the table's width and height, respectively.