繁体   English   中英

如何使用 `tkinter` 使用 Pythin 创建多列文本布局?

[英]How to create multi-column text layout with Pythin using `tkinter`?

tkinter允许我们在 Python 中创建 GUI 应用程序。 我的问题是创建一个响应式 window :

  • 列具有固定的宽度,但具有灵活的高度。
  • 当窗口的宽度增加时,会添加更多的列。
  • 当窗口的宽度减小时,列被删除。
  • 当窗口的高度增加时,列变得更长。
  • 当窗口的高度减小时,列变得更短。

每列都有根据大小移动到其他列的文本。 例如:

  • 如果列的高度增加,则在其中显示更多文本。 第一列将从第二列获取更多文本,第二列将从第三列(或缓冲区)获取文本。

我的问题是:如何用 tkinter 达到这个效果?

没有实现这种列功能的内置小部件。 Tkinter 也没有以这种方式运行的“列”布局管理器。 因此,有必要创建一个自定义小部件。

我的解决方案是在容器Frame中创建带有Text小部件的列。

  • 您设置所需的列宽(以字符为单位),然后在 window 通过绑定到<Configure>调整大小时更新列数(参见下面示例中.resize()方法的第一部分)。 Text小部件以.grid(row=0, column=<column number>, sticky="ns")显示在单行上,并通过选项sticky="ns"适应行高。

  • 为了在不同列之间拆分内容,我使用了Text小部件的peer 功能 最左边的列是主要的Text小部件,我为其他列创建了具有相同内容的对等小部件(请参阅下面示例中的.resize()方法的第一部分)。 这样,所有列的内容都相同,但显示的部分可以独立更改。 为此,我使用.yview_moveto(<column number>/<total number of columns>)来显示每列内容的适当部分。 但是,为了在内容短于可用显示空间时使用此功能,我需要用换行符填充内容以获得漂亮的列显示(请参阅下面示例中的.resize()方法的第二部分)。

这是代码:

import tkinter as tk


class MulticolumnText(tk.Frame):
    def __init__(self, master=None, **text_kw):
        tk.Frame.__init__(self, master, class_="MulticolumnText")
        # text widget options
        self._text_kw = text_kw
        self._text_kw.setdefault("wrap", "word")
        self._text_kw.setdefault("state", tk.DISABLED)  # as far as I understood you only want to display text, not allow for user input
        self._text_kw.setdefault("width", 30)
        # create main text widget
        txt = tk.Text(self, **self._text_kw)
        txt.grid(row=0, column=0, sticky="ns")  # make the Text widget adapt to the row height 
        # disable mouse scrolling
        # Improvement idea: bind instead a custom scrolling function to sync scrolling of the columns)
        txt.bind("<4>", lambda event: "break")
        txt.bind("<5>", lambda event: "break")
        txt.bind("<MouseWheel>", lambda event: "break")
        self.columns = [txt]  # list containing the text widgets for each column
        # make row 0 expand to fill the frame vertically
        self.grid_rowconfigure(0, weight=1)
        self.bind("<Configure>", self.resize)

    def __getattr__(self, name):  # access directly the main text widget methods
        return getattr(self.columns[0], name)

    def delete(self, *args):  # like Text.delete()
        self.columns[0].configure(state=tk.NORMAL)
        self.columns[0].delete(*args)
        self.columns[0].configure(state=tk.DISABLED)

    def insert(self, *args):  # like Text.insert()
        self.columns[0].configure(state=tk.NORMAL)
        self.columns[0].insert(*args)
        self.columns[0].configure(state=tk.DISABLED)

    def resize(self, event):
        # 1. update the number of columns given the new width
        ncol = max(event.width // self.columns[0].winfo_width(), 1)
        i = len(self.columns)
        while i < ncol: # create extra columns to fill the window
            txt = tk.Text(self)
            txt.destroy()
            # make the new widget a peer widget of the leftmost column
            self.columns[0].peer_create(txt, **self._text_kw)
            txt.grid(row=0, column=i, sticky="ns")
            txt.bind("<4>", lambda event: "break")
            txt.bind("<5>", lambda event: "break")
            txt.bind("<MouseWheel>", lambda event: "break")
            self.columns.append(txt)
            i += 1

        while i > ncol:
            self.columns[-1].destroy()
            del self.columns[-1]
            i -= 1

        # 2. update the view
        index = self.search(r"[^\s]", "end", backwards=True, regexp=True)
        if index:  # remove trailling newlines
            self.delete(f"{index}+1c", "end")
        frac = 1/len(self.columns)
        # pad content with newlines to be able to nicely split the text between columns
        # otherwise the view cannot be adjusted to get the desired display
        while self.columns[0].yview()[1] > frac:
            self.insert("end", "\n")
        # adjust the view to see the relevant part of the text in each column
        for i, txt in enumerate(self.columns):
            txt.yview_moveto(i*frac)


root = tk.Tk()
im = tk.PhotoImage(width=100, height=100, master=root)
im.put(" ".join(["{ " + "#ccc "*100 + "}"]*100))
txt = MulticolumnText(root, width=20, relief="flat")
txt.pack(fill="both", expand=True)
txt.update_idletasks()
txt.tag_configure("title", justify="center", font="Arial 14 bold")
txt.insert("1.0", "Title", "title")
txt.insert("end", "\n" + "\n".join(map(str, range(20))))
txt.insert("10.0", "\n")
txt.image_create("10.0", image=im)
root.mainloop()

截屏

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM