Tkinter Text Buchstaben sperren

Fragen zu Tkinter.
Antworten
_Scaui
User
Beiträge: 11
Registriert: Montag 17. April 2023, 19:47
Kontaktdaten:

Hallo,

Ich habe neulich mit tkinter ein Programm geschrieben das in einem Textfeld einzelne Buchstaben sperrt. Allerdings kann man Buchstaben immer noch mit Hexadezimalcode(z. B. Alt+066) eingeben.
Bei Google hab ich dazu nichts gefunden und ich wäre froh wenn mir jemand helfen könnte.

Mein Code:

Code: Alles auswählen

import tkinter as tk




class MyText(tk.Text):
    def __init__(self, sperren, *args, **kw):
        super().__init__()
        if not(isinstance(sperren, str)):     
            raise TypeError("\"Sperren\" muss ein String sein!")
        else:
            self.bind("<Control-v>", self.__sperreBuchstabe)
            for gesperrt in sperren:
                self.bind("<KeyPress-{}>".format(gesperrt), self.__sperreBuchstabe)

    def __sperreBuchstabe(self, event):
        return "break"



def main():
    fenster = tk.Tk()
    fenster.title("Texttest")

    keinABC = tk.Label(fenster, text="Du kannst keine As, Bs und Cs eingeben!")
    keinABC.pack()

    text = MyText("abcABC", fenster)
    text.focus_set()
    text.pack()

    fenster.mainloop()


if __name__ == "__main__":
    main()
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Statt mit irgendetwas mit bind zu versuchen, benutzt man statt dessen Validierung: https://www.pythontutorial.net/tkinter/ ... alidation/
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@_Scaui: Anmerkungen zum Quelltext: `My` ist kein sinnvoller Namenszusatz wenn es nicht auch `Our` oder `Their` gibt um das davon abzugrenzen. Entweder nur `Text` oder in den Namen sollte etwas rein was den Unterschied zwischen dieser Klasse und einem `tkinter.Text` beschreibt.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase). Siehe den Style Guide for Python Code.

Alle Widgetklassen nehmen als erstes Argument das Elternwidget entgegen. Deine Klasse nimmt das überhaupt nicht, beziehungsweise wird es zwar übergeben, dann aber ignoriert. Das funktioniert hier nur zufällig, weil ohne Angabe immer auf das globale Tk-Objekt zurückgegriffen wird. Sämtliche weitere Argumente werden auch ignoriert, statt an die `__init__()` der Basisklasse durchgereicht zu werden.

``not`` ist keine Funktion, sollte also auch nicht so geschrieben werden als wäre es eine.

Die Typprüfung ist nicht nur unpythonisch, sondern auch falsch. Es ist ja nicht nur so, dass man dort auch andere Typen als Zeichenketten übergeben kann, sondern das wäre sogar *notwendig* wenn man irgend etwas anderes als Ziffern und Ascii-Zeichen ausschliessen möchte. Denn fast alles andere hat Bezeichner die aus mehr als einem Buchstaben bestehen und damit geht eine Zeichenkette die Zeichen für Zeichen auseinander genommen wird, dafür nicht. An der Stelle geht jedes beliebige iterierbare Objekt das Zeichenketten liefert, beziehungsweise Objekte die sich in dem Kontext sinnvoll in Zeichenketten umwandeln lassen. Darauf kann man nicht sinnvoll vorab prüfen.

Doppelte führende Unterstriche sind nicht ”private”, da sollte nur einer verwendet werden um anzuzeigen, dass das ein Implementierungsdetail ist. Eine Methode ist das auch nicht wirklich, also sollte man das auch einfach als Funktion definieren.

Der `format()`-Aurfuf kann durch ein f-Zeichenkettenliteral ersetzt werden.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk


def ignoriere_ereignis(_event):
    return "break"


