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