0

I have a Tkinter use case where I need a text box that is multi-line and can filter and replace characters.

Here is a simplified but applicable use case:

  • A multi-line text control

  • Filters so only characters "A", "B", "C", "0", "1" and "*" can be typed

  • An exception to the filter above is that lower case characters can be typed, but they will converted to uppercase

Because I need a multi-line control, the Entry widget is out (it is single-line only). This means I need to use the Text widget.

With the Text widget, I can filter on text using the bind method. Example:

self.my_text_field.bind('<KeyPress>',self.filter_text)

def filter_text(self, event):
    character_set = ["A", "B", "C", "0", "1", "*"]
    if event.char.upper() in character_set:
        #Do nothing. Accept character.
        pass
    else:
        #Ignore character.
        return 'break'

The above code works fine for filtering, but it does not achieve the character replacement to replace any lower characters with their .upper() version.

So, if I want to actually do character replacement, I can use the validatecommand to achieve the same filtering as I get with the .bind method above, and if I want to actually replace characters, I need to use the invalidcommand to replace text when my validate function returns false. See this thread which covers the validatecommand and invalidcommand:

Interactively validating Entry widget content in tkinter

The problem is that validatecommand and invalid are only available on the Entry widget (which again is single line and won't work in my use case).

I experimented with using .bind on KeyRelease and using .get and .insert to replace the text of the Text widget, but that is messy and seems impractical because changing the control's text with .delete and .insert results in programmatic versions and additional instances of KeyPress/KeyRelease.

I can always ignore lower case characters with my filter and still use the Text control with what I have found to date, but I really would like to convert the characters to uppercase as well.

I've looked a good bit on StackOverflow and other resources and haven't found a solution. Any help would be appreciated.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
dgLurn
  • 51
  • 7
  • I haven't actually tried this, but I think you can produce an uppercase version of the key that was pressed via `widget.event_generate("", keysym=char.upper())` (and then do `return "break"` so that the lowercase key doesn't get inserted). – jasonharper Nov 28 '19 at 01:08

2 Answers2

1

Arguably, the simplest solution is for your functionto insert the character rather than letting the text widget do it.

Example:

def filter_text(self, event):
    character_set = ["A", "B", "C", "0", "1", "*"]
    if event.char.upper() in character_set:
        event.widget.insert("insert", event.char.upper())
    return "break"
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Thanks Bryan, this works great! Such a simple solution. I added a bit of code to allow for arrow keys as I realized those weren't considered by me earlier. Will post that code in another response. – dgLurn Nov 29 '19 at 20:23
0

Adding on to Bryan Oakley's solution, this allows for arrow keys and backspace for navigation within the Text widget:

def filter_text(self, event):        

    character_set = ["A", "B", "C", "0", "1", "*"]   

    navigation_keys = ['Left','Right','Up','Down','BackSpace']        

    if event.char.upper() in character_set:
        event.widget.insert('insert', event.char.upper())
    elif event.keysym in navigation_keys:
        return True

    return 'break'  
dgLurn
  • 51
  • 7