Tkinter.text - How To Calculate The Height Of A Dynamic String?
Solution 1:
I don't know of any built-in method that returns the total number of lines (including wrapped lines) in a tkinter Text
widget.
However, you can manually calculate this number by comparing the lengths of the unbroken strings in the Text widget to the Text widget's exact width (minus padding). This is what the LineCounter
class below does:
# python 2.x# from tkFont import Font# python 3.xfrom tkinter.font import Font
classLineCounter():
def__init__(self):
"""" This class can count the total number of lines (including wrapped
lines) in a tkinter Text() widget """defcount_total_nb_lines(self, textWidget):
# Get Text widget content and split it by unbroken lines
textLines = textWidget.get("1.0", "end-1c").split("\n")
# Get Text widget wrapping style
wrap = text.cget("wrap")
if wrap == "none":
returnlen(textLines)
else:
# Get Text widget font
font = Font(root, font=textWidget.cget("font"))
totalLines_count = 0
maxLineWidth_px = textWidget.winfo_width() - 2*text.cget("padx") - 1for line in textLines:
totalLines_count += self.count_nb_wrapped_lines_in_string(line,
maxLineWidth_px, font, wrap)
return totalLines_count
defcount_nb_wrapped_lines_in_string(self, string, maxLineWidth_px, font, wrap):
wrappedLines_count = 1
thereAreCharsLeftForWrapping = font.measure(string) >= maxLineWidth_px
while thereAreCharsLeftForWrapping:
wrappedLines_count += 1if wrap == "char":
string = self.remove_wrapped_chars_from_string(string,
maxLineWidth_px, font)
else:
string = self.remove_wrapped_words_from_string(string,
maxLineWidth_px, font)
thereAreCharsLeftForWrapping = font.measure(string) >= maxLineWidth_px
return wrappedLines_count
defremove_wrapped_chars_from_string(self, string, maxLineWidth_px, font):
avgCharWidth_px = font.measure(string)/float(len(string))
nCharsToWrap = int(0.9*maxLineWidth_px/float(avgCharWidth_px))
wrapLine_isFull = font.measure(string[:nCharsToWrap]) >= maxLineWidth_px
whilenot wrapLine_isFull:
nCharsToWrap += 1
wrapLine_isFull = font.measure(string[:nCharsToWrap]) >= maxLineWidth_px
return string[nCharsToWrap-1:]
defremove_wrapped_words_from_string(self, string, maxLineWidth_px, font):
words = string.split(" ")
nWordsToWrap = 0
wrapLine_isFull = font.measure(" ".join(words[:nWordsToWrap])) >= maxLineWidth_px
whilenot wrapLine_isFull:
nWordsToWrap += 1
wrapLine_isFull = font.measure(" ".join(words[:nWordsToWrap])) >= maxLineWidth_px
if nWordsToWrap == 1:
# If there is only 1 word to wrap, this word is longer than the Text# widget width. Therefore, wrapping switches to character modereturn self.remove_wrapped_chars_from_string(string, maxLineWidth_px, font)
else:
return" ".join(words[nWordsToWrap-1:])
Example of use:
import tkinter as tk
root = tk.Tk()
text = tk.Text(root, wrap='word')text.insert("1.0", "The total number of lines in this Text widget is " +
"determined accurately, even when the text is wrapped...")
lineCounter = LineCounter()
label = tk.Label(root, text="0 lines", foreground="red")
def show_nb_of_lines(evt):
nbLines = lineCounter.count_total_nb_lines(text)
if nbLines < 2:
label.config(text="{} line".format(nbLines))
else:
label.config(text="{} lines".format(nbLines))
label.pack(side="bottom")
text.pack(side="bottom", fill="both", expand=True)
text.bind("<Configure>", show_nb_of_lines)
text.bind("<KeyRelease>", show_nb_of_lines)
root.mainloop()
In your specific case, the height of the wrapped text in your ScrolledText
can be determined in update_text()
as follows:
from tkinter.font import Font
lineCounter = LineCounter()
...
classGUI():
...
defupdate_text(self, description):
...
nbLines = lineCounter.count_total_nb_lines(self.field_description)
font = Font(font=self.field_description.cget("font"))
lineHeight = font.metrics("linespace")
text_height = nbLines * lineHeight
...
Solution 2:
You know the number of lines in your Text. And you can tell when a line is off the scrolled region when dlineinfo
returns None. So go through each line and "see" it, to make sure it's visible before you run the dlineinfo()
call on it. Then sum them all up, and that's the minimum new height you need for the lines to all appear at the current width. From the height of a line's bbox and the height of the biggest font in the line, you can determine if the line is wrapped, and if so, how many times, if you care about that. The trick is to then use paneconfig()
to modify the height of the paned window. Even if the child window would resize automatically normally, the paned window will not. It must be told to resize through the paneconfig()
call.
If you "see" each line before measuring, you'll get all the measurements. And "seeing" each line shouldn't be a big deal since you intend to show them all at the end anyway.
Post a Comment for "Tkinter.text - How To Calculate The Height Of A Dynamic String?"