Click here to Skip to main content
15,881,380 members
Articles / Desktop Programming / WPF

International Phone Number Validation: Explained in Detail, Implemented with WPF TextBox

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
9 Apr 2020Public Domain7 min read 18.2K   6  
A WPF Textbox which can validate international and national phone numbers depending on your needs, plus detailed explanation of the structure of phone numbers.
Validating phone numbers is difficult, because the telephony authorities were too creative in how they used the limited available number range and because phone numbers look different in various countries. This article describes how phone number input validation can be reasonably done and provides a Control with production quality code, including letting the user enter only valid keystrokes, controlling if the phone number must be entered (required field) and alerting the user if he tries to close the window without saving the changes made. This control is part of the WpfWindowLib.

Table of Contents

Introduction

Writing a WPF control which validates phone numbers is a challenge, because the same phone number looks differently depending from which country it is dialled. The control must be flexible enough to meet your requirements, i.e., if you need strict control with precise formatting or if you just want to alert the user if he keyed in a funny looking phone number. Of course, best is if you prevent the user from making invalid input, and control which keystrokes he can make. Since this control is part of WpfWindowLib, it doesn't let the user save the data if the phone number is required and missing. It also informs the user if he tries to close the window without saving the data.

Phone Number Format

How does a phone number look like? This really depends on where you live and in which year.

When I grew up near Zürich, phone numbers were short and simple

34 56 78 fictitious phone number dialled locally in Zürich
01 34 56 78 when dialled from outside Zürich
0041 1 34 56 78 when dialled from Germany

But Zürich got too big and the numbers changed to:

234 56 78 fictitious phone number dialled locally in Zürich
01 234 56 78 when dialled from outside Zürich
0041 1 234 56 78 when dialled from Germany

Switzerland needed more phone regions and the numbers changed again:

<s>234 56 78 fictitious phone number dialled locally in Zürich</s> no longer valid
044 234 56 78 when dialled from within Switzerland
0041 44 234 56 78 when dialled from Germany

Then came the mobile phones adding another variant:

+41 44 234 56 78 from outside Switzerland

The exact same number can be written in different ways:

0442345678
044-234 56 7
(044)234 56 78
044/234 56 78-123 with extension

And last but not least, different countries use different dialling for exactly the same phone number destination:

011 41 44 234 56 78 US
0111 41 44 234 56 78 Australia
0010 41 44 234 56 78 Bolivia
0015 41 44 234 56 78 Brazil

And don't forget, there are also special numbers like 199, which follow a completely different format.

The consequence of this huge variety is that you have to choose if you want to give the user the freedom to key in all possible phone number notations or if you want to restrict the format, let's say to the international format with the leading '+'.

The PhoneTextBox control treats international and local phone numbers differently. If the entered phone number starts with '+', '00' or is longer than CountryCode.MaxLengthLocalCode (default=10), it is considered to be an international number. It then tries to detect any leading '+', '0' or '00' and removes them and any blanks or special characters. The next digits are the country code. Unfortunately, also the country code structure is a big mess!

Country Code Structure

Normally, the first 2 or 3 digits after the '+' specify the country. Exceptions are a leading '1' (USA, Canada and some smaller countries) and '9' (Russia and some smaller countries). The definition for a leading '1' is really messy:

1200
...
1339 USA
1340 Virgin Islands (Caribbean Islands)
1341 USA
1342 not used
1343 Canada
1344 Reserved
1345 Cayman Islands
1346 USA
...
1365 Canada
...
1999

For details, see this link.

Crazy right? But to format the phone number properly, one needs to know how long the country code is. For this purpose, I wrote the class CountryCode. Its method Country? GetCountry(string phoneNumber) returns the country and the country code length, if possible.

Formatting International Phone Numbers

CountryCode.Format() can be used to format local and international phone numbers. If it detects an international phone number, it will return it in the following format:

+ccc dddddddddd
  • ccc: country code, can be 2 to 4 digits
  • ddd: local code (including area code), any length
  • There will be exactly 1 blank, between country code and local code.
  • Exception: Of course, US and Canada. There is no country code separating these countries. In this case, ccc has 4 digits and indicates country and region.

Formatting Local Phone Numbers

As shown in my example, there are many ways how to write local phone numbers. Depending on the country you live in, you might want to write your own local phone number format method and assign it to CountryCode Func<string, string?>? LocalFormat. Or you can use one of the local format methods from CountryCode, which return different formatting depending on how many digits the phone number has:

LocalFormatNo0Area2(): 2 digit area code without leading 0

23-456 789
23-456 78 90
23-4567 8901

LocalFormat0Area2(): 2 digit area code with leading 0

023-456 789
023-456 78 90
023-4567 8901

LocalFormat0Area3(): 3 digit area code without leading 0

234-567 890
234-567 89 01
234-5678 9012

LocalFormat0Area3(): 3 digit area code with leading 0

0234-567 890
0234-567 89 01
0234-5678 9012

LocalFormat8Digits(): exactly 8 digits, no area code for countries like Singapore

1234 5678

Note: If a very short number is entered like 199, the entered format will not be changed.

Using the Code

