簡體   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