为什么我的小部件重叠?

Why is my widget being overlapped?

我正在使用 Bryan Oakley 在 Tkinter adding line number to text widget 的代码来创建带有行号的 Text 小部件。但我想创建一个自定义小部件,我可以像 Text 小部件一样使用它,但可以使用可选的行号。喜欢:

t = LinedText(top)
t.insert("insert", "Hello")
t.show()

但截至目前,当我显示行号时,它会覆盖文本小部件。 window 会自动调整大小。为什么会这样?我的代码:

import tkinter as tk


class TextLineNumbers(tk.Canvas):

    def __init__(self, *args, **kwargs):
        tk.Canvas.__init__(self, *args, **kwargs)
        self.textwidget = None

    def attach(self, text_widget):
        self.textwidget = text_widget

    def redraw(self, *args):
        '''redraw line numbers'''
        self.delete("all")

        i = self.textwidget.index("@0,0")
        while True :
            dline= self.textwidget.dlineinfo(i)
            if dline is None: break
            y = dline[1]
            linenum = str(i).split(".")[0]
            self.create_text(2,y,anchor="nw", text=linenum)
            i = self.textwidget.index("%s+1line" % i)


class CustomText(tk.Text):
    def __init__(self, *args, **kwargs):
        tk.Text.__init__(self, *args, **kwargs)

        self.tk.eval('''
            proc widget_proxy {widget widget_command args} {

                # call the real tk widget command with the real args
                set result [uplevel [linsert $args 0 $widget_command]]

                # generate the event for certain types of commands
                if {([lindex $args 0] in {insert replace delete}) ||
                    ([lrange $args 0 2] == {mark set insert}) || 
                    ([lrange $args 0 1] == {xview moveto}) ||
                    ([lrange $args 0 1] == {xview scroll}) ||
                    ([lrange $args 0 1] == {yview moveto}) ||
                    ([lrange $args 0 1] == {yview scroll})} {

                    event generate  $widget <<Change>> -when tail
                }

                # return the result from the real widget command
                return $result
            }
            ''')
        self.tk.eval('''
            rename {widget} _{widget}
            interp alias {{}} ::{widget} {{}} widget_proxy {widget} _{widget}
        '''.format(widget=str(self)))


class LinedText(CustomText):

    def __init__(self, *args, **kwargs):
        CustomText.__init__(self, *args, **kwargs)

        self.settings = self.Settings()
        self.linenumbers = None

        self.text = super()      

        self.vsb = tk.Scrollbar(orient="vertical", command=self.yview)
        self.vsb.pack(side="right", fill="y")

        self.text.configure(yscrollcommand=self.vsb.set)
        self.text.tag_configure("bigfont", font=("Helvetica", "24", "bold"))
        self.text.pack(side="left", fill="both", expand=True)
        self.text.bind("<<Change>>", self._on_change)
        self.text.bind("<Configure>", self._on_change)
        self.text.insert("end", "one\ntwo\nthree\n")
        self.text.insert("end", "four\n",("bigfont",))
        self.text.insert("end", "five\n")
        self.text.pack(side="left")

    def hide(self,event=None):
        if not self.settings.hide_linenumbers:
            self.settings.hide_linenumbers = True
            self.linenumbers.pack_forget()

    def show(self,event=None):
        if self.linenumbers == None:
            self.linenumbers = TextLineNumbers(self, width=30)
            self.linenumbers.attach(self.text)
            self.linenumbers.pack(side="left", fill="y")
        elif self.settings.hide_linenumbers:
            self.settings.hide_linenumbers = False
            self.linenumbers.pack(side="left", fill="y")

    def _on_change(self, event):
        if self.linenumbers:
            self.linenumbers.redraw()

    class Settings():
        def __init__(self):
            self.hide_linenumbers = True


if __name__ == "__main__":
    root = tk.Tk()
    text = LinedText(root)
    #text.pack(side="right", fill="both", expand=True)
    button = tk.Button(root, text="Hide", command=text.hide)
    button.pack()
    button = tk.Button(root, text="Show", command=text.show)
    button.pack()
    root.mainloop()

此外,我假设 .pack() 而不是 .pack(side="left"),小部件将绘制在以前的小部件下方。我的按钮被拉到右边。我如何让他们在文本和线条小部件下方绘制?我绝对需要使用 .grid()Frame 吗?

首先,我不确定我是否理解函数 showhide 中的逻辑,但是在下面的代码中,正如您将看到的,我对那些进行了一些更改函数。

我也不确定您为什么要在 LinedText class 中使用 super() 来初始化 self.text,但我认为问题正是从这里开始的。

我首先做的基本上是让 LinedText 继承自 Frame 而不是 CustomText,并创建一个类型为 self.text 的实例变量 CustomText 在你的 LinedText class 中。我这样做是因为我认为这个 LinedText class 是另外两个 CustomTextTextLineNumbers.

