Tkinter调整文本大小以适应内容

8

是否可以使Tkinter文本小部件调整大小以适应其内容?

例如:如果我放置一行文本,它会缩小,但如果我放置5行文本,它会增长。

4个回答

8
我能想到的唯一解决办法是在用户输入文本到文本窗口时每次计算宽度和高度,然后将窗口大小设置为该值。但此方法的限制是只有等宽字体才能正常工作,以下是示例代码:
import Tkinter

class TkExample(Tkinter.Frame):
   def __init__(self, parent):
      Tkinter.Frame.__init__(self, parent)
      self.init_ui()

   def init_ui(self):
      self.pack()
      text_box = Tkinter.Text(self)
      text_box.pack()
      text_box.bind("<Key>", self.update_size)

   def update_size(self, event):
      widget_width = 0
      widget_height = float(event.widget.index(Tkinter.END))
      for line in event.widget.get("1.0", Tkinter.END).split("\n"):
         if len(line) > widget_width:
            widget_width = len(line)+1
      event.widget.config(width=widget_width, height=widget_height)

if __name__ == '__main__':
    root = Tkinter.Tk()
    TkExample(root)
    root.mainloop()

1
是的,这正是我想到的。希望我错过了某个方法。啊,好吧,我会活下去的。 - SquidsEnMasse
3
您可以使用font_measure来获取变宽字体中文本行的实际宽度。另外,这会受到绑定在文本插入之前触发的影响。您需要绑定在<KeyRelease>上或调整绑定标签,以便您的绑定在类绑定之后发生。 - Bryan Oakley
代码相关内容的翻译如下:lines = event.widget.get("1.0", Tkinter.END).split("\n"); widget_height = max(imap(len, lines)+1 - ealfonso
选项卡怎么办?根据我的经验,在默认的单色字体中,Tkinter.Text 使用的选项卡长度长达8个字符(在我的OS X 10.10上)。 - ArtOfWarfare

6

在谷歌搜索中找到了这篇文章,希望需要的人能够找到。经过数小时的搜索,我仍然没有找到答案,所以我想出了这个技巧。

我想要一个弹出窗口,可以正确地自适应 Text widget 中任何未知但预定的文本内容,而不是用户输入。同时,Text widget 需要正确地自适应其文本内容。

tkinter.Label 很好用,但它没有 tkinter.Text.tag_configuretkinter.Text.tag_bind,我需要用这些方法来替换一些 HTML 标签为 tkinter 的丰富文本标签。tkinter.Text 有丰富的文本标签,但不易扩展;而tkinter.Label 可以很好地扩展,但没有丰富的文本标签。此外,我真的不喜欢滚动条和自动换行,除非它们真的有必要。这正好满足我的需求。虽然这只是一个简单的、工作在论坛上的摘要。适用于任何字体。仅在 Python 3.3 和 Ubuntu 13.10(Linux)中进行了测试。

#!/usr/bin/env python3

import tkinter as tk

class MyFrame(tk.Frame):
    def __init__(self):
        tk.Frame.__init__(self)

        root = self.master
        root.title("My Window Title")

        # Pack Frame into root window and make it expand in "both" x and y
        self.pack(side="top", fill="both", expand=True, padx=10, pady=10)
        # Statistical weight of 1 = 100% for cell (0, 0) to expand 100%
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)

        # The string text
        text = """Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed
diam nonummy nibh euismod tincidunt ut laoreet dolore magna
aliquam erat volutpat. Ut wisi enim ad minim veniam, quis
nostrud exerci tation ullamcorper suscipit lobortis nisl ut
aliquip ex ea commodo consequat. Duis autem vel eum iriure
dolor in hendrerit in vulputate velit esse molestie consequat,
vel illum dolore eu feugiat nulla facilisis at vero eros et
accumsan et iusto odio dignissim qui blandit praesent luptatum
zzril delenit augue duis dolore te feugait nulla facilisi. Nam
liber tempor cum soluta nobis eleifend option congue nihil
imperdiet doming id quod mazim placerat facer possim assum.
Typi non habent claritatem insitam; est usus legentis in iis qui
facit eorum claritatem. Investigationes demonstraverunt lectores
legere me lius quod ii legunt saepius. Claritas est etiam
processus dynamicus, qui sequitur mutationem consuetudium
lectorum. Mirum est notare quam littera gothica, quam nunc
putamus parum claram, anteposuerit litterarum formas
humanitatis per seacula quarta decima et quinta decima. Eodem
modo typi, qui nunc nobis videntur parum clari, fiant sollemnes
in futurum."""

        # Add a tk.Text widget to Frame (self) and its configuration
        textwidget = tk.Text(self, wrap="none", font=("Comic Sans MS", 12),
                             padx=10, pady=10)
        textwidget.grid(row=0, column=0, sticky="nesw")
        # Add the text to textwidget and disable editing
        textwidget.insert(tk.END, text)
        textwidget.config(state=tk.DISABLED)

        # Here is where the HACK begins
        def is_scroll(wh, lower, upper):
            nonlocal size
            size[wh][0] = upper < '1.0' or lower > '0.0'
            size[wh][1] += 20 * size[wh][0] # += 1 for accuracy but slower
        # Call the is_scroll function when textwidget scrolls
        textwidget.config(xscrollcommand=lambda *args: is_scroll('w', *args),
                          yscrollcommand=lambda *args: is_scroll('h', *args))

        # Add a tk.Button to the Frame (self) and its configuration
        tk.Button(self, text="OK", command=self.quit).grid(row=1, column=0,
                                                           sticky="we")

        # For reasons of magic, hide root window NOW before updating
        root.withdraw()

        # Initially, make root window a minimum of 50 x 50 just for kicks
        root.geometry('50x50')
        size = {'w': [False, 50], 'h': [False, 50]}
        # Update to trigger the is_scroll function
        root.update()
        while size['w'][0] or size['h'][0]:
            # If here, we need to update the size of the root window
            root.geometry('{}x{}'.format(size['w'][1], size['h'][1]))
            root.update()

        # Center root window on mouse pointer
        x, y = root.winfo_pointerxy()
        root.geometry('+{}+{}'.format(x-size['w'][1]//2, y-size['h'][1]//2))

        # Now reveal the root window in all its glory
        root.deiconify()

        # Print textwidget dimensions to the console
        print(textwidget.winfo_width(), textwidget.winfo_height())

def main():
    """Show main window."""
    MyFrame().mainloop()

if __name__ == '__main__':
    main()
说明:关键在于不要试图直接扩展或收缩文本小部件,这是徒劳无功的。答案有点违反直觉,因为人们的第一个想法是直接对该文本小部件进行操作。相反,扩展根(最外层)窗口(在本例中为self.master),并且只需让文本小部件保持原样。简单易行。
将文本小部件粘贴到以 100% 打包以扩展根窗口的框架中。随着根窗口的扩展,框架和其中的文本小部件也会扩展。但是,在扩展根窗口时,请测试文本小部件的 xscrollcommandyscrollcommandlowerupper 边界是否已消失(没有更多滚动)。这些命令将lowerupper参数作为百分位数发送到回调函数,需要滚动条,通常是tkinter.Scrollbar.set。然而,我们使用这些命令,因为我们不想要滚动条或任何滚动。我们想要完美贴合。
如果 lowerupper 边界消失(lower ≤ 0.0 且 upper ≥ 1.0),那意味着我们的文本小部件周围有一个完美贴合的窗口,它也完美地围绕其文本内容贴合。大功告成!
添加了一个按钮以演示即使添加其他小部件仍能正确工作。删除一些文本以查看它仍然完美贴合。

1
编辑:简短的方法:
text.pack(side="top", fill="both", expand=True, padx=0, pady=0)

通过重新使用sc0tt的答案和Bryan Oakley在这里的答案Get of number of lines of a Text tkinter widget,我们可以得到这个准备好使用的代码(为了将来参考而发布),它也适用于比例字体

import Tkinter as Tk
import tkFont

class Texte(Tk.Text):
    def __init__(self, event=None, x=None, y=None, size=None, txt=None, *args, **kwargs):
        Tk.Text.__init__(self, master=root, *args, **kwargs)
        self.font = tkFont.Font(family="Helvetica Neue LT Com 55 Roman",size=35)
        self.place(x=10,y=10)
        self.insert(Tk.INSERT,' blah ')
        self.config(font=self.font)
        self.update_size(event=None)
        bindtags = list(self.bindtags())
        bindtags.insert(2, "custom")
        self.bindtags(tuple(bindtags))
        self.bind_class("custom", "<Key>", self.update_size)

    def update_size(self, event):
        width=0
        lines=0
        for line in self.get("1.0", "end-1c").split("\n"):
            width=max(width,self.font.measure(line))
            lines += 1
        self.config(height=lines)
        self.place(width=width+10)

root = Tk.Tk()
root.geometry("500x500")
Texte()
root.mainloop()

6
你从我的另一个问题的答案中复制了一半的代码。请注明其他问题的引用或链接,这样会更好。不过,你可能需要在你的回答中提到这个方法只适用于等宽字体。如果要让它适用于比例字体,你需要实际测量每行的宽度和高度。 - Bryan Oakley
当然,我现在会添加引用。顺便说一下,我并不是为了点赞或类似的东西而在这里发帖(我已经提到我重新使用了sc0tt的被接受答案,所以我从一开始就声明我个人没有发明任何东西)。我只是在这里作为参考,如果有人以后需要重复使用。 - Basj
哦,@BryanOakley,我没有注意到它只适用于固定宽度...太遗憾了。你有什么想法可以使其适应比例字体吗? - Basj
在这段代码中,您正在使用place,而place允许您以像素为单位设置小部件的宽度和高度。 - Bryan Oakley
已更新答案,现在可以适用于比例字体。 - Basj
显示剩余5条评论

0

在sc0tt的帖子基础上,如果您不使用换行符(例如,只使用固定宽度并将高度作为唯一可扩展变量),则可以使用的辅助函数:

def update_height(event):
    text_height = (str(event.widget.index('1.end')) )
    text_int = int(re.search(".(\d+)", text_height).group(1))
    widget_height = int(int(text_int)/160) + 1
    event.widget.config(height=widget_height)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接