2017-08-02 27 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ウィジェット。

ただし、テキストウィジェット内の途切れていない文字列の長さとテキストウィジェットの正確な幅(パディングなし)の長さを手動で比較することができます。使用

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:]) 

例:これはLineCounterクラスは以下の何である

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 
     ... 
関連する問題