Need help Dynamically resizing my text to fit in the tkinter canvas - python

I was working on a small python script lately when I came across this problem. I tried to create a canvas in a strip shape and write text into it expecting that the text would auto adjust itself to the boundaries of canvas(similar to how a text box works in word processing software). But the text is apparently going out of boundaries.
ScreenShot
Code
from tkinter import *
top = Tk()
top.geometry("130x370")
c = Canvas(top,bg = "pink",height = "370")
c.create_text(30,30,fill="darkblue",font="Times 20 italic bold",text="Hey There!")
c.pack()
top.mainloop()

Firstly, the .create_text() method of the Canvas has a width option which sets the maximum width of the text beyond which it is wrapped. To get a dynamical effect when resizing the window, this width option can be changed in a function bound to the <Configure> event (the resize() function in the example below).
Secondly, to check that the text fits vertically in the canvas, I use the .bbox(item_id) method of the Canvas to get the coordinates of the bounding box of the text. Then, I decrement the fontsize as long as the bottom of the text is lower than the bottom of the canvas.
Here is the example:
import tkinter as tk
top = tk.Tk()
top.geometry("130x370")
def resize(event):
font = "Times %i italic bold"
fontsize = 20
x0 = c.bbox(text_id)[0] # x-coordinate of the left side of the text
c.itemconfigure(text_id, width=c.winfo_width() - x0, font=font % fontsize)
# shrink to fit
height = c.winfo_height() # canvas height
y1 = c.bbox(text_id)[3] # y-coordinate of the bottom of the text
while y1 > height and fontsize > 1:
fontsize -= 1
c.itemconfigure(text_id, font=font % fontsize)
y1 = c.bbox(text_id)[3]
c = tk.Canvas(top, bg="pink", height="370")
text_id = c.create_text(30, 30, anchor="nw", fill="darkblue", font="Times 20 italic bold", text="Hey There!")
c.pack(fill="both", expand=True)
c.bind("<Configure>", resize)
top.mainloop()
Also note that I set the anchor of the text to north west in .create_text() so that (30, 30) are the coordinates of the top-left corner of the text and not of the center to ensure that the start of the text is visible.

Related

Double Underline text Tkinter

please I need help using double underlines for a text in tkinter.
Sample code is:
allbl=tk.Label(hmpage,text='Purchases', font='calibri 25 bold doubleunderline',fg='white',bg='#2a475e')
allbl.place(relx=0.45,rely=0.25,anchor="center")
There is no font option for double underlining, therefore this cannot be done with a simple Label widget. However, one can create a custom class based on a Canvas to draw two lines below the text:
import tkinter as tk
from tkinter.font import Font
class DblUnderlineLabel(tk.Canvas):
def __init__(self, master, **kw):
# collect text's properties
font = Font(master, kw.pop('font', ''))
text = kw.pop('text', '')
if 'fg' in kw:
fg = kw.pop('fg')
else:
fg = kw.pop('foreground', 'black')
# initialize the canvas
tk.Canvas.__init__(self, master, **kw)
# display the text
self.create_text(2, 2, anchor='nw', font=font, text=text, fill=fg, tags='text')
h = font.metrics('linespace') # font property needed to position correctly the underlining
bbox = self.bbox('text') # get text bounding box in the canvas
w = font.actual('size')//8 # scale thickness of the underlining with fontsize
# create the double underlining
self.create_line(bbox[0], h - 1, bbox[2], h - 1, fill=fg, width=w, tags='line')
self.create_line(bbox[0], h + int(1.1*w), bbox[2], h + int(1.1*w), fill=fg, width=w, tags='line')
# resize the canvas to fit the text
bbox = self.bbox('all')
self.configure(width=bbox[2], height=bbox[3])
root = tk.Tk()
for size in [8, 16, 32, 64]:
DblUnderlineLabel(root, text="Arial %i bold" % size, font="Arial %i bold" % size).pack()
root.mainloop()
Text sample:
It seems that you are using the font attribute incorrectly.
I would say...
Instead of writing this:
font='calibri 25 bold doubleunderline'
Write this:
font=('calibri', 25, 'bold underline')
Also I didn't write doubleunderline as nothing like that exists in tkinter so you would get a straight Traceback.
So the corrected code would be:
allbl=tk.Label(hmpage,text='Purchases', font=('calibri', 25, 'bold underline'),fg='white',bg='#2a475e')
allbl.place(relx=0.45,rely=0.25,anchor="center")
After a long search, I have to terms with the sad reality as at this time of writing that tkinter doesn't support the double-underline feature.