class Text(tk.Text):
    def __init__(self, master, **kwargs):
        gesperrte_tasten = kwargs.pop("gesperrte_tasten")
        super().__init__(master, **kwargs)
        self.bind("<Control-v>", ignoriere_ereignis)
        for gesperrt in gesperrte_tasten:
            self.bind(f"<KeyPress-{gesperrt}>", ignoriere_ereignis)


def main():
    fenster = tk.Tk()
    fenster.title("Texttest")

    tk.Label(fenster, text="Du kannst keine As, Bs und Cs eingeben!").pack()

    text = Text(fenster, gesperrte_tasten="abcABC")
    text.focus_set()
    text.pack()

    fenster.mainloop()


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
_Scaui
User
Beiträge: 11
Registriert: Montag 17. April 2023, 19:47
Kontaktdaten:

Hallo Sirius3,

Ich habe mir die Validierung mal angeschaut und konnte sie auch auf Entrys anwenden.

Mein Code:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from tkinter import ttk


class Entry(ttk.Entry):
    def __init__(self, master, **kwargs):
        self.gesperrte_tasten = kwargs.pop("gesperrte_tasten")
        super().__init__(master, **kwargs)

        validatecommand = (master.register(self.validate), "%S")
        self.config(validate="key", validatecommand=validatecommand) 


    def validate(self, value):
        if value in self.gesperrte_tasten:
            return False
        else:
            return True


def main():
    fenster = tk.Tk()
    fenster.title("Texttest")

    tk.Label(fenster, text="Du kannst keine As, Bs und Cs eingeben!").pack()

    text = Entry(fenster, gesperrte_tasten="abcABC")
    text.focus_set()
    text.pack()

    fenster.mainloop()


if __name__ == "__main__":
    main()
Wenn ich allerdings auf Text statt auf Entry anwende bekomme ich immer folgenden Fehler:

Code: Alles auswählen

Traceback (most recent call last):
  File "C:\Users\Scaui\Desktop\Python\PythonForum\MyTk2.pyw", line 36, in <module>
    main()
  File "C:\Users\Scaui\Desktop\Python\PythonForum\MyTk2.pyw", line 28, in main
    text = Entry(fenster, gesperrte_tasten="abcABC")
  File "C:\Users\Scaui\Desktop\Python\PythonForum\MyTk2.pyw", line 12, in __init__
    self.config(validate="key", validatecommand=validatecommand)
  File "C:\Users\Scaui\AppData\Local\Programs\Python\Python311\Lib\tkinter\__init__.py", line 1702, in configure
    return self._configure('configure', cnf, kw)
  File "C:\Users\Scaui\AppData\Local\Programs\Python\Python311\Lib\tkinter\__init__.py", line 1692, in _configure
    self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
