2017-08-02 42 views
2

我有一個Text小部件,其中包含一個包含\n字符(多行)的自定義字符串。Tkinter.text - 如何計算動態字符串的高度?

微件置於內的垂直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

如果你把'end -1c'改成'end'對它有幫助嗎?我相信'-1c'帶走了最後一行的最後一行。 –

+0

@SierraMountainTech:沒有。問題是如果最後一行不可見,dlineinfo將返回None。 – NirMH

+0

好的。我會看到我能弄清楚的。如果將其設置爲[最小,完整和可驗證示例](https://stackoverflow.com/help/mcve),將會有所幫助。 –

回答

1

我不知道返回線的總數目(包括纏繞線)在任何內置的方法的一個tkinter Text小部件。

但是,您可以通過比較文本窗口小部件中不間斷字符串的長度與文本窗口小部件的確切寬度(減去填充)來手動計算此數字。這就是下面的LineCounter類做:用的

Screenshot

# 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 
     ...