Laden...
FAQ

[FAQ] In einer TextBox nur bestimmte Zeichen/Eingaben zulassen

Erstellt von herbivore vor 17 Jahren Letzter Beitrag vor 17 Jahren 57.501 Views
herbivore Themenstarter:in
49.485 Beiträge seit 2005
vor 17 Jahren
[FAQ] In einer TextBox nur bestimmte Zeichen/Eingaben zulassen

Hallo Community,

hier findet ihr (mehr als) eine Antwort für die Aufgabe "in einer TextBox nur bestimmte Zeichen zulassen", "in TextBox nur gültige Eingaben erlauben" oder "Eingabe einer TextBox prüfen".

Ich habe mich hier schon öfter im Forum zu diesen Themen geäußert und aus diesen Beiträgen mal folgendes zusammengetragen. Dadurch gibt es zwar ein paar Wiederholungen, aber bis zum Ende auch immer wieder neue Aspekte. Dabei habe ich meine Zitate redaktionell leicht überarbeitet:

Oft ist die erste Überlegung, in KeyDown für alle nicht erlaubten Zeichen e.Handled auf true zu setzen. Aber ganz so einfach ist das nicht, weil es ja noch eine ganze Reihe andere Möglichkeiten gibt, ein bestimmtes Zeichen ins Eingabefeld zu bekommen, außer die zugehörige Taste zu drücken.

Eine Möglichkeit wäre die Zwischenablage. Eine andere den Zeichencode per Alt einzugeben. Um ein großes A einzugeben kann man auch folgende machen: NumLock an, Alt drücken, festhalten, Num6, Num5, Alt loslassen. Gibt sicher noch andere Möglichkeiten.

Bis man da alle Möglichkeiten gefunden und abgedichtet hat, ist es doch einfacher und sicherer auf TextChanged zu reagieren.

Auf der anderen Seite ist der Ansatz nur die "richtigen" Zeichen bei KeyDown o.ä. zu zulassen zu restriktiv ist. So werden können z.B. die Cursortasten (und nicht nur die) funktionsunfähig werden.

Meine TextBox macht das quasi anders herum. Erstmal alles zulassen und nur bei Textänderungen gucken, ob sich ein ungewolltes Zeichen eingeschlichen hat. Wenn ja, den vorigen Zustand wiederherstellen. (Leider muss man für diesen vorigen Zustand dann doch wieder an KeyDown/KeyUp lauschen.)

[Code der TextBox am Ende dieses Beitrags]

Weil allerdings jeweils der ganze Text auf ungültige Zeichen abgesucht wird, eignet sich die Box für lange Texte (Multiline) wohl eher nicht.

Die Veränderung einer TextBox im KeyDown-Event verhindern zu wollen, ist ein Kampf gegen Windmühlen. Was nützt es dort ein bestimmtes Zeichen abzufangen, wenn man das Zeichen einfach nur in die Zwischenablage packen und mit Strg-V einfügen muss, um es doch in die TextBox zu bekommen? Und wenn man Strg-V abfängt (Kanonen/Spatzen), dann man man immer noch über das Kontextmenü der TextBox einfügen. Und wenn man das abgefangen hat, kann der Benutzer das Zeichen weiterhin über Alt+Zehnerblock+Zeichencode in die TextBox bekommen. Und wenn man das abgefangen hat, kommen die Benutzer, die mit Handschriftenerkennung oder Spracheingabe arbeiten. Dann muss man auch die entsprechenden Events abfangen.

Fazit: Der Versuch eine TextBox eingabeseitig zu schützen, ist zum Scheitern verurteilt.

Ansonsten kann ich hier nur meine schon öfter vertretene Meinung anbringen, dass es nicht sinnvoll ist, die Eingaben abzufangen, weil es einfach zu viele Möglichkeiten gibt, z.B. wird oft nicht berücksichtigt, dass man auch über die Zwischenablage Text einfügen kann. Und wenn man Strg-V abgefangen hat, stellt man fest, dass man ja Text auch über rechte Maustaste einfügen kann. Wenn man das festgestellt hat, merkt man irgendwann, dass mal über ALT-Nummernblock Zeichen auch über den Zeichencode eingeben kann. Also auf der Eingabeseite anzusetzen, ist heikel. Und selbst wenn man es schaffen sollte, alle Möglichkeiten abzufangen, wer sagt einem, dass es in der nächsten Windows-Version nicht neue Eingabemöglichkeiten gibt, z.B. über Handschriftenerkennung oder Mausgesten.

Eine weitere Gefahr bei der Eingabe anzusetzen, ist zuviel zu verbieten, also auch sinnvolle Aktionsmöglichkeiten zu verhindern. Also z.B. Ctrl-V vollständig zu sperren, obwohl man es nur nun müsste, wenn der eingefügte Text zuviel Zeilen hat.

Also muss man am Ergebnis ansetzen: im TextChanged-Event gucken, ob eine ungültiges Ergebnis vorliegt, und dann den vorhergehenden Zustand wiederherzustellen. Den vorhergehenden Zustand vorher zu sichern ist auch nicht ganz trivial, aber immer noch wesentlich leichter als auf der Eingabeseite anzusetzen. Leider gibt es keinen Event BeforeTextChanged o.ä. mit dem man eine Änderung leicht verhindern könnte.

