Collapse / expand text in Text widget
<p>I have a Text widget with ALOT of information being printed to it. Id like to have sections of it minimizable so I can hide information until I need it. </p>

<pre><code>from tkinter import *

main = Tk()
list1 = ['blah', 'blah', 'blah', 'blah', 'blah', 'blah', "\n" 'blah', 'blah', 'blah', 'blah', 'blah', 'blah', "\n", 'blah', 'blah', 'blah', 'blah', 'blah', 'blah', "\n",'blah', 'blah', 'blah', 'blah', 'blah', 'blah' , "\n"]
list2 = ['blah', 'blah', 'blah', 'blah', 'blah', 'blah', "\n" 'blah', 'blah', 'blah', 'blah', 'blah', 'blah', "\n", 'blah', 'blah', 'blah', 'blah', 'blah', 'blah', "\n",'blah', 'blah', 'blah', 'blah', 'blah', 'blah' , "\n"]
list3 = ['blah', 'blah', 'blah', 'blah', 'blah', 'blah', "\n" 'blah', 'blah', 'blah', 'blah', 'blah', 'blah', "\n", 'blah', 'blah', 'blah', 'blah', 'blah', 'blah', "\n",'blah', 'blah', 'blah', 'blah', 'blah', 'blah' , "\n"]

textwin = Text(main, height=20, width=150)
textwin.grid(rowspan=5, columnspan=5)
textwin.config(state='normal')

textwin.insert('end', "+--------------------------------------------------------------------------------- \n")
textwin.insert('end', list1)
textwin.insert('end', "+--------------------------------------------------------------------------------- \n")
textwin.insert('end', list2)
textwin.insert('end', "+--------------------------------------------------------------------------------- \n")
textwin.insert('end', list3)


main.mainloop()
</code></pre>

<p>I tried placing a button in the Text widget but it shrinks the Text widget to the size of the button and nothing else is displayed. I looked into linking text so I could make the "+" run a function that would delete/hide the section of text but I only found hyperlink instructions. </p>

<p>If this is not doable in the Text widget i'm open to other ideas. Maybe i'll have to move everything to a tree view widget. </p>

<p>You can configure a text tag to hide a range of characters. You can then hide or show a range of characters by applying or removing this tag. If you want to insert a button into the text, you will need to use the <code>window_create</code> method of the text widget.</p>

<p>You'll have to write the code to apply or remove the tag. For example, you could tag the start of a block with "block_start" or "heading" or something like that, then add a double-click binding on that tag to find all of the code between it and the next heading, and then either add or remove the "hidden" tag. Or, you could embed a button widget that does the same thing.</p>

<p><strong>To configure the tag:</strong></p>

<pre><code>textwin.tag_configure("hidden", elide=True)
</code></pre>

<p><strong>To hide a block of text</strong>, where <code>start</code> and <code>end</code> represent the range of characters to hide:</p>

<pre><code>textwin.tag_add("hidden", start, end)
</code></pre>

<p><strong>To show a block of text</strong>, where <code>start</code> and <code>end</code> represent the range of characters to show:</p>

<pre><code>textwin.tag_remove("hidden", start, end)
</code></pre>

<h2>Example</h2>

<p>The following code is a contrived example that lets you double-click a header to hide or show the text under the header:</p>

<pre><code>import tkinter as tk
from tkinter import font

class Example(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent, borderwidth=1, relief="sunken")

        self.text = tk.Text(self, borderwidth=0, highlightthickness=0, wrap="word")
        self.vsb = tk.Scrollbar(self, command=self.text.yview)
        self.text.configure(yscrollcommand=self.vsb.set)

        self.vsb.pack(side="right", fill="y")
        self.text.pack(side="left", fill="both", expand=True)

        self.text.tag_configure("hidden", elide=True)
        self.text.tag_configure("header", background="black", foreground="white", spacing1=10, spacing3=10)
        self.text.tag_bind("header", "&lt;Double-1&gt;", self._toggle_visibility)

        for i in range(3):
            self.text.insert("end", "Header %s\n" % i, "header")
            self.text.insert("end", "blah blah blah blah\nblah blah blah\n\n")

    def _toggle_visibility(self, event):
        block_start, block_end = self._get_block("insert")
        # is any of the text tagged with "hidden"? If so, show it
        next_hidden = self.text.tag_nextrange("hidden", block_start, block_end)
        if next_hidden:
            self.text.tag_remove("hidden", block_start, block_end)
        else:
            self.text.tag_add("hidden", block_start, block_end)


    def _get_block(self, index):
        '''return indicies after header, to next header or EOF'''
        start = self.text.index("%s lineend+1c" % index)
        next_header = self.text.tag_nextrange("header", start)
        if next_header:
            end = next_header[0]
        else:
            end = self.text.index("end-1c")
        return (start, end)

if __name__ == "__main__":
    root = tk.Tk()
    Example(root).pack(fill="both", expand=True)
    root.mainloop()
</code></pre>

python python-3.x tkinter