[英]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.