我有一个Text
小部件,其中包含一个包含\n
个字符(多行)的自定义字符串。
小部件位于垂直panedwindow
内,我想调整panedwindow
的窗框以在Text
窗口小部件中显示整个字符串。
字符串本质上是动态的(这意味着,我的应用程序中的其他方法正在更新它)。
由于Text
窗口小部件配置了wrap='word'
,如何计算字符串高度(以像素为单位)来相应调整窗框?
在将字符串加载到窗口小部件后,我尝试使用text.dlineInfo('end -1c')[1] + text.dlineinfo('end -1c')[3]
(对于行的y坐标+高度)。问题是如果最后一行不可见,则dlineinfo返回none
。
我也尝试使用Font.measure
例程,但这不包括Text
小部件的换行方面。
这是一个最小的,完整的,可验证的例子:
import tkinter
from tkinter import scrolledtext
class GUI():
def __init__(self, master):
self.master = master
self.body_frame = tkinter.PanedWindow(self.master, orient='vertical', sashwidth=4)
self.body_frame.pack(expand=1, fill='both')
self.canvas_frame = tkinter.Frame(self.body_frame)
self.description_frame = tkinter.Frame(self.body_frame)
self.body_frame.add(self.canvas_frame, sticky='nsew')
self.body_frame.add(self.description_frame, sticky='nsew')
tkinter.Button(self.canvas_frame, text='Update Text', command = lambda : self.update_text("""
A very long string with new lines
A very long string with new lines
A very long string with new lines
A very long string with new lines
A very long string with new lines
A very long string with new lines
""")).pack(fill='x')
self.field_description = scrolledtext.ScrolledText(self.description_frame, width=20, wrap='word')
self.field_description.pack(expand=1, fill='both')
self.master.update()
self.body_frame.sash_place(0,0,self.body_frame.winfo_height() - 50) # force sash to be lower
def update_text(self, description):
self.field_description.delete('1.0', 'end')
self.field_description.insert('1.0', description)
height = self.body_frame.winfo_height()
lastline_index = self.field_description.index('end - 1c')
text_height = self.field_description.dlineinfo(lastline_index)[1] + \
self.field_description.dlineinfo(lastline_index)[3]
self.body_frame.sash_place(0, 0, height - text_height)
root = tkinter.Tk()
my_gui = GUI(root)
root.mainloop()
答案 0 :(得分:1)
我不知道任何内置方法可以在tkinter Text
中返回总行数( 包括换行 )窗口小部件。
但是,您可以通过将“文本”窗口小部件中未断开的字符串的长度与“文本”窗口小部件的精确宽度(减去填充)进行比较来手动计算此数字。这就是下面LineCounter
类的作用:
# python 2.x
# from tkFont import Font
# python 3.x
from tkinter.font import Font
class LineCounter():
def __init__(self):
"""" This class can count the total number of lines (including wrapped
lines) in a tkinter Text() widget """
def count_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":
return len(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") - 1
for line in textLines:
totalLines_count += self.count_nb_wrapped_lines_in_string(line,
maxLineWidth_px, font, wrap)
return totalLines_count
def count_nb_wrapped_lines_in_string(self, string, maxLineWidth_px, font, wrap):
wrappedLines_count = 1
thereAreCharsLeftForWrapping = font.measure(string) >= maxLineWidth_px
while thereAreCharsLeftForWrapping:
wrappedLines_count += 1
if 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
def remove_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
while not wrapLine_isFull:
nCharsToWrap += 1
wrapLine_isFull = font.measure(string[:nCharsToWrap]) >= maxLineWidth_px
return string[nCharsToWrap-1:]
def remove_wrapped_words_from_string(self, string, maxLineWidth_px, font):
words = string.split(" ")
nWordsToWrap = 0
wrapLine_isFull = font.measure(" ".join(words[:nWordsToWrap])) >= maxLineWidth_px
while not 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 mode
return self.remove_wrapped_chars_from_string(string, maxLineWidth_px, font)
else:
return " ".join(words[nWordsToWrap-1:])
使用示例:
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()
在您的具体情况中,ScrolledText
中包装文字的高度可以在update_text()
中确定,如下所示:
from tkinter.font import Font
lineCounter = LineCounter()
...
class GUI():
...
def update_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
...
答案 1 :(得分:0)
您知道文本中的行数。当 dlineinfo
返回 None 时,您可以判断一行何时离开滚动区域。因此,检查每一行并“查看”它,以确保在对其运行 dlineinfo()
调用之前它是可见的。然后将它们全部加起来,这就是所有线条以当前宽度显示所需的最小新高度。从一行的bbox的高度和行中最大字体的高度,您可以确定该行是否被换行,如果是,多少次,如果您关心的话。诀窍是然后使用 paneconfig()
修改窗格窗口的高度。即使子窗口会正常自动调整大小,窗格窗口也不会。必须通过 paneconfig()
调用告诉它调整大小。
如果您在测量前“看到”每一行,您将获得所有测量值。 “看到”每一行应该没什么大不了的,因为无论如何你都打算在最后展示它们。