Fitting text into a rectangle (width x by height y) with tkinter

I'm trying to make a program which will fit text into a rectangle (x by y) depending on the text, the font and the font size
Here is the code
def fit_text(screen, width, height, text, font):
measure_frame = Frame(screen) # frame
measure_frame.pack()
measure_frame.pack_forget()
measure = Label(measure_frame, font = font) # make a blank label
measure.grid(row = 0, column = 0) # put it in the frame
##########################################################
# make a certain number of lines
##########################################################
words = text.split(" ")
lines = []
num = 0
previous = 0
while num <= len(words):
measure.config(text = " ".join(words[previous:num])) # change text
line_width = measure.winfo_width() # get the width
print(line_width)
if line_width >= width: # if the line is now too long
lines.append(" ".join(words[previous:num - 1])) # add the last vsion which wasn't too long
previous = num - 1 # previous is now different
num = num + 1 # next word
lines.append(" ".join(words[previous:])) # add the rest of it
return "\n".join(lines)
from tkinter import *
window = Tk()
screen = Canvas(window)
screen.pack()
text = fit_text(screen, 200, 80, "i want to fit this text into a rectangle which is 200 pixels by 80 pixels", ("Purisa", 12))
screen.create_rectangle(100, 100, 300, 180)
screen.create_text(105, 105, text = text, font = ("Purisa", 12), anchor = "nw")
The problem with this is no matter what text is in the label the result from measure.winfo_width() is always 1. Here is where I found this from but it doesn't seem to work for me
The problem with your code is that you're using the width of a widget, but the width will be 1 until the widget is actually laid out on the screen and made visible, since the actual width depends on a number of factors that aren't present until that happens.
You don't need to put the text in a widget in order to measure it. You can pass a string to font.measure() and it will return the amount of space required to render that string in the given font.
For python 3.x you can import the Font class like this:
from tkinter.font import Font
For python 2.x you import it from the tkFont module:
from tkFont import Font
You can then create an instance of Font so that you can get information about that font:
font = Font(family="Purisa", size=18)
length = font.measure("Hello, world")
print "result:", length
You can also get the height of a line in a given font with the font.metrics() method, giving it the argument "linespace":
height = font.metrics("linespace")
The widget will not have a width until it is packed. You need to put the label into the frame, then pack it, then forget it.
I've actually stumbled across a way of doing this through trial and error
By using measure.update_idletasks() it calculates the width properly and it works! Bryan Oakley definitely has a more efficient way of doing it though but I think this method will be useful in other situations
P.S. I wouldn't mind some votes to get a nice, shiny, bronze, self-learner badge ;)

How to get the true current size of a Label?

I have a Frame in the root window (the root window size is set via .geometry()) in which I have two Label which fill the Frame. I would like to know the current size of the Label in order to adapt the font size of its text.
I tried .winfo_reqheight() but I cannot make any sense of the values which are returned. The example below exemplifies the problem I face (the three questions are in bold):
import Tkinter as tk
root = tk.Tk()
root.geometry("200x200")
# top level frame, containing both labels below. Expands to fill root
f = tk.Frame(root)
f.pack(expand=True, fill=tk.BOTH)
# the condition for the two cases mentioned in the text below
if True:
text = ""
else:
text = "hello\nhello\nhello\nhello"
# two labels, the top one's txt will be changed
l1 = tk.Label(f, text=text, font=('Arial', 40), background="green")
l1.pack(expand=True, fill=tk.BOTH)
l2 = tk.Label(f, text="hello", font=('Arial', 15), background="blue")
l2.pack(expand=True, fill=tk.BOTH)
# just in case, thank you https://stackoverflow.com/users/2225682/falsetru
for i in [f, l1, l2]:
i.update_idletasks()
print("top label: reqheight = {0} height = {1}, bottom label: reqheight = {2} height = {3}".format(
l1.winfo_reqheight(), l1.winfo_height(),
l2.winfo_reqheight(), l2.winfo_height()
))
root.mainloop()
Case 1: condition set to True (empty text string)
and the output
top label: reqheight = 66 height = 1, bottom label: reqheight = 29 height = 1
All the widgets are set to expand, so how come their total height is 66+29=95 while the window is 200 px high?
How can I get the height of the i) empty, ii) filled both ways and iii) expanded Label -- which I would keep as the reference (if the Label grows I will know that it must not exceed that reference)?
Case 2: condition is False (multi-line text string)
and
top label: reqheight = 246 height = 1, bottom label: reqheight = 29 height = 1
Why has the top Label crushed the bottom one? Is there a mechanism which says 'expand as much as you can but be vary of the other widgets?'
reqheight is the request height -- the height that the label wants to be regardless of how it is put in the window. It is the size it requests when the geometry manager asks "how much space do you need?". This value won't change based on how you use pack, place or grid, or how big the containing window is.
If you want the actual height, use winfo_height.

