This article's download is for AVR for .NET version 14.x or 15.x. To use it with earlier versions you'll need to cut and paste the code (but otherwise the code should work on earlier versions).

Although ASNA Visual RPG for .NET works with Microsoft's  masked input control, that control's behavior is a little finicky and it's most effective with fixed-size inputs only (eg, a US social security number). It doesn't work well for entering numeric values of varying lengths.

This article provides three subroutines you can add to your Windows form projects that pumps up the decimal numeric input capabilities of the standard System.Windows.Forms.TextBox.

The features this code adds are:

  • Numeric-only input (no letters are allowed.)
  • A single minus sign (-) may be entered, but it must be at the start of the number.
  • The decimal length is set explicitly, and enforced, for each TextBox.
  • A single decimal point is allowed (if the decimal length is not zero).
  • Decimal digits input is limited to the number of decimals specified (or inhibited if the decimal length is zero).
  • Special keyboard control keystrokes (such as the tab and backspace key) are allowed
  • The decimal separator is the default culture-specific decimal separator.

The TextBox control has a TextAlign property. Setting this property to Right for numeric input works well with this technique.

The GIF below shows this numeric formatting in action.

Hooking things up

To register a TextBox to this use code, use the provided RegisterTextBoxForFormattedInput method like this from your form's Load event handler:

RegisterTextBoxForFormattedInput(textBox1, 2) 

where the first argument is the name of the TextBox to register and the second argument is the maximum number of decimal places.

The maximum decimal places is stored in the TextBox's Tag property. If you're using the tag property you'll need to refactor where this value is stored.

Add these two Using statments at the top of your class:

Using System.Globalization
Using System.Text.RegularExpressions

Figure 1. The Using statements required for this code.

And add the three subroutines below:

BegSr RegisterTextBoxForFormattedInput
    DclSrParm tb Type(System.Windows.Forms.TextBox) 
    DclSrParm MaxDecimalPlaces Type(*Integer4) 

    tb.Tag =  MaxDecimalPlaces 

    AddHandler SourceObject(tb) + 
               SourceEvent(keypress) +

    AddHandler SourceObject(tb) + 
               SourceEvent(leave) +

Figure 2a. This routine registers TextBox for formatted input.

Call RegisterTextBoxForFormattedInput once for each TextBox that needs this numeric formatting.


BegSr AllowOnlyNumericAndDecimalInput Access(*Private)    DclSrParm sender Type(*Object)
    DclSrParm e Type(System.Windows.Forms.KeyPressEventArgs)

    DclFld nfi Type(NumberFormatInfo)
    DclFld DecimalSeparator Type(*String) Inz('')
    DclFld AllowableCharacters Type(*String) 
    DclFld DecimalDigitsPattern Type(*String) 
    DclFld MaxDecimalPlaces Type(*Integer4) Inz(2) 

    // If max decimals is not zero then add culture-specific 
    // decimal separator to AllowableCharacters.
    // The max decimals is stored in the TextBox's Tag property.   
    If (sender *As TextBox).Tag.ToString() <> '0'
        // Get the decimal separator for the current culture.        
        nfi = CultureInfo.CurrentCulture.NumberFormat 
        DecimalSeparator = nfi.NumberDecimalSeparator

    // Define allowable input characters. 
    AllowableCharacters = '0123456789-' + DecimalSeparator 

    // Create the regular expression pattern than ensures 
    // the number of decimal places entered is less than 
    // or equal to the number of decimal places specified.
    If (sender *As TextBox).Tag <> *Nothing 
        MaxDecimalPlaces = (sender *As TextBox).Tag.ToString()
    DecimalDigitsPattern = String.Format('\{0}\d{{{1}}}$', +
                                         DecimalSeparator, +

    // Allow only control characters (eg backspace) 
    // and AllowableCharacters.
    If NOT System.Char.IsControl(e.KeyChar) AND + 
       NOT AllowableCharacters.Contains(e.KeyChar.ToString()) 
        e.Handled = *True 
    // Allow only one decimal point and one minus sign.
    If e.KeyChar = DecimalSeparator  AND +
       (sender *As TextBox).Text.Contains(DecimalSeparator) OR +
       (e.KeyChar = '-' AND (sender *As TextBox).Text.Contains('-'))
        e.Handled = *True

    // If minus sign is present make sure it is at the 
    // beginning of the input.
    If e.KeyChar.ToString() = '-' AND +  
       NOT (sender *As TextBox).Text = String.Empty
       e.Handled = *True
    // Ensure that the maximum number of decimal places isn't 
    // exceeded. 
    If char.IsDigit(e.KeyChar) AND +
       (sender *As TextBox).Text.Contains('.') AND + 
       Regex.Match((sender *As TextBox).Text, +
        e.Handled = *True

Figure 2b. This routine filters input characters for the TextBox.


BegSr FormatNumericOnLostFocus Access(*Private) 
    DclSrParm sender Type(*Object)
    DclSrParm e Type(System.EventArgs)

    DclFld ValueEntered Type(*Packed) Len(16,12)
    DclFld MaxDecimalPlaces Type(*Integer4) Inz(2) 
    DclFld NumericFormattingString Type(*String)
    If (sender *As TextBox).Tag <> *Nothing 
        MaxDecimalPlaces = (sender *As TextBox).Tag.ToString()

    // The "magic" Fx (where x is the number of decimal places)
    // used in the ToString() method below is .NET numeric
    // formatting parlance for fixed-decimal with MaxDecimalPlaces 
    // places. 
    // Click this Microsoft link to learn more about numeric formatting.
    // If MaxDecimalPlaces = 2 this resolves to 
    // NumericFormattingString = 'F2'

    NumericFormattingString = String.Format('F{0}', MaxDecimalPlaces) 
    ValueEntered = (sender *As TextBox).Text

    (sender *As TextBox).Text = +
          ValueEntered.ToString(NumericFormattingString, +

Figure 2c. This routine sets the value displayed in the TextBox when it loses focus.