类型对象的容器

我还决定将主要 window(root)分成 2 个框架,一个用于 LinedText 对象,另一个包含按钮 ShowHide。通过这种方式,我可以打包框架,而不是单个小部件,例如,我可以打包底部按钮的框架,以及顶部 LinedText 对象的框架。

使用 Frames 组织数据通常是创建布局时正确且更简单的方法。

为了使 TextLineNumber 看起来更漂亮,我还更改了绘制数字的位置。查看代码的注释。

我想说的另一件事是,您应该使用isis not而不是==和[=36]将对象的值与None进行比较=].

此外,我没有看到只使用实例变量的 class (Settings) 有什么意义,这没有意义,我猜你这样做是因为 class以后会变大

如果您对引入或删除小部件时 window 缩小这一事实有疑问,请查看此问题

How to stop Tkinter Frame from shrinking to fit its contents?

希望你能理解showhide两个方法的逻辑,不懂的再问。

完整代码如下:

import tkinter as tk


class TextLineNumbers(tk.Canvas):

    def __init__(self, *args, **kwargs):
        tk.Canvas.__init__(self, *args, **kwargs)
        self.textwidget = None

    def attach(self, text_widget):
        self.textwidget = text_widget

    def redraw(self, *args):
        '''redraw line numbers'''
        self.delete("all")

        i = self.textwidget.index("@0,0")

        while True :
            dline= self.textwidget.dlineinfo(i)
            if dline is None: break
            y = dline[1]
            linenum = str(i).split(".")[0]

            # changed where text is draw: it starts from 4
            self.create_text(4, y, anchor="nw", text=linenum)  
            i = self.textwidget.index("%s+1line" % i)


class CustomText(tk.Text):
    def __init__(self, *args, **kwargs):
        tk.Text.__init__(self, *args, **kwargs)

        self.tk.eval('''
            proc widget_proxy {widget widget_command args} {

                # call the real tk widget command with the real args
                set result [uplevel [linsert $args 0 $widget_command]]

                # generate the event for certain types of commands
                if {([lindex $args 0] in {insert replace delete}) ||
                    ([lrange $args 0 2] == {mark set insert}) || 
                    ([lrange $args 0 1] == {xview moveto}) ||
                    ([lrange $args 0 1] == {xview scroll}) ||
                    ([lrange $args 0 1] == {yview moveto}) ||
                    ([lrange $args 0 1] == {yview scroll})} {

                    event generate  $widget <<Change>> -when tail
                }

                # return the result from the real widget command
                return $result
            }
            ''')
        self.tk.eval('''
            rename {widget} _{widget}
            interp alias {{}} ::{widget} {{}} widget_proxy {widget} _{widget}
        '''.format(widget=str(self)))


class LinedText(tk.Frame):

    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)

        self.settings = self.Settings()
        self.linenumbers = None

        self.text = CustomText(self)
        self.vsb = tk.Scrollbar(orient="vertical", command=self.text.yview)
        self.vsb.pack(side="right", fill="y")

        self.text.configure(yscrollcommand=self.vsb.set)
        self.text.tag_configure("bigfont", font=("Helvetica", "24", "bold"))
        self.text.bind("<<Change>>", self._on_change)
        self.text.bind("<Configure>", self._on_change)
        self.text.insert("end", "one\ntwo\nthree\n")
        self.text.insert("end", "four\n",("bigfont",))
        self.text.insert("end", "five\n")
        self.text.focus()        
        self.text.pack(side="right", fill="both", expand=True)

    def hide(self,event=None):
        if not self.settings.hide_linenumbers:
            self.settings.hide_linenumbers = True
            self.linenumbers.pack_forget()
            self.linenumbers = None

    def show(self,event=None):
        if self.linenumbers is None:
            self.linenumbers = TextLineNumbers(self, width=30)
            self.linenumbers.attach(self.text)
            self.linenumbers.pack(side="left", fill="y")
            self.settings.hide_linenumbers = False

    def _on_change(self, event):
        if self.linenumbers:
            self.linenumbers.redraw()

    class Settings():
        def __init__(self):
            self.hide_linenumbers = True


if __name__ == "__main__":
    root = tk.Tk()

    top_frame = tk.Frame(root)
    text = LinedText(top_frame)
    text.pack(expand=1, fill="both")
    top_frame.pack(side="top", expand=1, fill="both")

    bottom_frame = tk.Frame(root)
    button = tk.Button(bottom_frame, text="Hide", command=text.hide)
    button.pack(side="right")
    button = tk.Button(bottom_frame, text="Show", command=text.show)
    button.pack(side="right")
    bottom_frame.pack(side="bottom", fill="x")

    root.mainloop()

它们重叠的原因是您将行号小部件设为文本小部件的子项。您需要使用作为文本小部件和行号小部件的父级的框架,然后将它们并排打包或网格化。这正是您从中复制的原始代码的工作方式。