how to create a window of a given size?

please help fix the code
import tkinter
def makeWorkArea(parent):
WorkArea = tkinter.Frame(parent)
WorkArea.config(relief = 'sunken', width = 340, height = 170, bg = 'red')
WorkArea.pack(expand = 'yes', fill = 'both')
msg = tkinter.Label(WorkArea, text='Window menu basics')
msg.pack()
root = tkinter.Tk()
makeWorkArea(root)
root.mainloop()
the problem is that the parameters are specific dimensions packer area WorkArea, but after starting the program for some reason, a window smaller (approximately equal to the size lettering msg). whether it is possible to do so after the launch created a window of 340x170 pixels, bathed in red. and placed in the window text msg?
I believe that you want the tkinter.Tk.geometry method:
import tkinter
def makeWorkArea(parent):
WorkArea = tkinter.Frame(parent)
WorkArea.config(relief = 'sunken', width = 340, height = 170, bg = 'red')
WorkArea.pack(expand = 'yes', fill = 'both')
msg = tkinter.Label(WorkArea, text='Window menu basics')
msg.pack()
root = tkinter.Tk()
###########################
root.geometry("340x170")
###########################
makeWorkArea(root)
root.mainloop()
The line in the comment box sets the window's initial size to 340x170 pixels.
By default, the grid and pack geometry managers do what is called "geometry propagation". That's a fancy name for saying that frames and windows shrink or expand to fit their contents. This is almost always exactly what you want.
If you want to make a frame a specific size, the best solution is to turn geometry propagation off. When you do that, the widget will honor the width and height attributes and ignore the size of any child widgets.
See the last line in the following code:
def makeWorkArea(parent):
WorkArea = tkinter.Frame(parent)
WorkArea.config(relief = 'sunken', width = 340, height = 170, bg = 'red')
WorkArea.pack(expand = 'yes', fill = 'both')
WorkArea.pack_propagate(False)
Note that turning propagation off is often a code smell. It usually means you're doing something wrong. Tkinter is very good at making widgets just the right size. By turning this off, you are completely responsible for making widgets the appropriate size, which can be difficult in the face of different screen resolutions, different fonts, and when the user resizes the window.

How to center a tkinter window and retain the "fit to children" behavior?

I have a window whose content changes. Sometimes the content is larger than the window, so the window expands to fit it's children. However, when I center a window using a call to "geometry", the window no longer resizes. Below, you will find code that illustrates this.
If you comment out the delayed center() function call, you'll notice that the window expands to fit its content. If you leave it as is, the window centers, but no longer expands to fit its content.
Is it possible to center a window AND have it continue to resize to fit its content?
from Tkinter import *
import ttk
def center(root):
w = root.winfo_screenwidth()
h = root.winfo_screenheight()
rootsize = tuple(int(_) for _ in root.geometry().split('+')[0].split('x'))
x = w/2 - rootsize[0]/2
y = h/2 - rootsize[1]/2
root.geometry("%dx%d+%d+%d" % (rootsize + (x, y)))
root = Tk()
var = StringVar()
var.set('Small Text')
label = ttk.Label(root, textvariable=var)
label.grid(column=0, row=0)
# Change the text label in a couple of seconds.
def changeit():
var.set('BIG TXT - ' * 5)
root.after(2000, changeit)
# Comment out this center call and the label expands.
root.after(100, lambda: center(root))
root.mainloop()
When you call the geometry command, don't provide a width and height -- just supply the x/y values. When you give it an explicit width and height, you're telling Tk "I want the window to be exactly this size" so it turns it's auto-resize behavior off.
root.geometry("+%d+%d" % (rootsize + (x, y)))
Also, you can use winfo_width() and winfo_height() to get the actual size of the window, instead of parsing the output of the geometry method.

Categories

Resources