Search code examples
pythontkintertkinter-text

Is there a way to display text at one level in 3 separated tk.Text boxes?


I make my program with tkinter. Program stores some information in array([[aaa, bbb, ccc],[ddd, eee, fff]]) and I want to display it in 3 text boxes. The first box will contain aaa and ddd, other 2 will contain data in same way. But I don't know how to display it in same level, because if one of the variables contains long text, then it will be displayed in several lines, while variables from other columns will be displayed under each other without empty lines to maintain the same level.

How it works

As you can see, the text "cccc" is opposite the text "bbbbb" when it needs to be spaced across blank lines to maintain the same level. I tried to add blank lines to the first and second columns, counting the number of displayed lines in the third column, but couldn't figure out how to do it. Here some code of text boxes and displaying the data.

for item in array:
    entry_ip.insert(tk.END, item[0] + "\n")
    entry_id.insert(tk.END, item[1] + "\n")
    entry_comment.insert(tk.END, item[2] + "\n")
entry_comment = Text(
    bd=0,
    bg="#FFFFFF",
    fg="#000716",
    highlightthickness=0
)
entry_comment.place(
    x=564.0,
    y=167.0,
    width=198.0,
    height=432.0
)

entry_ip = Text(
    bd=0,
    bg="#FFFFFF",
    fg="#000716",
    highlightthickness=0
)
entry_ip.place(
    x=207.0,
    y=167.0,
    width=177.0,
    height=432.0
)

entry_id = Text(
    bd=0,
    bg="#FFFFFF",
    fg="#000716",
    highlightthickness=0
)
entry_id.place(
    x=386.0,
    y=167.0,
    width=176.0,
    height=432.0
)

Solution

  • There's no simple solution when using a text widget, for the very reasons you include in your post. I think a simpler solution would be to use a grid of labels. The only real trick is that you need to add a binding that sets the wraplength attribute of each label so that text on the label wraps when it is too long to fit.

    Here's an example of creating a small grid of labels:

    import tkinter as tk
    
    data = (
        ("aaa", "bbb", "ccc"),
        ("ddd", "eee", "an example of a long value that wraps"),
        ("ggg", "hhh", "iii"),
    )
    
    class LabelGrid(tk.Frame):
        def __init__(self, parent, data):
            super().__init__(parent)
    
            # this assumes only the third column should grow or shrink
            # when the containing frame grows or shrinks.
            self.grid_columnconfigure(2, weight=1)
    
            for row_number, row_data in enumerate(data):
                for column_number, column_data in enumerate(row_data):
                    label = tk.Label(self, width=10, text=column_data, justify="left", anchor="nw")
                    label.grid(row=row_number, column=column_number, sticky="nsew")
                    label.bind("<Configure>", self._reset_wraplength)
    
        def _reset_wraplength(self, event):
            event.widget.configure(wraplength = event.widget.winfo_width()-2)
    
    root = tk.Tk()
    lg = LabelGrid(root, data)
    lg.pack(fill="both", expand=True, padx=2, pady=2)
    
    root.mainloop()
    

    screenshot narrow window

    If you need to be able to scroll the list, you can put this frame inside a canvas since the canvas supports scrolling. The following example shows how to do that, based on a technique seen here: Adding a scrollbar to a group of widgets in Tkinter

    import tkinter as tk
    
    data = (
        ("aaa", "bbb", "ccc"),
        ("ddd", "eee", "an example of a long value that wraps"),
        ("ggg", "hhh", "iii"),
    )
    
    class LabelGrid(tk.Frame):
        def __init__(self, parent, data):
            super().__init__(parent, bg="pink")
    
            # this assumes only the third column should grow or shrink
            # when the containing frame grows or shrinks.
            self.grid_columnconfigure(2, weight=1)
    
            for row_number, row_data in enumerate(data):
                for column_number, column_data in enumerate(row_data):
                    label = tk.Label(self, width=10, text=column_data, justify="left", anchor="nw")
                    label.grid(row=row_number, column=column_number, sticky="nsew")
                    label.bind("<Configure>", self._reset_wraplength)
    
        def _reset_wraplength(self, event):
            event.widget.configure(wraplength = event.widget.winfo_width()-2)
    
    
    class ScrollableFrame(tk.Frame):
        def __init__(self, parent):
    
            tk.Frame.__init__(self, parent, bg="bisque")
            self.canvas = tk.Canvas(self, borderwidth=0)
            self.frame = None
            self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
            self.canvas.configure(yscrollcommand=self.vsb.set)
    
            self.vsb.pack(side="right", fill="y")
            self.canvas.pack(side="left", fill="both", expand=True)
    
            self.canvas.create_window((4,4), window=None, anchor="nw",tags=("inner_frame",))
            self.bind("<Configure>", self._reset_width)
    
        def set_frame(self, frame):
            self.frame = frame
            self.canvas.itemconfigure("inner_frame", window=frame)
    
            self.frame.lift(self.canvas)
            self.frame.bind("<Configure>", self._reset_scrollregion)
    
        def _reset_width(self, event):
            self.canvas.itemconfigure("inner_frame", width=event.width-20)
    
        def _reset_scrollregion(self, event):
            '''Reset the scroll region to encompass the inner frame'''
            self.canvas.configure(scrollregion=self.canvas.bbox("all"))
    
    
    root = tk.Tk()
    sf = ScrollableFrame(root)
    sf.pack(fill="both", expand=True)
    
    lg = LabelGrid(sf, data)
    sf.set_frame(lg)
    
    root.mainloop()