15,610,517 members
Articles / Desktop Programming / WPF
Article
Posted 9 Feb 2020

4.4K views
4 bookmarked

A WPF Text Control with Chilean RUT Mask

Rate me:
A Windows Presentation Foundation TextBox control with chilean tax ID mask, and its validation through modulus 11 algorithm.
Given that RUT format responds to pattern NN.NNN.NNN-C, and I wished to prevent the user from putting any value that didn't correspond; I decided to create my first custom control from a TextBox to accept only the allowed values, besides giving the RUT mask automatically. This post shows how to create the control, define properties, handle behavior of the control, and finally tell us how to use the code.

Introduction

Since the past few months, I'm working on a desktop app that requires, in various forms, the chilean tax ID (known as RUT) of customers and suppliers as a user input. Given that RUT format responds to pattern NN.NNN.NNN-C, and I wished to prevent the user from putting any value that didn't correspond; I decided to create my first custom control from a `TextBox` to accept only the allowed values, besides giving the RUT mask automatically.

Background

RUT (Rol Único Tributario) is the unique number utilized as tax ID in Chile. This is a 7 to 8 digits number, plus an extra digit (can be a number from 0 to 9, or letter 'K'), that corresponds to check digit, which is obtained through modulus 11. RUT is usually written with an hyphen that splits the 7 to 8 length number (left side), from check digit (right side).

Modulus 11

Modulus 11 is a mathematical algorithm for check data integrity in a sequence of numbers. This algorithm returns a value between 0 and 11, which is called check digit, and it is used to validate the sequence. Usually, the last digit of a sequence in an identification number (like RUT) is the check digit.

The steps to calculate the check digit through modulus 11 are the following:

1. Get the sequence of numbers without the check digit and reverse it.
`18798442 -> 24489781`
2. Multiply each digit of the reversed number using the following factor pattern: 2, 3, 4, 5, 6, 7. If the sequence is longer than six digits, repeat the pattern.
 Value Factor Result 2 4 4 8 9 7 8 1 x2 x3 x4 x5 x6 x7 x2 x3 =4 =12 =16 =40 =54 =49 =16 =3
3. Sum the products obtained in the last step (2).
`4 + 12 + 16 + 40 + 54 +49 + 16 + 3  = 194`
4. Get the division remainder between 1) the result of the sum in step 3, and 2) 11.
`194 % 11 = 7`
5. To 11, subtract the remainder of step 4. The result of subtraction is the check digit.
`11 - 7 = 4`
6. Extra step for chilean RUT: If the result of subtraction is 10, the check digit value is 'K', and if is 11, the value is 0.

Creating the Control

First, I created a new control that inherits from `TextBox`, which I decided to call `RutBox`:

C#
```public class RutBox : TextBox
{

}```

So, this control has the same properties of `TextBox`. After that, I defined the fields I'd use in the code, starting to declare a constant string containing the hyphen used as separator between number and check digit:

C#
`private const string ComponentSeparator = "-";`

Also, a `string` to set the culture name to use later with the `CultureInfo` class:

C#
`private const string CultureName = "es-CL";`

The minimum and maximum length allowed for RUT (including the check digit):

C#
```private const int MaxLengthAllowed = 9;

private const int MinLengthAllowed = 8;```

The pattern of RUT and Regex option of ignore case (considering the letter '`K`'):

C#
```private const string Pattern = @"^[0-9]+K?\$";

private const RegexOptions RegexPatternOption = RegexOptions.IgnoreCase;```

On the other hand, I've declared two readonly fields: one to get the `CultureInfo`, and another to get the thousands separator from `RutCulture` and use it to format the number component.