Eine andere sinnvolle Möglichkeit sehe ich darin, den Benutzer erstmal so viele Zeilen eingeben zu lassen, wie er will und wenn er mehr eingibt, zu verhindern, dass er das Feld verlassen kann, bevor er den Inhalt korrigiert hat (Stichwort: Validating-Event). Eine noch entspanntere Möglichkeit ist, beim Verlassen nur eine Warnung (z.B. per ErrorProvider) auszugeben und wenn der Benutzer OK drückt, die Warnung zu wiederholen und das OK solange zu ignorieren, bis der Inhalt korrigiert ist.

Es gibt zwei Varianten:

  1. verhindern, dass der Benutzer überhaupt etwas falsches eingeben kann und
  2. den Benutzer eingeben lassen was er will und ihm später ggf. auf die Finger zu klopfen.

Wenn man sich erstmal dazu durchgerungen hat, die erste Variante aufzugeben, findet man in OnValidating durchaus eine elegante Lösung. Und die Probleme im Vergleich zur ersten Variante sind m.E. doch um einiges geringer.

Es gibt übrigens ein Problem bei der ersten Variante, dass meist übersehen wird. Der Benutzer hat - außer über die (oft nicht vorhandene) Online-Hilfe oder das (ebenfalls nicht selbstverständliche) Handbuch - keine Chance herauszufinden, warum er etwas bestimmtes nicht eingeben kann. Es piept halt bestenfalls nur blöd. Bei OnValidating ist es um einiges einfacher ihm in der ohnehin notwendigen Meldung (nicht Fehlermeldung, positives Denken 🙂 , Informationen zu geben, in welcher Art er seine Eingabe verändern muss.

Bei alle dem sollte man auch noch berücksichtigen, dass während der Eingabe und besonders beim Editieren eines am Ende gültigen Werts oft ungültige Zwischenzustände entstehen können. Wenn man erzwingen will, dass zu jedem Zeitpunkt in der TextBox ein gültiger Wert ist, dann kann dadurch die Eingabe bzw. das Editieren sehr erschwert oder im Extremfall gar verhindert werden. Auch das spricht dafür, den Wert erst beim Verlassen des Feldes (oder noch später) zu prüfen.

Für rein nummerische Eingaben gibt es außerdem das NumericUpDown-Control und für Eingaben in einem festem Format das MaskedTextBox-Control.

herbivore

Hier der angekündigte Code (inkl. Verbesserungen von winSharp93):


using System;
using System.Text;
using System.Windows.Forms;

//*************************************************************
public class AllowedCharsTextBox : TextBox
{
   //----------------------------------------------------------
   private String _allowedChars             = "-0123456789,";
   // Um von einer bestimmten Kultur unabhängig zu sein, kann man statt "-" und "," besser
   // System.Globalization.NumberFormatInfo.CurrentInfo.NegativeSign und
   // System.Globalization.NumberFormatInfo.CurrentInfo.NumberDecimalSeparator
   // verwenden
   private String _lastValidText            = "";
   private int    _lastValidSelectionStart  = 0;
   private int    _lastValidSelectionLength = 0;
   private bool   _validating               = false;

   //==========================================================
   public String AllowedChars
   {
      get { return _allowedChars; }
      set {
         _allowedChars = value;
         Text = Text; //Text von nun ungültigen Zeichen "bereinigen"
      }
   }

   //==========================================================
   public override String Text
   {
      get { return base.Text; }
      set {
         StringBuilder onlyValid = new StringBuilder ();

         foreach (char ch in value) {
            if (_allowedChars.Contains (ch.ToString ())) {
               onlyValid.Append (ch);
            }
         }

         base.Text = onlyValid.ToString ();
      }
   }

   //==========================================================
   protected override void OnTextChanged (EventArgs e)
   {
      if (_validating) {
         return;
      }
      try {
         _validating = true;

         foreach (char ch in base.Text) {
            if (!_allowedChars.Contains (ch.ToString ())) {
               base.Text       = _lastValidText;
               SelectionStart  = _lastValidSelectionStart;
               SelectionLength = _lastValidSelectionLength;
               return;
            }
         }
         _lastValidText = base.Text;
         _lastValidSelectionStart  = SelectionStart;
         _lastValidSelectionLength = SelectionLength;
         base.OnTextChanged (e);
      }
      finally {
         _validating = false;
      }
   }

   //==========================================================
   protected override void OnClick (EventArgs e)
   {
      _lastValidSelectionStart  = SelectionStart;
      _lastValidSelectionLength = SelectionLength;
      base.OnClick (e);
   }

   //==========================================================
   protected override void OnKeyDown (KeyEventArgs e)
   {
      if (base.Text == _lastValidText) {
         _lastValidSelectionStart  = SelectionStart;
         _lastValidSelectionLength = SelectionLength;
      }
      base.OnKeyDown (e);
   }

   //==========================================================
   protected override void OnKeyUp (KeyEventArgs e)
   {
      if (base.Text == _lastValidText) {
         _lastValidSelectionStart  = SelectionStart;
         _lastValidSelectionLength = SelectionLength;
      }
      base.OnKeyUp (e);
   }
}

PS: Der Code und die im Text genannten Events beziehen sich auf Windows Forms. Das Gesagte gilt jedoch für alle für Windows verfügbaren GUI-Technologien analog.