Gefilterte Eingabe: InputFilter.au3 (1.1)

  • Hi,

    ich habe in den letzten Tagen eine kleine UDF zusammengebastelt, mit der man einen Filter für ein Input (oder Edit) festlegen kann. So muss die Eingabe nicht im Nachhinein überprüft werden, denn der User hat gar nicht erst die Möglichkeit, eine ungültige Eingabe zu machen.

    Eine ähnliche UDF (Thread im EN-Forum) hat der liebe peethebee schon mal geschrieben, allerdings habe ich einen geringfügig anderen Ansatz gewählt.

    Die übliche Methode für so etwas führt über GUIRegisterMsg, um so die Nachricht EN_CHANGE abzufangen und ggf. den Inhalt des Inputs wieder zurück zu ändern. Dies erschien mir ein wenig ineffektiv, da man eine bereits vorgenommene Änderung wieder rückgängig macht. Daher habe ich einfach die WndProc des eigentlichen Edit-Controls ersetzt und fange dort bereits die Nachricht WM_CHAR ab. So findet die Änderung des Inhalts bei unzulässigen Zeichen gar nicht erst statt. ^^
    Ein weiterer Vorteil meiner Methode besteht in der Nutzung von SetWindowLong: Im selben Skript kann eine eigene GUIRegisterMsg-Funktion ohne jegliche Einschränkung genutzt werden, es sind keinerlei Anpassungen nötig.
    Der wahrscheinlich größte Vorteil meiner UDF liegt allerdings in den logischen Möglichkeiten, wenn man es so nennen möchte. Wo sich andere UDFs oder Funktionen nur auf bestimmte definierte Zeichen oder reguläre Ausdrücke verlassen, wird hier einfach eine Art Entscheidungs-Funktion angegeben, in der die Logik frei programmiert werden kann. Diese Technik garantiert das höchste Maß an Flexibilität. Diese Filter-Funktion wird dann bei jedem relevanten Tastendruck mit zwei Parametern aufgerufen: Das erzeugte Zeichen und der resultierende Gesamtstring. Über den Rückgabewert wird dann bestimmt, ob das Zeichen zulässig ist oder nicht. Ganz einfach und flexibel. ^^

    Dazu gibt es - praktisch als kleine Spielerei - für den Benutzer ein optisches Feedback bei ungültigen Eingaben:

    Wie man sieht, ähnelt das Ganze ein wenig dem Style ES_NUMBER.

    Ich habe auch ein kleines Beispielskript, an dem vielleicht das ein oder andere nochmal veranschaulicht wird:

    InputFilter Example.au3
    [autoit]

    #include "InputFilter.au3"$hMainWnd = GUICreate("InputFilter Example", 200, 30)$cInput = GUICtrlCreateInput("", 0, 0, 200, 30)GUICtrlSetFont($cInput, 15)GUICtrlInputSetFilter($cInput, "InputFilter")GUISetState()While True Switch GUIGetMsg() Case -3 Exit EndSwitchWEndFunc InputFilter($sChr, $sStr) If StringRegExp($sStr, "^\+?(\d{2,4} )?(\d{3} )?\d*$") Then Return $INPUTFILTER_ALLOW ;Simple Handynummer If StringInStr(@CRLF, $sChr) Then Return $INPUTFILTER_SILENTDENY ;"Enter" ist gleichzeitig eine Bestätigung, dabei nervt die Benachrichtigung nur Return $INPUTFILTER_DENYEndFunc ;==>InputFilter

    [/autoit]

    Wie schon erwähnt, wird die Filter-Funktion mit zwei Parametern aufgerufen:

    • Char: Das eingegebene Zeichen
    • String: Der String, der theoretisch im Input stehen würde.

    Die Gültigkeit einer Eingabe wird dann über drei Konstanten als Rückgabewert der Filterfunktion geregelt:

    • $INPUTFILTER_ALLOW: Das Zeichen ist gültig.
    • $INPUTFILTER_DENY: Das Zeichen ist ungültig, der Benutzer erhält eine optische Rückmeldung.
    • $INPUTFILTER_SILENTDENY: Das Zeichen ist ungültig, der Benutzer erhält keine optische Rückmeldung.


    Update :: Version 1.1
    Raupi meinte, dass für Anfänger das Konzept einer Callback-Funktion zu kompliziert sein könnte. Daher habe ich die UDF so erweitert, dass 4 vordefinierte Filtermethoden zur Verfügung stehen. Die Callback-Funktion ist dann die 5. Möglichkeit.

    • Whitelist ($INPUTFILTER_WHITELIST)
      Bei jedem Tastendruck wird überprüft, ob das entstehende Zeichen im Filter-String steht. Wenn ja, dann wird er zugelassen.Beispiel: Filter-String "ABC" - Es dürfen nur die drei Zeichen "ABC" eingegeben werden.
    • Blacklist ($INPUTFILTER_BLACKLIST)
      Bei jedem Tastendruck wird überprüft, ob das entstehende Zeichen im Filter-String steht. Wenn nein, dann wird er zugelassen.Beispiel: Filter-String "ABC" - Es dürfen alle Zeichen außer "ABC" eingegeben werden.
    • RegExp ($INPUTFILTER_REGEXP)
      Bei jedem Tastendruck wird überprüft, ob das entstehende Zeichen einen RegExp-Match bei dem Filter-String als Pattern erzeugt. Wenn ja, dann wird er zugelassen.Beispiel: Filter-String "[A-C]" - Es dürfen nur die drei Zeichen "ABC" eingegeben werden.
    • RegExp All ($INPUTFILTER_REGEXP_ALL)
      Bei jedem Tastendruck wird überprüft, ob der entstehende String einen RegExp-Match bei dem Filter-String als Pattern erzeugt. Wenn ja, dann wird er zugelassen.


    Die Konstante in den Klammern übergibt man zur Auswahl der Arbeitsweise einfach als dritten Parameter an GUICtrlInputSetFilter. Wenn der dritte Parameter weggelassen wird, wird automatisch $INPUTFILTER_CALLBACK genutzt. Der Filter-String fungiert dann wie oben beschrieben als Filter-Funktion, es handelt sich also nicht um ein Script-Breaking-Change. :D

    Beispiel (Handynummer-Beispiel von oben):
    GUICtrlInputSetFilter($cInput, "^\+?(\d{2,4} )?(\d{3} )?\d{0,8}$", $INPUTFILTER_REGEXP_ALL)


    Wer sich noch für das Interne interessiert: Die Verwaltung der fensterbezogenen Daten erfolgt nicht über ein globales Array o.Ä., sondern über das USERDATA-Feld in der Fenstertabelle. Das ist dann im direkten Vergleich mit einem globalen Array "übersauber". :P

    Damit sollte die UDF eigentlich benutzbar sein. Die einzige Funktion (GUICtrlInputSetFilter) sollte wohl selbsterklärend sein. Wenn nicht, dann liegt im angehängten ZIP-Archiv eine Kurzdokumentation im HTML-Format bei. Die UDF selber unterliegt nebenbei der GPLv3, ist auch im Header angemerkt.

    Lob und Kritik sind natürlich immer gern gesehen, solange letzteres konstruktiv ist. ^^

    Gruß,
    chess

    • Offizieller Beitrag

    Super Sache das. Für einen Anfänger hat es nur den Nachteil, das man die Eingabe selber filtern muss. Einfacher wäre es wenn man z.B. Vorgeben benutzen kann oder einen Filter durch angabe der erlaubten Zeichen beim Aufruf von GUICtrlInputSetFilter.

    Z.B.
    GUICtrlInputSetFilter($Control, "A-Z,a-z,0-9")

    Für fortgeschrittene ist es weniger das Problem die Abfrage in der InputFilter Func selbst zu erstellen, aber neulinge werden daran verzweifeln. ;)

  • @Oscar @Kanashius
    Hehe, danke.

    @Raupi
    Man muss die Eingabe nicht selber filtern, man darf. :P
    Erst dieser Mechanismus erlaubt ja die hohe Flexibilität, um die es hier geht. Es wäre zwar einfacher, aber in meinen Augen zu statisch. Eine Kontrolle, die über eine Zeile RegExp hinausgeht, wäre so praktisch unmöglich.
    Man könnte natürlich Whitelist, Blacklist und RegExp als vordefinierte Filtermethoden bereitstellen, da stimme ich dir zu. Hauptaugenmerk liegt dann für mich aber immer noch auf der FilterFunc-Methode. ^^

    • Offizieller Beitrag

    Vordefinierte Filtermethoden wären für non Profis gut, denke ich :)
    99 % der Neulinge sind eh mit RegExp überfordert.

  • So, Update auf 1.1.
    Über den dritten Parameter von GUICtrlInputSetFilter kann nun eine vordefinierte Filtermethode ausgewählt werden, beim Aufruf mit zwei Parametern wird der zweite Parameter weiterhin als Filter-Funktion interpretiert. Nähere Informationen und Beispiele sind dem Startpost zu entnehmen.

  • Sehr nice! :thumbup:

    Ich würde in den Balloon beim Fehlerfall noch die erlaubten, bzw unerlaubten Buchstaben anzeigen. "Es sind nur folgende Zeichen erlaubt: ABCDE" bzw. "Es sind folgende Zeichen nicht erlaubt: ABCDE"

  • Danke! :D
    Klar, könnte man. Bei White- und Blacklist-Filtern macht das auch durchaus Sinn. Aber sobald wir bei regulären Ausdrücken oder gar Callback-Funktionen sind, ist das leider nicht mehr ganz so einfach. Man kann dem Nutzer ja nicht einfach ein RegExp-Pattern anzeigen. :D

    Im Falle der Callback-Funktion könnte man das ganze Problem auf den Programmierer abwälzen, indem man sagt, dass der Rückgabewert, sollte er vom Typ String sein, die Fehler-Nachricht darstellt. So könnte man in meinem Handynummern-Beispiel ein Return "Hier muss eine gültige Handynummer eingegeben werden." setzen und alles wäre gut.

  • Im Falle der Callback-Funktion könnte man das ganze Problem auf den Programmierer abwälzen,

    Dann wälz das ganze Problem doch auf den Programmierer ab! Sind doch schlimmstenfalls nur zusätzliche Parameter der Funktion!
    Eine schicke Fehlermeldung bzw. der Hinweis, was der Anwender falsch gemacht hat, bzw. was er besser anders machen sollte, edelt JEDE Anwendung und "separates the boys from the men". :thumbup:
    Dann bekommt der Anwender eine Info, mit der er etwas anfangen kann:
    Return "Hier muss eine gültige Handynummer im Format +49 6684 98765432 eingegeben werden."

    • Offizieller Beitrag

    Für eine Eingabe von Uhrzeiten würde ich lieber ein Date-Control verwenden.
    Das ist einfach und hat den Vorteil, dass man sich nicht um Fehleingaben kümmern muss (passiert automatisch).

    AutoIt
    #include <DateTimeConstants.au3>
    $hGui = GUICreate('Uhrzeit-Test', 220, 130)
    $idDate = GUICtrlCreateDate('', 60, 50, 100, 30, $DTS_TIMEFORMAT)
    GUICtrlSetFont(-1, 12, 400, 0, 'Arial', 5)
    GUISetState()
    Do
    Until GUIGetMsg() = -3
  • Für eine Eingabe von Uhrzeiten würde ich lieber ein Date-Control verwenden.
    Das ist einfach und hat den Vorteil, dass man sich nicht um Fehleingaben kümmern muss (passiert automatisch).

    AutoIt
    #include <DateTimeConstants.au3>
    $hGui = GUICreate('Uhrzeit-Test', 220, 130)
    $idDate = GUICtrlCreateDate('', 60, 50, 100, 30, $DTS_TIMEFORMAT)
    GUICtrlSetFont(-1, 12, 400, 0, 'Arial', 5)
    GUISetState()
    Do
    Until GUIGetMsg() = -3

    Moin!

    Entschuldigung für meine Off-Topic-Frage, aber kann man bei GUICtrlCreateDate auch einstellen, dass bei Benutzung der UpDown-Controls mit den Sekunden begonnen wird? Standardmässig gehts nämlich bei Stunden los.
    Wusste übrigens garnicht, dass man ein Date-Control auch ohne Date benutzen kann, also nur mit Uhrzeit. Danke für den Hinweis, Oscar!

    • Offizieller Beitrag

    Entschuldigung für meine Off-Topic-Frage, aber kann man bei GUICtrlCreateDate auch einstellen, dass bei Benutzung der UpDown-Controls mit den Sekunden begonnen wird? Standardmässig gehts nämlich bei Stunden los.

    Wenn es per Flag nicht geht (kann es gerade nicht prüfen), bleibt die Variante die Up/Down Message abzufangen und die Werte selbst zu setzen.
    Eventuell kannst du auch den Fokus verschieben. Zwischen den Positionen kannst du mit Shift+Pfeil wechseln.

    • Offizieller Beitrag

    Entschuldigung für meine Off-Topic-Frage, aber kann man bei GUICtrlCreateDate auch einstellen, dass bei Benutzung der UpDown-Controls mit den Sekunden begonnen wird? Standardmässig gehts nämlich bei Stunden los.

    Das geht auch:

    AutoIt
    #include <DateTimeConstants.au3>
    $hGui = GUICreate('Uhrzeit-Test', 220, 130)
    $idDate = GUICtrlCreateDate('', 60, 50, 100, 30, $DTS_TIMEFORMAT)
    GUICtrlSetFont(-1, 12, 400, 0, 'Arial', 5)
    GUISetState()
    ControlSend($hGui, '', $idDate, '{RIGHT}' & '{RIGHT}')
    Do
    Until GUIGetMsg() = -3
  • Hallo zusammen

    erlaubt mir, dieses Thema nochmals aufzuwärmen.

    Dieses UDF ist genau was ich schon für mehrere Programme gesucht und bis jetzt nie gefunden hatte. :thumbup:
    Leider jedoch führt es unter Windows Server 2012 zu zwei Absturzmeldungen. Interessanterweise erst beim Beenden des Programms und unabhängig von der Laufzeit. Bevor ihr diese UDF auf dieser Plattform in Betrieb nehmt, solltet ihr euch dessen bewusst sein. Wie es mit Windows 10 aussieht, kann ich leider nicht sagen, mir steht gerade kein System zum Testen zur Verfügung, würde aber mit demselben Resultat rechnen. Die genaue Ursache, Workaround oder gar einen Bugfix kann ich euch ebenfalls noch nicht präsentieren. :/

    Übrigens muss ich Raupi Recht geben, ich habe lieber die verschollene Version 1.1 nachgebaut, als mir RegExp genauer anzuschauen. Die offizielle Version 1.1 ist aber sicher besser als mein Konstrukt.

    Grüsse
    ncx