_tkinter.TclError: unknown option "-validate"
Woraus man schließen kann, dass der tkinter Text keine Validierung hat. :(
Hat noch jemand eine Idee?
_Scaui
User
Beiträge: 11
Registriert: Montag 17. April 2023, 19:47
Kontaktdaten:

Hallo,
hab nach einigem überlegen selbst eine Lösung mit bind() gefunden.
Der komplette Code:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from tkinter import ttk


class Entry(ttk.Entry):
    def __init__(self, master, **kwargs):
        self.gesperrte_tasten = kwargs.pop("gesperrte_tasten")
        super().__init__(master, **kwargs)

        validatecommand = (master.register(self.validate), "%S")
        self.config(validate="key", validatecommand=validatecommand) 


    def validate(self, value):
        if value in self.gesperrte_tasten:
            return False
        else:
            return True

class Text(tk.Text):
    def __init__(self, master, **kwargs):
        self.gesperrte_tasten = kwargs.pop("gesperrte_tasten")
        super().__init__(master, **kwargs)
        
        self.bind("<Any-KeyPress>", self.validate)

    def validate(self, event):
        if event.char in self.gesperrte_tasten:
            return "break"

def main():
    fenster = tk.Tk()
    fenster.title("Texttest")

    ttk.Label(fenster, text="Du kannst keine As, Bs und Cs eingeben!").pack()

    text = Entry(fenster, gesperrte_tasten="abcABC")
    text.focus_set()
    text.pack()

    ttk.Label(fenster, text="Und hier auch nicht!").pack()

    text = Text(fenster, gesperrte_tasten="abcABC")
    text.pack()
    
    fenster.mainloop()

if __name__ == "__main__":
    main()
_Scaui
User
Beiträge: 11
Registriert: Montag 17. April 2023, 19:47
Kontaktdaten:

Für alle, die es interessiert der fertige Code:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from tkinter import ttk


class Entry(ttk.Entry):
    def __init__(self, master=None, **kwargs):
        if "gesperrte_tasten" in kwargs:
            self.gesperrte_tasten = kwargs.pop("gesperrte_tasten")
        else:
            self.gesperrte_tasten = []
            
        super().__init__(master, **kwargs)

        validatecommand = (master.register(self.validate), "%S")
        self.config(validate="key", validatecommand=validatecommand)

        self.bind("<Control-v>", self.copy_in)

    def validate(self, value):
        if value in self.gesperrte_tasten:
            return False
        else:
            return True

    def configure(self, **options):
        if "gesperrte_tasten" in options:
            self.gesperrte_tasten = options.pop("gesperrte_tasten")
            
        a = super().config(options)

    config = configure

    def insert(self, pos, text):
        for i in text:
            if i not in self.gesperrte_tasten:
                super().insert(pos, i)

    def copy_in(self, text):
        self.insert("insert", self.clipboard_get())
        return "break"


class Text(tk.Text):
    def __init__(self, master=None, **kwargs):
        if "gesperrte_tasten" in kwargs:
            self.gesperrte_tasten = kwargs.pop("gesperrte_tasten")
        else:
            self.gesperrte_tasten = []
            
        super().__init__(master, **kwargs)
        
        self.bind("<Any-KeyPress>", self.validate)

        self.bind("<Control-v>", self.copy_in)

    def validate(self, event):
        if event.char in self.gesperrte_tasten:
            return "break"

    def configure(self, **options):
        if "gesperrte_tasten" in options:
            self.gesperrte_tasten = options.pop("gesperrte_tasten")
            
        a = super().config(options)

    config = configure

    def insert(self, pos, text):
        for i in text:
            if i not in self.gesperrte_tasten:
                super().insert(pos, i)

    def copy_in(self, text):
        self.insert("insert", self.clipboard_get())
        return "break"



def main():
    fenster = tk.Tk()
    fenster.title("Texttest")

    ttk.Label(fenster, text="Du kannst keine As, Bs und Cs eingeben!").pack()

    entry = Entry(fenster, gesperrte_tasten="abcABC")
    entry.focus_set()
    entry.pack()

    ttk.Label(fenster, text="Und hier auch nicht!").pack()

    text = Text(fenster, gesperrte_tasten="abcABC")
    text.pack()
    
    fenster.mainloop()

if __name__ == "__main__":
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@_Scaui: Die `pop()`-Methode kennt ein Default-Argument, damit kann man sich in der `__init__()` das ``if``/``else`` sparen.

In der `__init__()` einfach so `master.register()` aufrufen geht nicht, denn der Default-Wert davon ist ja `None` und `None` hat keine `register()`-Methode. Da die auf *jedem* Widget existiert, kann man da einfach `self` verwenden.

Das ``if``/``else`` in `Entry.validate()` ist auch überflüssig, denn die Bedingung ergibt einen Wahrheitswert, dessen Gegenteil als Ergebnis zurückgegeben wird. Da kann man einfach die Bedingung umkehren und deren Ergebnis direkt zurückgeben.

Dein `Entry.validate()` verdeckt die originale `ttk.Entry.validate()`-Methode, die dadurch nicht mehr benutzbar ist. Dein `Entry.validate()` sollte vielleicht auch gar nicht zur öffentlichen API gehören.

Deine `configure()`-Methode hat eine andere Signatur und eine leicht andere Semantik als die originale `configure()`-Methode. Und das obwohl Du das sogar beim Aufruf der originalen Methode nutzt, nämlich das man beispielsweise auch ein Wörterbuch mit Optionen als erstes Argument übergeben kann, statt das über Schlüsselwort-Argumente zu machen.

Und da `configure()` auch einzelne Optionen *abfragen* kann, muss man das Ergebnis des originalen Aufrufs auch mit ``return`` zurück geben.

Wenn man das korrigiert, gehen auch lesende und schreibende ``widget["option"]``-Zugriffe wieder.

`i` ist ein ganz schlechter Name für Zeichen und dazu noch in einer Schleife wo `i` eigentlich immer für eine ganze Zahl steht.

Bei `copy_in()`, was vielleicht auch nicht zur öffentlichen API gehören sollte, heisst das nicht verwendete Ereignisobjekt `text`, was ziemlich irreführend ist.

Man sollte nicht implizit `None` zurück geben (lassen). Wenn man ``return`` verwendet, sollte jeder Weg durch die Funktion/Methode auch ein ``return`` mit einem expliziten Rückgabewert haben.

Die originale `tk.Text.insert()`-Methode erlaubt die zusätzliche Angabe eines Tags und noch weitere Zeichen und Tags als Argumente. Mindestens das erste Tag sollte man noch durchreichen, damit das in der Funktionalität nicht zu sehr gegenüber dem Original eingeschränkt wird.

Und dann gibt es mindestens noch `tk.Text.replace()` mit dem man dann doch noch `gesperrte_tasten` umgehen kann.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from tkinter import ttk


class Entry(ttk.Entry):
    def __init__(self, master=None, **kwargs):
        self.gesperrte_tasten = kwargs.pop("gesperrte_tasten", [])
        super().__init__(master, **kwargs)
        self.config(
            validate="key",
            validatecommand=(self.register(self._validate), "%S"),
        )
        self.bind("<Control-v>", self._paste)

    def _validate(self, value):
        return value not in self.gesperrte_tasten

    def configure(self, cnf=None, **options):
        if "gesperrte_tasten" in options:
            self.gesperrte_tasten = options.pop("gesperrte_tasten")

        return super().configure(cnf, **options)

    config = configure

    def insert(self, index, string):
        for character in string:
            if character not in self.gesperrte_tasten:
                super().insert(index, character)

    def _paste(self, _event):
        self.insert("insert", self.clipboard_get())
        return "break"


#
# TODO Look what other methods need to be overridden.  At least `replace()`.
#
class Text(tk.Text):
    def __init__(self, master=None, **kwargs):
        self.gesperrte_tasten = kwargs.pop("gesperrte_tasten", [])
        super().__init__(master, **kwargs)
        self.bind("<Any-KeyPress>", self._validate)
        self.bind("<Control-v>", self._paste)

    def _validate(self, event):
        return "break" if event.char in self.gesperrte_tasten else None

    def configure(self, cnf=None, **options):
        if "gesperrte_tasten" in options:
            self.gesperrte_tasten = options.pop("gesperrte_tasten")

        return super().configure(cnf, **options)

    config = configure

    def insert(self, index, chars):
        #
        # TODO Orginal method allows giving a tag, and additional chars and tags
        # as arguments.
        #
        for character in chars:
            if character not in self.gesperrte_tasten:
                super().insert(index, character)

    def _paste(self, _event):
        self.insert("insert", self.clipboard_get())
        return "break"


def main():
    fenster = tk.Tk()
    fenster.title("Texttest")

    ttk.Label(fenster, text="Du kannst keine As, Bs und Cs eingeben!").pack()

    entry = Entry(fenster, gesperrte_tasten="abcABC")
    entry.focus_set()
    entry.pack()

    ttk.Label(fenster, text="Und hier auch nicht!").pack()

    text = Text(fenster, gesperrte_tasten="abcABC")
    text.pack()

    fenster.mainloop()


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten