如何使用“ tkinter”使用pythin创建多列文本布局?

发布于 2025-02-10 03:06:24 字数 443 浏览 1 评论 0原文

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

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

每列的文本根据其大小而移至其他列。例如:

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

我的问题是:如何使用TKINTER实现此效果?

tkinter allows us to create GUI applications in Python. My question is to create a responsive window that:

  • 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

星光不落少年眉 2025-02-17 03:06:24

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

我的解决方案是用text widgets在容器frame中创建列。

  • 您设置所需的列宽度(以字符为单位),然后在窗口大小时更新列的列数,并绑定到< configure>(请参阅的第一部分。在下面的示例中resize()方法。 text.grid(row = 0,column =< lt; column>,sticky =“ ns”)在单行上显示并适应该行高度感谢选项sticky =“ ns”

  • 要在不同列之间拆分内容,我使用 text小部件的功能。最左列是主text小部件,我创建了对其他列具有相同内容的对等小部件(请参阅示例中的.resize()方法的第一部分以下)。这样,所有列的内容都具有相同的内容,但是显示的部分可以独立更改。为此,我使用.yview_moveto(<列号>/<)列的总数>)在每列中显示内容的适当部分。但是,为了使内容短于可用的显示空间时,我需要使用newlines添加内容以获取一个不错的列显示(请参阅.resize> .resize()方法的第二部分 in下面的示例)。

这是代码:

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()

“屏幕截图”

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. It is therefore necessary to create a custom widget.

My solution is to create the columns with Text widgets in a container Frame.

  • 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). 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".

  • To split the content between the different columns, I use the peer feature of the Text widget. 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). 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. 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).

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()

screenshot

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文