2

Is there a way to map key events to other letters. This is for a convienient way to put ciphres in an entry field:

An example: My laptop has no extra keys for ciphres on the keyboard, but i often had to do number input. So i thought it would be nice to have an entry which changes letters in ciphres:

e -> 1
z -> 2
d -> 3
v -> 4
f -> 5
...

I tried many things:

  • a label simulates a entry, but theres a problem with the event, because this event isnt supported by the label widget.
  • by the entry <Key> event i couldn't change the input in the entry. A pressed e will always displayed in the entry. (i used the propagated validation method by tkinter!).
jgsedi
  • 227
  • 4
  • 18

3 Answers3

1

The reason your attempt to bind to <Key> failed is probably because you failed to do return "break" after processing the keystroke.

You can create your own binding for any key on the keyboard. For example, to change "e" to do anything you want, do self.entry.bind("<e>", ...). Then, in the binding you can insert whatever you want. If you do return "break", then the default handling of the key will be suppressed.

If you want to set up a mapping in a dictionary, you can bind to "<Key>". Here's a quick example:

import Tkinter as tk

class Example(tk.Frame):
    map = {"e": "1",
           "z": "2",
           "v": "3",
           # ... and so on
           }

    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)
        self.entry = tk.Entry(self, width=20)
        self.entry.pack(side="top", fill="x")
        self.entry.bind("<Key>", self.keymap)

    def keymap(self, event):
        if event.char in self.map:
            self.entry.insert("insert", self.map[event.char])
            return "break"

if __name__ == "__main__":
    root = tk.Tk()
    Example(root).pack(side="top", fill="both", expand=True)
    root.mainloop()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • you're right - i didn't do a ``return "break"``. I tested it with that and it solves - a part - of the problem. I don't understand the ``return "break"`` so far but i think it's a tcl interna - right? – jgsedi Jun 02 '13 at 19:52
  • 1
    Tkinter has the notion of "bind tags" (or bindtags) -- it defines an order for processing events. Doing "return 'break'" stops the processing of an event -- no more tags for the current event will be processed. The bind tag that handles the default behavior of an event falls _after_ the tag for the widget itself. Thus, if you put a binding on a widget and then do `return "break"` you prevent the default binding from happening. For more information see this answer: http://stackoverflow.com/a/3513906/7432 – Bryan Oakley Jun 02 '13 at 20:05
0

The easiest way that I know is to intercept the input using the string variable that you've assigned to the entry widget. One of the things that you can do with such variables is execute a function or method whenever that variable changes, and you can use that function/method to modify the entry contents. Here's a quick example:

from Tkinter import *

class EntryHack(Frame):
    def __init__(self, root):
        self.root = root
        Frame.__init__(self, root)
        self.pack(side="left")
        self.entry = Entry(self, width=10)
        self.entry.pack(side="left")
        self.entry_var = StringVar()
        self.entry_var.trace_variable("w", self.changed)
        self.entry["textvariable"] = self.entry_var

    def changed(self, *args):
        """Intercept changes"""
        value = self.entry_var.get()
        changed = False
        frm = "zxcasdqwe"
        to = "123456789"
        if not value.isdigit():
            changed = True
            new = ""
            for char in value:
                if char.isdigit() or char not in frm:
                    new += char
                else:
                    new += to[frm.index(char)]
            value = new
        if changed:
            self.entry_var.set(value)

root = Tk()
app = EntryHack(root)
root.mainloop()

The nice thing about the trace_variable assignment is that the function/method that you give it will be called when you type something into the field, but will not be called when you make manual changes to the variable, thus avoiding an endless loop.

Justin S Barrett
  • 636
  • 4
  • 14
0

Ok my own solution...

thanks to @Justin S Barrett and to @Brian Oakley.

According to @Brian Oakley's hint i solved my problem:

class DigitEntry(Frame):
    mapping = {
        "o": "0",
        "e": "1",
        "z": "2",
        "d": "3",
        "v": "4",
        "f": "5",
        "s": "6",
        "i": "7",
        "a": "8",
        "n": "9",
        ",": ".",

        "0": "0",
        "1": "1",
        "2": "2",
        "3": "3",
        "4": "4",
        "5": "5",
        "6": "6",
        "7": "7",
        "8": "8",
        "9": "9",
        ".": "."
        }
    def __init__(self, *args, **kwargs):
        Frame.__init__(self, *args, **kwargs)
        self.entry = Entry(self, width=10)
        self.entry.pack(fill="x")

        self.entry.bind("<Key>", self.keymap)
        self.entry.bind("<FocusOut>", self.check)

    def check(self, event):
        """if checks are necessary
        """
        pass

    def keymap(self, event):
        """ do mappings, pass allowed inputs and block the rest.
        """
        if event.char in self.mapping:
            self.entry.insert("insert", self.mapping[event.char])
            return "break"
        elif event.keysym in ["BackSpace", "Tab"]:
            pass
        else:
            self.entry.insert("insert", "")
            return "break"


class PointsEntry(DigitEntry):
    def __init__(self, *args, **kwargs):
        DigitEntry.__init__(self, *args, **kwargs)

    def check(self, event):
        value = self.entry.get()

        if value.find(".") >= 0:
            if not (value[-1] == "." or value[-2:] == ".5"):
                err = value + " bad frac digit"
                self.entry.delete(0, "end")
                self.entry["bg"] = "red"
                self.entry.insert("insert", err)

            elif value[-1] == ".":
                self.entry.insert("insert", "5")
                if len(value) == 1:
                    self.entry.insert(0, "0")
                return "break"
jgsedi
  • 227
  • 4
  • 18
  • there's no point in the final else in the `keymap` function -- you're just replicating the default behavior. You can remove those three lines and the program will still work the way you expect. In fact, I think your solution breaks the behavior of the back and forward arrow keys; by removing that else block the arrow keys will work as expected. – Bryan Oakley Jun 02 '13 at 20:57
  • i need this else branch because the keys "q,w,..." must be blocked! if special keys are needed they have to be listed in the ``elif event.keysym in [...]`` branch. – jgsedi Jun 02 '13 at 21:27
  • Oh, I see that now. You can remove the insert command and just return "break". Inserting an empty string serves no purpose; it's not a requirement for a key binding that it has to call the insert function. – Bryan Oakley Jun 02 '13 at 21:31
  • Even though you wrote your own code for your specific situation, it's based primarily on the tips from @BryanOakley, as you indicated. In that light, methinks his answer deserves to be marked as the accepted solution. – Justin S Barrett Jun 03 '13 at 23:24