【问题标题】:how do you restrict the length of a tkinter text widget in python by blocking the latest character entered?如何通过阻止输入的最新字符来限制 python 中 tkinter 文本小部件的长度?
【发布时间】:2019-01-30 11:36:40
【问题描述】:

我正在尝试以一种非常具体的方式限制 Python 中 tkinter 文本小部件或条目小部件(任何一种小部件类型都可以)中的字符数量。我有一个带有接近文本小部件的解决方案:

string =  self.text.get("1.0","end-1c")
if len(string) >= 5:
    self.text.delete('1.0', "end-1c")
    string = string[:-1]
    self.quantity_entry.insert(END, string)

但是上面的代码做了以下事情:如果我输入 123456,它会将内容变成 12346。它会砍掉现有字段的最后一个字符 (5),然后将输入的最后一个字符 (6) 放在结尾。所以它变成了 12346。

我想要的是 12345 保持 12345,这意味着现有的 12345 字符串应该保持原样,并且任何新字符都会被有效地阻止被附加。有没有办法做到这一点?

我尝试使用:

self.text.configure(state="disabled")

这确实会阻止插入后续字符,因此 12345 保持为 12345。但这会锁定该字段以防将来编辑。即使随后尝试使用:

self.text.configure(state="normal")

该字段保持锁定状态。如果用户达到五个字符的限制,我希望用户能够编辑该字段。

谢谢。

【问题讨论】:

  • 我认为您在这里真正想要的是 tkinter 验证:您可以编写一个函数,当用户尝试编辑文本时将调用该函数,您将在其中传递建议的新字符串(或现有字符串,或两者,或各种不同的选项),您可以接受或拒绝更改。那会做你想要的吗?如果是这样,我可以为您找到一个很好的重复问题和一个很好的答案。
  • 感谢 abarnert。我玩弄了验证,但我仍然在附加输入的最后一个字符(这不是我想要的)。我想出了解决方案,我在下面发布了。
  • 条目小部件的解决方案可能与文本小部件的解决方案大不相同。你更倾向哪个?条目小部件有一个专门用于验证数据的选项。
  • 谢谢布莱恩。我很高兴让其中任何一个工作。但是现在我看到代码问题出在 key vs keyrelease 上,我将尝试使用 entry 小部件。它已验证,与文本小部件相比,删除语法更简洁。

标签: python text tkinter widget


【解决方案1】:

跟踪变量会更简洁。然后它甚至不会在屏幕上短暂闪烁。你可以像这样打包成一个整洁的子类:

import tkinter as tk

class Lotfi(tk.Entry):
    def __init__(self, master=None, max_len=5, **kwargs):
        self.var = tk.StringVar()
        self.max_len = max_len
        tk.Entry.__init__(self, master, textvariable=self.var, **kwargs)
        self.old_value = ''
        self.var.trace('w', self.check)

    def check(self, *args):
        if len(self.get()) <= self.max_len:
            self.old_value = self.get() # accept change
        else:
            self.var.set(self.old_value) # reject change

#demo code:
window = tk.Tk()
ent=Lotfi(window, max_len=5)
ent.pack()
window.mainloop()

【讨论】:

  • 谢谢,小说。我对跟踪感兴趣,但我听说它已被弃用。但我没有找到任何确凿的证据。您是否看到过任何有关逐步淘汰跟踪的权威信息?
  • Trace 没有折旧,我还没有听到有人说它有折旧的计划。
  • 谢谢,小说。我做了我自己的 check 方法版本,签名中没有 *args,但它不起作用。然后我把 *args 放进去,它起作用了。为什么需要 *args?当我调用它时,我没有为该方法提供任何参数。它必须是 tkinter 内部的东西。
  • 是的,tkinter 的跟踪在调用函数时发送了 3 个无用的参数。如果您有兴趣,只需添加print(args) 即可查看。
【解决方案2】:

我想出了解决办法。在 .bind 方法中,我需要使用

"<KeyRelease>"

而不是

"<Key>"

然后,作为次要编辑,需要将 >=5 更改为 > 5。现在它可以工作了。当我输入 123456 时,它在屏幕上非常短暂地显示 123456,但随后很快将 6 切掉,所以它又回到 12345。耶!

