在 Canvas 中创建新的 class 对象和使用 Python 和一些 tcl 制作的文本小部件时出现问题?
Problem in creating new class objects in Canvas and Text widgets made using Python and some tcl?
我找到了一个文本编辑器,其中为行号添加了 tkinter.Canvas
,并且 tkinter.Text
小部件和 tkinter.Canvas
位于 tkinter.Frame
中,并且该框架用于添加到 tkinter.ttk.Notebook
选项卡中。这是我从我试图添加的笔记本选项卡中删除的代码:
from tkinter import *
import tkinter as tk
class LineNumberCanvas(Canvas):
def __init__(self, *args, **kwargs):
Canvas.__init__(self, *args, **kwargs)
self.text_widget = None
self.breakpoints = []
def connect(self, text_widget):
self.text_widget = text_widget
def re_render(self):
"""Re-render the line canvas"""
self.delete('all') # To prevent drawing over the previous canvas
temp = self.text_widget.index("@0, 0")
while True :
dline= self.text_widget.dlineinfo(temp)
if dline is None:
break
y = dline[1]
x = dline[0]
linenum = str(temp).split(".")[0]
id = self.create_text(2, y, anchor="nw", text=linenum, font='Consolas 13')
if int(linenum) in self.breakpoints:
x1, y1, x2, y2 = self.bbox(id)
self.create_oval(x1, y1, x2, y2, fill='red')
self.tag_raise(id)
temp = self.text_widget.index("%s+1line" % temp)
def get_breakpoint_number(self,event):
if self.find_withtag('current'):
i = self.find_withtag('current')[0]
linenum = int(self.itemcget(i,'text'))
if linenum in self.breakpoints:
self.breakpoints.remove(linenum)
else:
self.breakpoints.append(linenum)
self.re_render()
class CustomText:
def __init__(self, text):
self.text = text
self.master = text.master
self.mechanise()
self._set_()
self.binding_keys()
def mechanise(self):
self.text.tk.eval('''
proc widget_interceptor {widget command args} {
set orig_call [uplevel [linsert $args 0 $command]]
if {
([lindex $args 0] == "insert") ||
([lindex $args 0] == "delete") ||
([lindex $args 0] == "replace") ||
([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 <<Changed>>
}
#return original command
return $orig_call
}
''')
self.text.tk.eval('''
rename {widget} new
interp alias {{}} ::{widget} {{}} widget_interceptor {widget} new
'''.format(widget=str(self.text)))
return
def binding_keys(self):
for key in ['<Down>', '<Up>', "<<Changed>>", "<Configure>"]:
self.text.bind(key, self.changed)
self.linenumbers.bind('<Button-1>', self.linenumbers.get_breakpoint_number)
return
def changed(self, event):
self.linenumbers.re_render()
#print "render"
return
def _set_(self):
self.linenumbers = LineNumberCanvas(self.master, width=30)
self.linenumbers.connect(self.text)
self.linenumbers.pack(side="left", fill="y")
return
if __name__ == '__main__':
root = Tk()
l = Text(root, font='Consolas 13')
CustomText(l)
l.pack(expand=TRUE, fill=BOTH)
root.mainloop()
现在的问题是我不懂 tcl 语言,程序生成的错误在 CustomText class 的机械功能中'new'关键字。它说:
File "C:\Users\Prerak\AppData\Local\Programs\Python\Python37\EZ_PY\ColorText.py", line 590, in mechanise
'''.format(widget=str(self)))
_tkinter.TclError: can't rename to "new": command already exists
任何人都可以帮助我解决这个问题...我所做的就是在单击将 CustomText 对象添加到 tkinter.Notebook 选项卡的按钮后添加新选项卡。
问题就是它所说的:Tcl 解释器中已经有一个名为 new
的命令。它不是基本 Tcl 命令集的一部分,因此它可能来自默认加载的某些包。不管是什么,如果它不是你的,它可能需要留在原地,否则其他东西会坏掉。
处理这个问题的最简单方法是使用一个唯一的计数器,这样每个被拦截的小部件都会得到一些唯一的东西(这类似于 gensym
在 Lisp 中所做的)。由于此问题出现在 Tcl 端,您可以将该计数器保留在该端,而这样做的便捷方法涉及创建第二个过程,我将其称为 install_widget_interceptor
。作为奖励,使用它的调用变得更简单 并且 您可以同时拥有两个 CustomText
实例。
(...省略了不会改变的代码,我整理了 widget_interceptor
过程,使其更加地道...)
# You probably shouldn't repeat this bit every time you create a widget
self.text.tk.eval('''
proc widget_interceptor {widget command args} {
set orig_call [uplevel 1 [linsert $args 0 $command]]
if {
[lindex $args 0] in {insert delete replace} ||
([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})
} then {
event generate $widget <<Changed>>
}
#return original command
return $orig_call
}
proc install_widget_interceptor {widget} {
global unique_widget_id
set handle ::_intercepted_widget_[incr unique_widget_id]
rename $widget $handle
interp alias {} ::$widget {} widget_interceptor $widget $handle
}
''')
# This bit you absolutely need each time
self.text.tk.eval('''
install_widget_interceptor {widget}
'''.format(widget=str(self.text)))
如代码中所述,Tcl 端过程的创建可能只应在创建 Python class 时完成一次。每次都这样做并不重要;太浪费了。
此外,Tk 本身会在文本小部件被修改时为它们生成 <<Modified>>
事件。它使用不同的方式来看待对您重要的事情,专注于对其管理的模型的更改,而不是该模型上的视图(因此移动光标或滚动不会触发它)。
我找到了一个文本编辑器,其中为行号添加了 tkinter.Canvas
,并且 tkinter.Text
小部件和 tkinter.Canvas
位于 tkinter.Frame
中,并且该框架用于添加到 tkinter.ttk.Notebook
选项卡中。这是我从我试图添加的笔记本选项卡中删除的代码:
from tkinter import *
import tkinter as tk
class LineNumberCanvas(Canvas):
def __init__(self, *args, **kwargs):
Canvas.__init__(self, *args, **kwargs)
self.text_widget = None
self.breakpoints = []
def connect(self, text_widget):
self.text_widget = text_widget
def re_render(self):
"""Re-render the line canvas"""
self.delete('all') # To prevent drawing over the previous canvas
temp = self.text_widget.index("@0, 0")
while True :
dline= self.text_widget.dlineinfo(temp)
if dline is None:
break
y = dline[1]
x = dline[0]
linenum = str(temp).split(".")[0]
id = self.create_text(2, y, anchor="nw", text=linenum, font='Consolas 13')
if int(linenum) in self.breakpoints:
x1, y1, x2, y2 = self.bbox(id)
self.create_oval(x1, y1, x2, y2, fill='red')
self.tag_raise(id)
temp = self.text_widget.index("%s+1line" % temp)
def get_breakpoint_number(self,event):
if self.find_withtag('current'):
i = self.find_withtag('current')[0]
linenum = int(self.itemcget(i,'text'))
if linenum in self.breakpoints:
self.breakpoints.remove(linenum)
else:
self.breakpoints.append(linenum)
self.re_render()
class CustomText:
def __init__(self, text):
self.text = text
self.master = text.master
self.mechanise()
self._set_()
self.binding_keys()
def mechanise(self):
self.text.tk.eval('''
proc widget_interceptor {widget command args} {
set orig_call [uplevel [linsert $args 0 $command]]
if {
([lindex $args 0] == "insert") ||
([lindex $args 0] == "delete") ||
([lindex $args 0] == "replace") ||
([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 <<Changed>>
}
#return original command
return $orig_call
}
''')
self.text.tk.eval('''
rename {widget} new
interp alias {{}} ::{widget} {{}} widget_interceptor {widget} new
'''.format(widget=str(self.text)))
return
def binding_keys(self):
for key in ['<Down>', '<Up>', "<<Changed>>", "<Configure>"]:
self.text.bind(key, self.changed)
self.linenumbers.bind('<Button-1>', self.linenumbers.get_breakpoint_number)
return
def changed(self, event):
self.linenumbers.re_render()
#print "render"
return
def _set_(self):
self.linenumbers = LineNumberCanvas(self.master, width=30)
self.linenumbers.connect(self.text)
self.linenumbers.pack(side="left", fill="y")
return
if __name__ == '__main__':
root = Tk()
l = Text(root, font='Consolas 13')
CustomText(l)
l.pack(expand=TRUE, fill=BOTH)
root.mainloop()
现在的问题是我不懂 tcl 语言,程序生成的错误在 CustomText class 的机械功能中'new'关键字。它说:
File "C:\Users\Prerak\AppData\Local\Programs\Python\Python37\EZ_PY\ColorText.py", line 590, in mechanise
'''.format(widget=str(self)))
_tkinter.TclError: can't rename to "new": command already exists
任何人都可以帮助我解决这个问题...我所做的就是在单击将 CustomText 对象添加到 tkinter.Notebook 选项卡的按钮后添加新选项卡。
问题就是它所说的:Tcl 解释器中已经有一个名为 new
的命令。它不是基本 Tcl 命令集的一部分,因此它可能来自默认加载的某些包。不管是什么,如果它不是你的,它可能需要留在原地,否则其他东西会坏掉。
处理这个问题的最简单方法是使用一个唯一的计数器,这样每个被拦截的小部件都会得到一些唯一的东西(这类似于 gensym
在 Lisp 中所做的)。由于此问题出现在 Tcl 端,您可以将该计数器保留在该端,而这样做的便捷方法涉及创建第二个过程,我将其称为 install_widget_interceptor
。作为奖励,使用它的调用变得更简单 并且 您可以同时拥有两个 CustomText
实例。
(...省略了不会改变的代码,我整理了 widget_interceptor
过程,使其更加地道...)
# You probably shouldn't repeat this bit every time you create a widget
self.text.tk.eval('''
proc widget_interceptor {widget command args} {
set orig_call [uplevel 1 [linsert $args 0 $command]]
if {
[lindex $args 0] in {insert delete replace} ||
([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})
} then {
event generate $widget <<Changed>>
}
#return original command
return $orig_call
}
proc install_widget_interceptor {widget} {
global unique_widget_id
set handle ::_intercepted_widget_[incr unique_widget_id]
rename $widget $handle
interp alias {} ::$widget {} widget_interceptor $widget $handle
}
''')
# This bit you absolutely need each time
self.text.tk.eval('''
install_widget_interceptor {widget}
'''.format(widget=str(self.text)))
如代码中所述,Tcl 端过程的创建可能只应在创建 Python class 时完成一次。每次都这样做并不重要;太浪费了。
此外,Tk 本身会在文本小部件被修改时为它们生成 <<Modified>>
事件。它使用不同的方式来看待对您重要的事情,专注于对其管理的模型的更改,而不是该模型上的视图(因此移动光标或滚动不会触发它)。