Tkinter Resize text to contents

后端 未结 4 792
长发绾君心
长发绾君心 2020-12-16 06:27

Is it possible to have a Tkinter text widget resize to fit its contents?

ie: if I put 1 line of text it will shrink, but if I put 5 lines it will grow

相关标签:
4条回答
  • 2020-12-16 06:35

    Found this thread at the top of a Google search and, therefore, maybe someone who needs this will find it.  Couldn't find the answer even after hours of searching.  So here's the HACK I came up with.

    I wanted a popup window that form-fits itself correctly around any unknown, yet predetermined text in a Text widget, rather than user input.  Also, the Text widget needs to form-fit itself correctly around its text content.

    A tkinter.Label works great, but it doesn't have tkinter.Text.tag_configure, and tkinter.Text.tag_bind which I needed to replace some HTML tags with tkinter's rich text tags.  tkinter.Text has rich text tags, but does not expand nicely, while tkinter.Label expands nicely but does not have rich text tags.  Moreover, I just hate scrollbars and word wrap, unless they're REALLY needed.  This does exactly what I wanted it to.  Although, this is just a very simple, working abstract for this forum.  Works with any font.  Only tested with Python 3.3 in 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()
    

    Explanation:  The TRICK is to NOT even bother with the futility of trying to expand or shrink the Text widget directly.  The answer is a bit counter-intuitive, because one's FIRST thought is to go straight for that Text widget and do something to it.  Instead, expand the root (outermost) window (in this case, self.master), and just leave the Text widget alone.  Easy peasy.

    Sticky ("nesw") the Text widget to the Frame which is packed for 100% expansion in the root window.  As the root window expands, so will the Frame and the Text widget inside it.  However, as you're expanding the root window, TEST if the lower and upper bounds have vanished for the Text widget's xscrollcommand, and yscrollcommand (no more scrolling).  Those commands send lower and upper arguments as percentiles to a callback function needed for scrollbars, usually tkinter.Scrollbar.set.  However, we're using those commands because we do NOT WANT scrollbars or any scrolling at all.  We want a PERFECT FIT.

    If the lower and upper bounds have vanished (lower <= 0.0 and upper >= 1.0), that means we have a perfectly fitted window around our Text widget which is also perfectly fitted around its text content.  TADA!

    Added a button to demonstrate it still works correctly even with other widgets added.  Remove some text to see it still form-fits perfectly.

    0 讨论(0)
  • 2020-12-16 06:41

    The only way I can think of accomplishing this is to calculate the width and height every time the user enters text into the Text widget and then set the size of the widget to that. But the limitation here is that only mono-spaced fonts will work correctly, but here it is anyway:

    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()
    
    0 讨论(0)
  • 2020-12-16 06:49

    Building on sc0tt's post, a helper function that works well if you are not using line breaks (e.g. just use a fixed width and make the height the only expanding variable):

    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)
    
    0 讨论(0)
  • 2020-12-16 06:55

    Edit: short method:

    text.pack(side="top", fill="both", expand=True, padx=0, pady=0)
    

    By re-using sc0tt's answer, and Bryan Oakley's answer here Get of number of lines of a Text tkinter widget, we can have this ready-to-use code (posted here for future reference) that also works for proprtional fonts :

    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()
    
    0 讨论(0)
提交回复
热议问题