【讨论】:

    【解决方案3】:

    我知道这已经很老了,但我认为它可以帮助一些人。

    一直在寻找一个非常优雅的解决方案来解决这个问题。但是还没有找到。所以我想我会发布我的凌乱的。

    如果你觉得这很有用,请投票,如果你对我如何更清楚地沟通有想法,我也会喜欢。

    这是“一个”答案。但是,如果您解释一下为什么选择编写您所做的代码,这将有所帮助,这样我就可以更好地指导您思考解决方案的方法。

    # IMPORT MODULES AND ALIAS THEM
    import tkinter as tk
    
    # This is a place holder to self.quantity_entry.insert(END, string) just to 
    # simulate the call to some other object
    def OtherTextWidget(string):
        print( f"{text1.index(tk.END)}, {string}" )
    
    # This is the 'meat' of your solution that limits the input
    def Restrict(e=None):
        print(e.type) # just to proof that we are pressing a key
    
        # If for some unknown reason this function gets called on key-release, it will 
        # terminate the function. Because it will break our desired behavior.
        if int(e.type) == 3: 
                return
    
        # The 'widget.bind()' event argument pass a reference to the widget that
        # called it. Se we use that here to get the text from the caller 
        # "text1.bind()" as event.widget.get() note we have aliased 'event' as 'e'
        # inside the function space.
    
        # Get text from start to the current end length minus the '\n' character
        string =  e.widget.get("1.0","end-1c")
    
        # If there are 5 or more characters go ahead with the limit
        # Note len() starts at 0, so 4<= is equal to 5+ chars
        if 4 <= len(string):
            # Get the first five characters before the delete
            OtherTextWidget( e.widget.get("1.0","1.5") )
        
            # !!NOTES!!
            # We are lazy and don't want to code a rejection for a 6th character. So 
            # we overwrite the 5th character, the result is the same for less work.
      
            # we are operating during the key press, and the 6th character is not 
            # inserted until the key release. The 5th character is empty if you 
            # debug it after the delete. Again the 5th character will be replaced 
            # by the 6th on the key release.
       
            # delete the 5th character to the '\n', remember we are counting from 0.
            e.widget.delete('1.4', "end-1c")
    
    # TKINTER OBJECTS SETUP:
    root = tk.Tk()
    text1 = tk.Text(root, width=7, height=1) # 7 chars long, 1 char high
    # We give the "entry box" 5 +2 extra character for aesthetics spacing.
    # It just looks better with a little room to breath.
    
    text1.pack() # fits the widget in the minimum space within the frame/window
    text1.focus_set() # because I'm lazy and I want the cursor already in the widget.
    
    # BIND TEXT WIDGET KEY INPUT EVENT:
    # Whenever there is a key press inside the text widget this will fire the 
    # Restrict() function with the 'kepress event' argument
    # That events data is in key:value pairs and looks something like this:
    # <KeyPress event state=Mod1 keysym=1 keycode=97 char='1' x=1959 y=69>
    text1.bind('<Key>', Restrict)
    
    # start the tkinter event loop and open a toplevel window
    root.mainloop()
    
    # The '<Key>' gives a e.type of 2 or key press/down
    # if you use '<KeyRelease>' it gives a e.type of 3 release/up and we will have to 
    # change some code around to make it work. But the important difference is it will 
    # place the 6th character in the field then delete it, that doesn't look crisp. 
    # This is why we are operating on a key press rather than release, because we want 
    # to operate on the string before the character is insert into the text widget.
    

    为了清楚起见,这里是有限的注释代码。 (更新:我认为有实际显示的字符串会很好,所以添加了这个发布切换来捕获它。)

    import tkinter as tk
    
    def OtherTextWidget(string):
        print("Key Press Phase:", string)
    
    # Solution - Step 1. toggle full flag
    global full #(update)
    full = False
    
    # Solution - Step 4. profit
    def AfterRestrict(e=None): #(update)
        global full
        if True == full:
            OtherTextWidget( e.widget.get("1.0","1.5") )
            print("Key Release Phase:", e.widget.get("1.0","1.5") )
    
    # Solution - Step 3. limit input
    def Restrict(e=None):
        global full #(update)
        string = e.widget.get("1.0","end-1c")
        if 4 <= len(string):
            e.widget.delete('1.4', "end-1c")
            full = True #(update)
        else: #(update)
            full = False
    
    root = tk.Tk()
    text1 = tk.Text(root, width=7, height=1)
    text1.pack()
    text1.focus_set()
    
    # Solution - Step 2. get input event from widget
    text1.bind('<Key>', Restrict)
    text1.bind('<KeyRelease>', AfterRestrict) #(update)
    
    root.mainloop()
    

    很抱歉冗长乏味,但希望这对一些人有所帮助。 :)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-11
      • 1970-01-01
      相关资源
      最近更新 更多