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

Numpad’s Decimal-point Correction for WPF

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
19 Oct 2014CPOL2 min read 25.7K   1   4
Numpad's decimal point correction for WPF

numpad

Just a quick-and-dirty solution for solving the tedious problem of the numpad’s decimal-point insertion where the culture require something different than a dot “.”.

The Problem

Many of you, who aren’t using a dot as decimal separator, maybe noticed that the character issued by the numeric-pad is always a doe, regardless the OS settings. In Italy, for instance, we’re using the comma as decimal separator.
You know, if you type the wrong character, the number won’t be recognized as valid (sometimes even worse, because it’s mistaken as valid).
If you try to open Notepad or any raw-input application, you’ll notice that there’s no way to “hack” the Windows settings in order to correctly input the numeric-pad “point” as a comma. By the way, if you enter a number in Microsoft Excel or so, the character is actually a comma.
Looks like the translation is something managed by the application.

It’s not so simple, though.
Imagine to write your own application (WPF in my case), and have a series of textboxes. Whereas a textbox used for entering a number (e.g. most physical units) would be fine having a “translation” to a comma, when another textbox used for an IP-pattern, clearly should *NOT* be translated any time.
Looks like that some countries use a different punctuation for generic numbers and for currency: my “neighbor” friends of Switzerland do use the comma for any number but currency, where the dot is preferred.

The Solution

Here is a solution, but I believe is difficult to satisfy all the developers’ habits. I just opted for a simple attached-property, as “behavior” to any TextBoxBase object, which “intercepts” the Decimal key (the numpad’s DP) and replaces it with the proper one.

C#
namespace DecimalPointCorrectorDemo
{
    public enum DecimalPointCorrectionMode
    {
        /// <summary>
        /// (Default) No correction is applied, and any style
        /// inherited setting may influence the correction behavior.
        /// </summary>
        Inherits,

        /// <summary>
        /// Enable the decimal-point correction for generic numbers.
        /// </summary>
        Number,

        /// <summary>
        /// Enable the decimal-point correction for currency numbers.
        /// </summary>
        Currency,

        /// <summary>
        /// Enable the decimal-point correction for percent-numbers.
        /// </summary>
        Percent,
    }

    /// <summary>
    /// General purpose container for <see cref="System.Windows.Controls.TextBox"/> helpers.
    /// </summary>
    public static class TextBoxHelper
    {
        #region DPA DecimalPointCorrection

        public static readonly DependencyProperty 
                  DecimalPointCorrectionProperty = DependencyProperty.RegisterAttached(
            "DecimalPointCorrection",
            typeof(DecimalPointCorrectionMode),
            typeof(TextBoxHelper),
            new UIPropertyMetadata(
                default(DecimalPointCorrectionMode),
                DecimalPointCorrectionChanged
                ));

        public static DecimalPointCorrectionMode GetDecimalPointCorrection(TextBoxBase obj)
        {
            return (DecimalPointCorrectionMode)obj.GetValue(DecimalPointCorrectionProperty);
        }

        public static void SetDecimalPointCorrection(TextBoxBase obj, DecimalPointCorrectionMode value)
        {
            obj.SetValue(DecimalPointCorrectionProperty, value);
        }

        #endregion

        private static void DecimalPointCorrectionChanged(
            object sender,
            DependencyPropertyChangedEventArgs args
            )
        {
            var tbox = (TextBoxBase)sender;

            //remove any existent event subscription
            switch ((DecimalPointCorrectionMode)args.OldValue)
            {
                case DecimalPointCorrectionMode.Number:
                case DecimalPointCorrectionMode.Currency:
                case DecimalPointCorrectionMode.Percent:
                    tbox.PreviewKeyDown -= tbox_PreviewKeyDown;
                    break;
            }

            //subscribe the event handler, whereas necessary
            switch ((DecimalPointCorrectionMode)args.NewValue)
            {
                case DecimalPointCorrectionMode.Number:
                case DecimalPointCorrectionMode.Currency:
                case DecimalPointCorrectionMode.Percent:
                    tbox.PreviewKeyDown += tbox_PreviewKeyDown;
                    break;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void tbox_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            //filter the numpad's decimal-point key only
            if (e.Key == System.Windows.Input.Key.Decimal)
            {
                //mark the event as handled, so no further action will take place
                e.Handled = true;

                //grab the originating texbox control...
                var tbox = (TextBoxBase)sender;

                //the current correction mode...
                var mode = TextBoxHelper.GetDecimalPointCorrection(tbox);

                //and the culture of the thread involved (UI)
                var culture = Thread.CurrentThread.CurrentCulture;

                //surrogate the blocked key pressed
                SimulateDecimalPointKeyPress(
                    tbox, 
                    mode, 
                    culture
                    );
            }
        }

        /// <summary>
        /// Insertion of the proper decimal-point as part of the textbox content
        /// </summary>
        /// <param name="tbox"></param>
        /// <param name="mode"></param>
        /// <param name="culture"></param>
        /// <remarks>
        /// Typical "async-void" pattern as "fire-and-forget" behavior.
        /// </remarks>
        private static async void SimulateDecimalPointKeyPress(
            TextBoxBase tbox,
            DecimalPointCorrectionMode mode,
            CultureInfo culture
            )
        {
            //select the proper decimal-point string upon the context
            string replace;
            switch (mode)
            {
                case DecimalPointCorrectionMode.Number:
                    replace = culture.NumberFormat.NumberDecimalSeparator;
                    break;

                case DecimalPointCorrectionMode.Currency:
                    replace = culture.NumberFormat.CurrencyDecimalSeparator;
                    break;

                case DecimalPointCorrectionMode.Percent:
                    replace = culture.NumberFormat.PercentDecimalSeparator;
                    break;

                default:
                    replace = null;
                    break;
            }

            if (string.IsNullOrEmpty(replace) == false)
            {
                //insert the desired string
                var tc = new TextComposition(
                    InputManager.Current,
                    tbox,
                    replace
                    );

                TextCompositionManager.StartComposition(tc);
            }

            await Task.FromResult(false);
        }
    }
}

The code is rather simple, so I think would be useless chatting more.
The only worthwhile point is regarding the “async” pattern in the key-replace function. I just wanted to leave the originating event (PreviewKeyDown) a bit of time to finish before adding another (possible) event. Honestly, I don’t know whether that’s really necessary: the async-await pattern comes easy and reliable, so I prefer to keep the code safer. Feel free to improve the it.

correction-off

correction-on

The complete demo solution source code can be downloaded here.

This code has been tested widely enough, including on the Windows 8 on-screen touch-keyboard.
Enjoy!

Image 4 Image 5

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) CET Electronics
Italy Italy
Played with transistors and ICs before being ten. First approaches to programming (PET Commodore) in the early '80.
Then AppleSoft, TurboPascal, Assembler and VisualBasic.
Currently employed at CET Electronics as lead software developer, involved in creation of industrial control systems.
Loving graphics technologies, I had some great time with SVG.
Since 2006 my primary language is C#, where I am focusing on WPF.

Comments and Discussions

 
QuestionThe source code is not available. Pin
roby399-Dec-23 11:11
roby399-Dec-23 11:11 
QuestionWhy use async in SimulateDecimalPointKeyPress ? Pin
vmolines1-Aug-18 22:39
vmolines1-Aug-18 22:39 
GeneralActually most languages that are based on Latin alphabet except English and Malay use comma as a radix point Pin
fulloflove21-Oct-14 21:20
fulloflove21-Oct-14 21:20 
QuestionVery good article! Pin
Volynsky Alex19-Oct-14 12:51
professionalVolynsky Alex19-Oct-14 12:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.