C#
```private readonly CultureInfo RutCulture;

Finally, I declared a field to display or hide the thousands separator:

C#
`private bool showThousandsSeparator;`

Defining Properties

The first property I've created was `Value`, which gets the text of `TextBox` and calls `GetRutWithoutSeparators()` to remove the group separator and component separator from the input. Also sets the value only if it matches with the pattern, otherwise returns an exception (by the way, if the value is `null`, it changes to an empty `string`).

C#
```public string Value
{
get
{
return GetRutWithoutSeparators(this.Text);
}
set
{
//Sets empty string if value is null.
value = value ?? string.Empty;
if (!Regex.IsMatch(value, Pattern, RegexPatternOption) && value != string.Empty)
{
throw new ArgumentException("Value is not valid.", "Value");
}
else
{
this.Text = value;
}
}
}```

Defined here is the `GetRutWithoutSeparators()` method, whose only parameter is a `string` that contains RUT with separators.

C#
```private string GetRutWithoutSeparators(string rutWitSeparators)
{
rutWitSeparators = rutWitSeparators.Replace(GroupSeparator, string.Empty).Replace
(ComponentSeparator, string.Empty);
return rutWitSeparators;
}```

The other property is `IsValid`. This one returns `true` if the value is between the minimum and maximum allowed length, and meets with the pattern of RUT, otherwise will be `false`. For validating the string, it is necessary to split the RUT in two parts: the left side, with the number, and right side, with the check digit; then get the check digit using `GetModulus11CheckDigit()` and compare it with the check digit obtained splitting the RUT.

C#
```public bool IsValid
{
get
{
if (Regex.IsMatch(this.Value, Pattern, RegexPatternOption) &&
this.Value.Length >= MinLengthAllowed && this.Value.Length <= MaxLengthAllowed)
{
long rutWithoutCheckDigit =
long.Parse(this.Value.Substring(0, this.Value.Length - 1));
string checkDigit = this.Value.Substring(this.Value.Length - 1, 1);
return checkDigit ==
this.GetModulus11CheckDigit(rutWithoutCheckDigit) ? true : false;
}
else
{
return false;
}
}
}```

The `GetModulus11CheckDigit()` method needs an integer value to apply the algorithm, returning a `string` that represents the check digit, and replacing it with `0` if is `11`, or '`K`' if is `10`:

C#
```private string GetModulus11CheckDigit(long number)
{
long sum = 0;
int multiplier = 2;
//Get each digit of the number.
while (number != 0)
{
//Check if multiplier is between 2 and 7, otherwise reset to 2.
multiplier = multiplier > 7 ? 2 : multiplier;
//Get the last digit of the number, multiply and add it.
sum += (number % 10) * multiplier;
//Remove last number from right to left.
number /= 10;
//And increase multiplier by 1.
multiplier++;
}

sum = 11 - (sum % 11);
//Evaluate the result of the operation to get the check digit.
switch (sum)
{
case 11:
return "0";
case 10:
return "K";
default:
return sum.ToString();
}
}```

The last property created is `ShowThousandsSeparator`, giving the possibility to display or hide the group separator:

C#
```public bool ShowThousandsSeparator
{
get
{
return showThousandsSeparator;
}
set
{
showThousandsSeparator = value;
}
}```

Handling Behavior of the Control

Now it's time to think how the control is going to behave when:

1. Show the mask when the control has the focus and hide it when it loses focus
2. User enters a value typing in the keyboard
3. User enters a value pasting it

First, we must define the constructor, where we add a handler for pasting and text changed events, the C`haracterCasing` property is defined like uppercase by default, the `MaxLength` property is the maximum allowed for RUT, `RutCulture` member is initialized with "`es-CL`" value, `showThousandsSeparator` is set to `true`, and the `Value` property is set to empty string.

C#
```public RutBox()
{
this.CharacterCasing = CharacterCasing.Upper;
this.MaxLength = MaxLengthAllowed;
this.RutCulture = new CultureInfo(CultureName);
this.GroupSeparator = RutCulture.NumberFormat.NumberGroupSeparator;
this.TextChanged += this.RutBox_TextChanged;
this.showThousandsSeparator = true;
this.Value = string.Empty;
}```

For the mask, I created a method called `UseMask()`, where it checks if `RutBox` has the focus. If `IsFocused` is `false`, it tries to apply the format to the text. It's important to unsubscribe the `TextChanged` event while making the changes, or when we assign a new value to `Text` property, the event handler will be called.

C#
```private void UseMask()
{
//It's necessary to unsubscribe TextChanged event handler
//while setting a value for Text property.
this.TextChanged -= this.RutBox_TextChanged;
if (this.IsFocused)
{
//If control is Focused, show chilean RUT without separators.
this.Text = this.Value;
}
else
{
//But if the control isn't focused, show chilean RUT with separators.
if (this.Value.Length > 1)
{
bool isValidNumber = long.TryParse(this.Value.Substring(0, this.Value.Length - 1),
NumberStyles.Any, RutCulture, out long rutWithoutCheckDigit);
if (isValidNumber)
{
//If left component is a valid number,
//the displayed text in the control will correspond to NN.NNN.NNN-C pattern.
string checkDigit = this.Value.Substring(this.Value.Length - 1, 1);
this.Text = string.Join(ComponentSeparator,
string.Format(RutCulture, "{0:N0}", rutWithoutCheckDigit), checkDigit);
//If showThousandsSeparator is false, the text won't display the separator.
this.Text = showThousandsSeparator ? this.Text :
this.Text.Replace(GroupSeparator, string.Empty);
this.SelectionStart = this.Text.Length;
}
else
{
this.Text = string.Empty;
}
}
}
//Don't forget to subscribe again to TextChanged event handler
//after changing Text property.
this.TextChanged += this.RutBox_TextChanged;
}```

So, to hide the mask, we override `OnGotFocus()` and call the base method and `UseMask()` method.

C#
```protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
}```

And, to show the mask, we override `OnLostFocus()` and call the base method plus `UseMask()` method.

C#
```protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
}```

On the other hand, we must call `UseMask()` when the text changes. This is to cover the case where the value is changed manually in the code.

C#
```private void RutBox_TextChanged(object sender, EventArgs e)
{
}```

For user input from keyboard, we override `OnPreviewTextInput()` and call the base method associated (again), and get the character entered. With the character, we'll validate if it is a number, the letter '`K`' or a control character, and covering other cases.

C#
```protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
base.OnPreviewTextInput(e);
char characterFromText = Convert.ToChar(e.Text);
//Cancels the character if isn't a number, letter 'K' or a control character.
if (!char.IsDigit(characterFromText) &&
!char.Equals(char.ToUpper(characterFromText), 'K') && !char.IsControl(characterFromText))
{
e.Handled = true;
}
//Cancels the character if caret is not positioned at the end of text and is a letter 'K'.
else if (this.SelectionStart != this.Text.Length &&
char.Equals(char.ToUpper(characterFromText), 'K'))
{
e.Handled = true;
}
//Cancels the character if caret is positioned at the end of text
//and this contains 'K', and the key pressed is a number or letter 'K'.
else if (this.SelectionStart == this.Text.Length &&
this.Text.ToUpper().Contains("K") && (char.IsDigit(characterFromText) ||
char.Equals(char.ToUpper(characterFromText), 'K')))
{
e.Handled = true;
}
}```

Finally, if the user pastes the value to `RutBox`, we check in the first instance if is a `string`, and then verify if the value matches with the RUT pattern. If it is valid, the value will be pasted to `RutBox`, but if is not, it won't be pasted.

C#
```private void RutBox_OnPaste(object sender, DataObjectPastingEventArgs e)
{
bool isText = e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true);
if (isText)
{
string rut = e.SourceDataObject.GetData(DataFormats.UnicodeText) as string;
e.CancelCommand();
rut = GetRutWithoutSeparators(rut);
if (Regex.IsMatch(rut, Pattern, RegexPatternOption))
{
this.Text = rut;
this.SelectionStart = this.Text.Length;
}
}
}```

Using the Code

`RutBox` behaves as any other WPF control. You can drop it on a window and set the properties you want to modify.

Properties

The following properties are the control-specific properties that can be used:

Property Name Type Category Description
`IsValid` `bool` Data Indicates whether the value is a valid chilean RUT using modulus 11.
`ShowThousandsSeparator` `bool` Appearance Indicates whether the thousands separator will be displayed in the control when this loses the focus.
`Value` `string` Data Value of chilean RUT without dots or hyphen.

History

• 9th February, 2020: Version 1.0

Written By
Engineer
Chile
University of Santiago, Chile (USACH).

I started programming in 2013, using Python in first instance, but my enthusiasm for programming increased in 2015 with C#, using Windows Forms, and modelling relational databases with MS Access and MariaDB DBMS.

 For me, as you describe what RUT is, it looks like the regex C# `private const string Pattern = @"^[0-9]+K?\$";` is wrong: ```Since a few months ago I'm working in a desktop app that requires, in various forms, the chilean tax ID (known as RUT) of customers and suppliers as an user input. Given that RUT format responds to pattern NN.NNN.NNN-C, and I wished avoid that the user put any value that didn't corresponds; I decided to create my first custom control from a TextBox to accept only the allowed values, besides giving the RUT mask automatically. ... RUT (Rol Único Tributario) is the unique number utilized as tax ID in Chile. This is a 7 to 8 digits number, plus an extra digit (can be a number from 0 to 9, or letter 'K'), that corresponds to check digit, which is obtained through modulus 11. RUT is usually written with an hyphen that splits the 7 to 8 length number (left side), from check digit (right side).``` Regex must be someting more like: C# `private const string Pattern = @"^(\d{1,3}(\.?\d{3}){2})\-?([\dkK])\$` Testmodified 10-Feb-20 16:34pm.