XML
<wwl:CheckedWindow x:Class="Samples.SampleWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wwl="clr-namespace:WpfWindowsLib;assembly=WpfWindowsLib"
                   SizeToContent="WidthAndHeight">
  <StackPanel>
    <Label Content="Phone No required"/>
    <wwl:PhoneTextBox x:Name="RequiredPhoneTextBox" MinWidth="100" MinDigits="7" 
       MaxDigits="13" IsRequired="True"/>
    <Label Content="Phone No  not required"/>
    <wwl:PhoneTextBox x:Name="NotRequiredPhoneTextBox" MinWidth="100"/>
    <Button x:Name="SaveButton" Content="_Save"/>
  </StackPanel>
</wwl:CheckedWindow>

Image 1

The upper window displayed is the data entry window with 2 PhoneTextBoxes. The first box is required, but the user has not entered any data yet, that's why the background is khaki. That is also the reason why the Save Button is disabled. The user has entered a number into the second PhoneTextBox. The user tried then to close the window without saving the data. A second window with a warning opened and the second PhoneTextBox's background got light green, to show the user which data has changed. For a detailed explanation of how this works, see my article, Base WPF Window Functionality for Data Entry.

Configuring PhoneTextBox

In many cases, you don't need to configure anything. You might want to set IsRequired, MinDigits and MaxDigits in XAML or, if the user wants to edit some existing data, call Initialise() passing the existing phone number as parameters from code behind.

Instance Properties

Some properties can be set individually for every PhoneTextBox:

  • MinDigits (DependencyProperty): How many digits a user needs to enter before leaving the PhoneTextBox. Only characters between '0' and '9' get counted.
  • MaxDigits (DependencyProperty): How many digits the user can maximally enter.
  • IsRequired (DependencyProperty): Needs the user to provide this control with a value?
  • CompactNumber (normal property): When storing the phone number, it might be convenient to store every phone with number digits only. This simplifies searches, indexing, etc. CompactNumber can only be read and only from code (not XAML).

Static Properties

Some properties apply for every PhoneTextBox and are therefore declared as static:

  • LegalSpecialCharacters: digits and one leading '+' can always be entered. Per default, also '-' and '/' can be used. If it should be possible to also enter other characters, change LegalSpecialCharacters accordingly.
  • ValidateUserInput (Func<> property): gets called every time a user has keyed in a character, except when he enters a blank. Assign a different method to change how user input gets validated. Normally, you don't need to do that.
  • ValidatePhoneNumber (Delegate property): gets called when the user wants to move to the next controls (PreviewLostKeyboardFocus) and validates if phoneNumber is a valid phone number. When returning, compactNumber might have been updated.

Static Class CountryCode

I moved the country area specific code in its own class. It stores country specific information like Name, international dialling code and 2 and 3 letter abbreviations of the country name in 2 collections:

C#
SortedDictionary<string, Country> ByAbbreviation3;
DigitInfo?[] ByPhone;

CountryCode.ByPhone holds like a dictionary all countries, indexed by their phone country code. But the storage is very different. The very first digit of the country code points to a DigitInfo.

C#
public class DigitInfo {
  public char Digit;
  public Country? Country;
  public DigitInfo?[]? Digits;
}

The second digit is used as index into DigitInfo.Digits, which returns another DigitInfo. Once all digits of a country code are use, the country is found. Examples:

country code 1: byPhone[1].Country is US
country code 1236: byPhone[1].Digits[2].Digits[3].Digits[6].Country is Canada
country code 1235: byPhone[1].Digits[2].Digits[3].Digits[5].Country is null. Since 
                   byPhone[1].Country is US, also 1235 is US, because no other 
                   country was found in the later digits

This complicated structure is needed to cater for all country code variations. Usually, one does not access ByPhone directly, that would be too complicated. Instead, one calls CountryCode.GetCountry(string phoneNumber).

Configuring CountryCode

  • MaxLengthLocalCode: Number of digits a local phone code (including area code) can maximal have. If more digits are provided, number is considered to be an international dialling code. Default is 10 (2 area code and 8 local digits).
  • DefaultFormat (Func<string, string>): Formats a phone number. Assign your own Format() method, if you have special requirements. CountryCode.DefaultFormat() works like this: If phone number starts with '+' or "00" or is longer than MaxLengthLocalCode, it is formatted like an international dialling code, i.e., '+' country-code local-code. If it is not international, CountryCode.LocalFormat() is called for formatting.
  • LocalFormat (Func<string, string?>): holds the function being used for formatting a local phone number (i.e., not an international dialling code). You can provide your own method or use one from CountryCode (see description in Formatting local phone numbers):
    • LocalFormatNo0Area2()
    • LocalFormat0Area2()
    • LocalFormatNo0Area3()
    • LocalFormat0Area3()
    • LocalFormat8Digits()

Getting the Code

The latest version is available from Github @ https://github.com/PeterHuberSg/WpfWindowsLib.

Download or clone everything to your PC, which gives you a solution WpfWindowsLib with the following projects:

  • WpfWindowsLib: (.Dll) to be referenced from your other solutions, contains PhoneTextBox
  • Samples: WPF Core application showing all WpfWindowsLib controls
  • WpfWindowsLibTest: with few WpfWindowsLib unit tests

Recommended Reading

History

  • 9th April, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
Software Developer (Senior)
Singapore Singapore
Retired SW Developer from Switzerland living in Singapore

Interested in WPF projects.

Comments and Discussions

 
-- There are no messages in this forum --