ValidationTextBox

TextBox & (IntegerBox | DecimalBox)

esskar über Programmierung

Wer schon einmal eine GUI in einem seiner Projekte angeboten hat, wird an der TextBox nicht vorbei gekommen sein. .NET liefert ab Version 1 ein TextBox Control im Namespace System.Windows.Forms mit.
Schaut man sich die nachfolgende Definition an, kann man herauslesen, dass eine TextBox eine Zeichenkette aufnehmen kann.

Eine TextBox ist ein Steuerelement einer grafischen Benutzeroberfläche (GUI), das Benutzereingaben oder Programmausgaben in Form einer Zeichenkette aufnehmen kann. In den meisten Programmiersprachen und Entwicklungsumgebungen wird zwischen ein- und mehrzeiligen Textfeldern unterschieden.

In .NET heißt dies dann auch, dass jedes Zeichen aus dem Unicode Zeichnsatz in einer TextBox landen kann. Eine solche riesige Auswahl lässt dem Nutzer auch oft die Möglichkeit, sich einfach zu vertippen oder hinterhältigerweise eine absichtliche Fehleingabe zu tätigen. Beispiel:

Textbox - Wie alt bist du?

Solch eine Eingabe zu verstehen bzw. zu verarbeiten ist recht schwierig und auch nicht im Sinne des Erfinders. Also muss man dem Benutzer erst wieder vorhalten, das er bitte nur ganze Zahlen als Ziffern einzugeben hat, blablabla. Geschickter wäre es aber, wenn man dem Benutzer gar nicht erst die Möglichkeit lassen würde, etwas anderes als Ziffern einzugeben. So beugt man von vornherein Falscheingaben vor und erstickt den DAU im Keim.

Und Gott erschuf…

die ValidationTextBox! — Ableiten hilft. Auch in diesem Fall sogar 3-fach. Als Grundbaustein steht natürlich die System.Windows.Forms.TextBox selber. Daruaf bau ich dann folgende Controls auf:

  • ValidationTextBox
  • CharValidationTextBox
  • IntegerBox

public abstract class ValidationTextBox : TextBox
{
    public ValidationTextBox(bool allowEmpty) 
    {
        this.AllowEmpty = allowEmpty;
    }
   
    public bool AllowEmpty { get; private set; }
}

Okay. Wir bauen uns eine generische Klasse, die von der TextBox ableitet. Die AllowEmpty Property hilft uns später bei der Entscheidung, ob wir eine leeres Feld zulassen oder nicht.


protected abstract bool OnCharValidation(char c);
protected abstract bool OnTextValidation(string text);

Zwei protected Funktionen, die uns helfen werden, die Zeichen zu validieren. OnCharValidation validiert einzelne Zeichen (z.B. bei der Eingabe) und OnTextValidation die komplette Zeichenkette.

Dann ein paar Getter und Setter Methoden und Properties.


public T Value
{
    get { return this.GetValue(); }
    set { this.SetValue(value); }
}

public abstract T GetValue();

public virtual void SetValue(T value)
{            
    this.ValidateAndSetText(value.ToString());          
}

private void ValidateAndSetText(string text)
{
    base.Text = "";
    if (this.OnTextValidation(text))
    {
        foreach (char c in text)
        {
            if (this.OnCharValidation(c))
                base.Text += c;
        }
    }
}

Hierbei ist die Funktion ValidateAndSetText zu beachten. Die eine Zeichenkette eben als ganzes checkt und dann auch nochmal jedes einzelne Zeichen.

Als letztes überschreiben wir noch die Funktion OnKeyPress Funktion, die immer dann aufgerufen wird, wenn der Nutzer eine Taste drückt (gedrückt hält).
Auch dort muss die Eingabe validiert werden und nur gültige Zeichen werden dann an die TextBox zur Darstellung gegeben


protected override void OnKeyPress(KeyPressEventArgs e)
{            
    base.OnKeyPress(e);
    if (!e.Handled)
    {
        string text = this.Text;
        if (this.SelectionLength > 0)
            text = text.Remove(this.SelectionStart, this.SelectionLength);
        text = text.Insert(this.SelectionStart, e.KeyChar.ToString());
        e.Handled = e.KeyChar != 8
            && (this.OnCharValidation(e.KeyChar) && this.OnTextValidation(text)) == false;
    }
}

ValidationTextBox: Check!

Als nächstes bauen wir uns eine CharValidationTextBox. Was muss die können?

  • eine Zeichenkette gegen einen vordefinierte Range von Zeichen validieren
  • WhiteList und BlackList Validierung

WhiteList und BlackList Validierung? Das kennt man ja z.B. schon bei SPAM. Entweder man validiert z.B. eine Absender Adresse einer E-mail gegen einen Satz von E-mail Adressen, denen man explizit vertraut (White-List) oder man schaut, ob die Absender Adresse in einer Liste von Adressen ist, die man auf keinen Fall vertraut (Black-List).


