为什么我的小部件重叠?
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
吗?
首先,我不确定我是否理解函数 show
和 hide
中的逻辑,但是在下面的代码中,正如您将看到的,我对那些进行了一些更改函数。
我也不确定您为什么要在 LinedText
class 中使用 super()
来初始化 self.text
,但我认为问题正是从这里开始的。
我首先做的基本上是让 LinedText
继承自 Frame
而不是 CustomText
,并创建一个类型为 self.text
的实例变量 CustomText
在你的 LinedText
class 中。我这样做是因为我认为这个 LinedText
class 是另外两个 CustomText
和 TextLineNumbers
.
类型对象的容器
我还决定将主要 window(root
)分成 2 个框架,一个用于 LinedText
对象,另一个包含按钮 Show
和 Hide
。通过这种方式,我可以打包框架,而不是单个小部件,例如,我可以打包底部按钮的框架,以及顶部 LinedText
对象的框架。
使用 Frames
组织数据通常是创建布局时正确且更简单的方法。
为了使 TextLineNumber
看起来更漂亮,我还更改了绘制数字的位置。查看代码的注释。
我想说的另一件事是,您应该使用is
或is not
而不是==
和[=36]将对象的值与None
进行比较=].
此外,我没有看到只使用实例变量的 class (Settings
) 有什么意义,这没有意义,我猜你这样做是因为 class以后会变大
如果您对引入或删除小部件时 window 缩小这一事实有疑问,请查看此问题
How to stop Tkinter Frame from shrinking to fit its contents?
希望你能理解show
和hide
两个方法的逻辑,不懂的再问。
完整代码如下:
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()
它们重叠的原因是您将行号小部件设为文本小部件的子项。您需要使用作为文本小部件和行号小部件的父级的框架,然后将它们并排打包或网格化。这正是您从中复制的原始代码的工作方式。
我正在使用 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
吗?
首先,我不确定我是否理解函数 show
和 hide
中的逻辑,但是在下面的代码中,正如您将看到的,我对那些进行了一些更改函数。
我也不确定您为什么要在 LinedText
class 中使用 super()
来初始化 self.text
,但我认为问题正是从这里开始的。
我首先做的基本上是让 LinedText
继承自 Frame
而不是 CustomText
,并创建一个类型为 self.text
的实例变量 CustomText
在你的 LinedText
class 中。我这样做是因为我认为这个 LinedText
class 是另外两个 CustomText
和 TextLineNumbers
.
我还决定将主要 window(root
)分成 2 个框架,一个用于 LinedText
对象,另一个包含按钮 Show
和 Hide
。通过这种方式,我可以打包框架,而不是单个小部件,例如,我可以打包底部按钮的框架,以及顶部 LinedText
对象的框架。
使用 Frames
组织数据通常是创建布局时正确且更简单的方法。
为了使 TextLineNumber
看起来更漂亮,我还更改了绘制数字的位置。查看代码的注释。
我想说的另一件事是,您应该使用is
或is not
而不是==
和[=36]将对象的值与None
进行比较=].
此外,我没有看到只使用实例变量的 class (Settings
) 有什么意义,这没有意义,我猜你这样做是因为 class以后会变大
如果您对引入或删除小部件时 window 缩小这一事实有疑问,请查看此问题
How to stop Tkinter Frame from shrinking to fit its contents?
希望你能理解show
和hide
两个方法的逻辑,不懂的再问。
完整代码如下:
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()
它们重叠的原因是您将行号小部件设为文本小部件的子项。您需要使用作为文本小部件和行号小部件的父级的框架,然后将它们并排打包或网格化。这正是您从中复制的原始代码的工作方式。