首页 > 解决方案 > 在使用 Python 和一些 tcl 制作的 Canvas 和 Text 小部件中创建新类对象时出现问题?

问题描述

我找到了一个文本编辑器,在该编辑器中tkinter.Canvas添加了行号,并且tkinter.Text小部件以及tkinter.Canvas在 a 内tkinter.Frame,并且该 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 类的机械功能中,其中有' 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 选项卡的按钮后添加新选项卡。

标签: pythontkintertcl

解决方案


问题是它所说的: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 类时完成一次。每次都这样做并不重要;只是浪费。

此外,Tk 本身<<Modified>>会在修改 Text 小部件时为其生成事件。它使用对您而言重要的不同意义,专注于对其管理的模型的更改,而不是对该模型上的视图的更改(因此移动光标或滚动不会触发它)。


推荐阅读