public abstract class CharValidationTextBox : ValidationTextBox
{
    public CharValidationTextBox(bool allowEmpty)
        : this(null, false, allowEmpty) { }

    public CharValidationTextBox(T value, bool allowEmpty)
        : this(value, null, false, allowEmpty) { }

    public CharValidationTextBox(T value, char[] chars, bool allowed, bool allowEmpty)
        : this(chars, allowed, allowEmpty) { this.SetValue(value); }

    public CharValidationTextBox(char[] chars, bool allowed, bool allowEmpty)
        : base(allowEmpty)
    {
        if (chars != null && chars.Length > 0)
            this.CharValidationRange = (char[])chars.Clone();
        this.AllowCharValidationRange = allowed;
    }

    protected override bool OnCharValidation(char c)
    {
        bool isone = false;
        if (this.CharValidationRange != null)
            isone = Array.IndexOf(this.CharValidationRange, c) > -1;
        return this.AllowCharValidationRange ? isone : !isone;
    }

    protected override bool OnTextValidation(string text) { return true; }

    public bool AllowCharValidationRange
    {
        get; private set;
    }

    public char[] CharValidationRange
    {
        get; private set;
    }
}

Okay. Das Grundgerüst ist fertig. Jetzt wenden wir uns zum einfachen Teil (wobei der erste Teil auch nicht so schwer war, oder?)

Die IntegerBox …

… ist recht schnell definiert. Deswegen spare ich mir jetzt große Worte


[System.ComponentModel.ToolboxItem(true)]
public class IntegerBox : CharValidationTextBox
{
    public IntegerBox()
        : base(new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }, true, true) { }

    public override int GetValue()
    {
        return string.IsNullOrEmpty(this.Text) ? 0 : Convert.ToInt32(this.Text);
    }
}

Plumps. Einfach oder?
Nach erfolgreichem Build, sollte die IntegerBox dank [System.ComponentModel.ToolboxItem(true)] in der ToolBox zu finden sein, und kann einfach in bestehende Forms gezogen und benutzt werden.

…Trommelwirbel

Da ich gerade so schön in Fahrt bin, kommen nun 2 weiter Highlights:

RegexValidationBox

Trommelwirbel…

und die darausfolgende

DecimalBox

Also:
Die RegexValidationBox validiert die Zeichenkette gegen eine Regex. Dadurch hat man ein recht mächtiges Werkzeug, den Input-Text verlässlich zu validieren.


public abstract class RegexValidationBox : ValidationTextBox
{
    private Regex m_regex;

    public RegexValidationBox(Regex regex, bool allowEmpty)
        : base(allowEmpty)
    {
        if (regex == null)
            throw new ArgumentNullException("regex");
        m_regex = regex;
    }

    public RegexValidationBox(T value, Regex regex, bool allowEmpty)
        : this(regex, allowEmpty)
    {
        if(!value.Equals(default(T)))
            this.SetValue(value);
    }

    protected override bool OnCharValidation(char c) { return true; }

    protected override bool OnTextValidation(string text)
    {
        bool rc = true;
        if (string.IsNullOrEmpty(text))
        {
            text = "";
            rc = this.AllowEmpty;                    
        }
        return rc && m_regex.Match(text).Success;            
    }
}

Diese ValidationTextBox-Variante ist ebenfalls recht simple. Gekümmert wird sich nur um den Text als solches (nicht aber um die einzelnen Zeichen, siehe zum Vergleich die CharValidationTextBox Klasse). Mit ihrer Hilfe bauen wir uns jetzt die versprochene DecimalBox.


[System.ComponentModel.ToolboxItem(true)]
public class DecimalBox : RegexValidationBox
{
    private static Regex s_decimalRegex = new Regex(
        @"^[\+\-]?[0-9]+\" 
        + CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator 
        + "?[0-9]*$", 
        RegexOptions.Compiled
    );

    public DecimalBox()
        : this(default(decimal)) { }

    public DecimalBox(decimal value)
        : base(value, s_decimalRegex, true) { }

    public override decimal GetValue()
    {
        return string.IsNullOrEmpty(this.Text) ? 0m : Convert.ToDecimal(this.Text);
    }        
}

Das komplizierteste an dieser Klasse ist vielleicht der Reguläre-Ausdruck, aber mit ein bissle Üben klappt auch das!

Informationen über den Autor

esskar

ist das, was man einen Fullstack Developer bezeichnet. Vom Design bis in die Tiefen der C-Programmierung verfügt er nationale und internationale Projekterfahrung, und alles unter dem Motto — Code ist Poesie. Follow @esskar