简体   繁体   English

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

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

tkinter allows us to create GUI applications in Python. tkinter允许我们在 Python 中创建 GUI 应用程序。 My question is to create a responsive window that:我的问题是创建一个响应式 window :

  • A column has a fixed width, but a flexible height.列具有固定的宽度,但具有灵活的高度。
  • When window's width increases, more columns are added.当窗口的宽度增加时,会添加更多的列。
  • When window's width decreases, columns are removed.当窗口的宽度减小时,列被删除。
  • When window's height increases, columns become longer.当窗口的高度增加时,列变得更长。
  • When window's height decreases, columns become shorter.当窗口的高度减小时,列变得更短。

Each column has texts that moves to other columns depending on their sizes.每列都有根据大小移动到其他列的文本。 For example:例如:

  • If columns' heights increase, then more text is shown inside them.如果列的高度增加,则在其中显示更多文本。 The 1st column will be taking more texts from the 2nd column, and the 2nd column will be taking texts from the 3rd (or the buffer).第一列将从第二列获取更多文本,第二列将从第三列(或缓冲区)获取文本。

My question is: how to achieve this effect with tkinter?我的问题是:如何用 tkinter 达到这个效果?

There is no built-in widget implementing this kind of column feature.没有实现这种列功能的内置小部件。 Tkinter does not have a "column" layout manager that behaves this way either. Tkinter 也没有以这种方式运行的“列”布局管理器。 It is therefore necessary to create a custom widget.因此,有必要创建一个自定义小部件。

My solution is to create the columns with Text widgets in a container Frame .我的解决方案是在容器Frame中创建带有Text小部件的列。

  • You set the desired column width (in character) and then update the number of columns when the window is resized with a binding to <Configure> (see the first part of .resize() method in the example below).您设置所需的列宽(以字符为单位),然后在 window 通过绑定到<Configure>调整大小时更新列数(参见下面示例中.resize()方法的第一部分)。 The Text widgets are displayed with .grid(row=0, column=<column number>, sticky="ns") on a single row and adapt to the row height thanks to the option sticky="ns" . Text小部件以.grid(row=0, column=<column number>, sticky="ns")显示在单行上,并通过选项sticky="ns"适应行高。

  • To split the content between the different columns, I use the peer feature of the Text widget.为了在不同列之间拆分内容,我使用了Text小部件的peer 功能 The leftmost column is the main Text widget and I create peer widgets that have the same content for the other columns (see the first part of .resize() method in the example below).最左边的列是主要的Text小部件,我为其他列创建了具有相同内容的对等小部件(请参阅下面示例中的.resize()方法的第一部分)。 This way all the columns have the same content but the part of it which is displayed can be changed independently.这样,所有列的内容都相同,但显示的部分可以独立更改。 To do so, I use .yview_moveto(<column number>/<total number of columns>) to display the proper part of the content in each column.为此,我使用.yview_moveto(<column number>/<total number of columns>)来显示每列内容的适当部分。 However, for this to work when the content is shorter than the available display space, I need to pad the content with newlines to get a nice column display (see the second part of .resize() method in the example below).但是,为了在内容短于可用显示空间时使用此功能,我需要用换行符填充内容以获得漂亮的列显示(请参阅下面示例中的.resize()方法的第二部分)。

Here is the code:这是代码:

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.

相关问题 如何在 matplotlib 中制作多列文本注释? - How to make a multi-column text annotation in matplotlib? 使用 matplotlib 或 seaborn 的多列线图 - Multi-column lineplot using matplotlib or seaborn 如何将多列从一个文本文件替换为另一文本文件中的列? - How to replace multi-column from one text file to a column in another text file? 如何通过索引重塑多列数据框? - How to reshape a multi-column dataframe by index? 如何创建多列索引数据框以及如何为每组值绘制图形 - How to create Multi-column index dataframe & how to plot graphs for each set of values 如何创建具有相同名称的pandas DataFrame多列索引的内部级别平均值的DataFrame? - How to create a DataFrame of the mean of an inner level of a pandas DataFrame multi-column index with identical names? 如何从页面中抓取表格并使用 python 创建多列数据框? - How to scrape a table from a page and create a multi-column dataframe with python? 创建熊猫多列Dataframe的更简单方法? - Easier way to create pandas multi-column Dataframe? 连接包含数字和nan的多列文本文件 - Concatenate multi-column text files containing numbers & nan 如何将单列 QVBoxLayout 修改为多列? - How to modify single column QVBoxLayout to be multi-column?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM