Click here to Skip to main content
13,146,742 members (79,683 online)
Click here to Skip to main content
Add your own
alternative version

Stats

4.3K views
148 downloads
6 bookmarked
Posted 14 Nov 2016

WPF Numerical TextBox with Math Operations

, 14 Nov 2016
Rate this:
Please Sign up or sign in to vote.
A WPF custom TextBox for editing numbers that features a context menu for math operations

Introduction

The NumberTextBox class filters its input to allow only numbers. In addition, if the user types +-*/, then a context menu opens which allows the entry of a second number (operand). The context menu displays the result as the user types, allowing the user to either accept the result or cancel it.

Both the normal number keys and the keypad are supported. The result can be accepted either by clicking on it with the mouse or by using the Enter key.

The zip archive includes the NumberTextBox class plus a simple test application.

The NumberTextBox Class

using System;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;

namespace WpfApplication1
{
    /// <summary>
    /// A WPF custom <see cref="System.Windows.Controls.TextBox">TextBox</see> for editing numbers.
    /// </summary>
    public class NumberTextBox : TextBox
    {
        public NumberTextBox()
            : base()
        {
            PreviewTextInput += OnPreviewTextInput;
        }

        private static Regex regexDisallowedInteger = 
        new Regex(@"[^0-9-]+");  // matches disallowed text
        private static Regex regexDisallowedFloat = 
        new Regex(@"[^0-9-+.,e]+");  // matches disallowed text

        private void OnPreviewTextInput(object sender, TextCompositionEventArgs e)
        {
            if (Text.Length > 0 && OpenMathMenu(e.Text))
                e.Handled = true;
            else
                e.Handled = regexDisallowedFloat.IsMatch(e.Text);  // or regexDisallowedInteger
        }

        #region Math

        private enum EOperation { Add, Subtract, Multiply, Divide }
        private EOperation operation = EOperation.Add;
        private ContextMenu menuMath = null;
        private MenuItem miOperand = null;
        private MenuItem miResult = null;

        private bool OpenMathMenu(string text)
        {
            if (menuMath != null)
                return false;

            if (text == "+" || text == "-" || 
            text == "*" || text == "/")
            {
                if (text == "-" && CaretIndex == 0)  // negative sign in front of number
                    return false;

                miOperand = new MenuItem();
                miOperand.Header = "";
                miOperand.FontSize = 18;
                miOperand.FontWeight = FontWeights.Medium;
                miOperand.IsEnabled = false;

                if (text == "+")
                {
                    operation = EOperation.Add;
                    miOperand.Icon = new Image 
                    { Source = new BitmapImage(new Uri("Images/operator_add.png", UriKind.Relative)) };
                }
                else if (text == "-")
                {
                    operation = EOperation.Subtract;
                    miOperand.Icon = new Image 
                    { Source = new BitmapImage(new Uri("Images/operator_sub.png", UriKind.Relative)) };
                }
                else if (text == "*")
                {
                    operation = EOperation.Multiply;
                    miOperand.Icon = new Image 
                    { Source = new BitmapImage(new Uri("Images/operator_mult.png", UriKind.Relative)) };
                }
                else if (text == "/")
                {
                    operation = EOperation.Divide;
                    miOperand.Icon = new Image 
                    { Source = new BitmapImage(new Uri("Images/operator_div.png", UriKind.Relative)) };
                }

                miResult = new MenuItem();
                miResult.Header = "";
                miResult.Icon = new Image 
                { Source = new BitmapImage(new Uri("Images/operator_equals.png", UriKind.Relative)) };
                miResult.FontSize = 18;
                miResult.FontWeight = FontWeights.Medium;
                miResult.IsEnabled = false;
                miResult.Click += OnResultClick;

                menuMath = new ContextMenu();
                menuMath.Items.Add(miOperand);
                menuMath.Items.Add(new Separator());
                menuMath.Items.Add(miResult);
                menuMath.PlacementTarget = this;
                menuMath.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
                menuMath.PreviewKeyDown += OnMathPreviewKeyDown;
                menuMath.Closed += OnMathClosed;
                menuMath.IsOpen = true;
                return true;
            }

            return false;
        }

        private void OnMathPreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
                return;

            string operand = miOperand.Header.ToString();
            //System.Diagnostics.Debug.WriteLine
            (string.Format("OnMathPreviewKeyDown {0}", e.Key.ToString()));
            switch (e.Key)
            {
                case Key.Cancel:
                case Key.Clear:
                case Key.Escape:
                case Key.OemClear:
                    miResult.Header = "";
                    OnResultClick(this, null);
                    break;

                case Key.Back:
                case Key.Delete:
                    if (operand.Length > 0)
                        UpdateResult(operand.Substring(0, operand.Length - 1));
                    else
                        OnResultClick(this, null);
                    break;

                case Key.LineFeed:
                case Key.Enter:
                    OnResultClick(this, null);
                    break;

                case Key.OemPlus:  // '='
                    OnResultClick(this, null);
                    break;

                case Key.Subtract:
                case Key.OemMinus:
                    if (operand.Length == 0)
                        miOperand.Header = "-";
                    break;

                case Key.D0:
                case Key.D1:
                case Key.D2:
                case Key.D3:
                case Key.D4:
                case Key.D5:
                case Key.D6:
                case Key.D7:
                case Key.D8:
                case Key.D9:
                    char key1 = (char)((e.Key - Key.D0) + '0');
                    if (char.IsDigit(key1))
                        UpdateResult(operand + key1);
                    break;

                case Key.NumPad0:
                case Key.NumPad1:
                case Key.NumPad2:
                case Key.NumPad3:
                case Key.NumPad4:
                case Key.NumPad5:
                case Key.NumPad6:
                case Key.NumPad7:
                case Key.NumPad8:
                case Key.NumPad9:
                    char key2 = (char)((e.Key - Key.NumPad0) + '0');
                    if (char.IsDigit(key2))
                        UpdateResult(operand + key2);
                    break;

                case Key.OemComma:
                    if (!operand.Contains(","))
                        miOperand.Header = operand + ",";
                    break;

                case Key.Decimal:
                case Key.OemPeriod:
                    if (!operand.Contains("."))
                        miOperand.Header = operand + ".";
                    break;

                //case Key.Multiply:
                //case Key.Add:
                //case Key.Divide:
            }
        }

        private void UpdateResult(string operand)
        {
            miOperand.Header = operand;
            double dop1, dop2, result;
            if (double.TryParse(Text, out dop1) && double.TryParse(operand, out dop2))
            {
                switch (operation)
                {
                    case EOperation.Add:
                        result = dop1 + dop2;
                        miResult.Header = result.ToString();
                        miResult.IsEnabled = true;
                        break;

                    case EOperation.Subtract:
                        result = dop1 - dop2;
                        miResult.Header = result.ToString();
                        miResult.IsEnabled = true;
                        break;

                    case EOperation.Multiply:
                        result = dop1 * dop2;
                        miResult.Header = result.ToString();
                        miResult.IsEnabled = true;
                        break;

                    case EOperation.Divide:
                        if (dop2 == 0.0)
                        {
                            miResult.Header = "";
                            miResult.IsEnabled = false;
                        }
                        else
                        {
                            result = dop1 / dop2;
                            if (double.IsInfinity(result) || double.IsNaN(result))
                            {
                                miResult.Header = "";
                                miResult.IsEnabled = false;
                            }
                            else
                            {
                                miResult.Header = result.ToString();
                                miResult.IsEnabled = true;
                            }
                        }
                        break;
                }
            }
            else
            {
                miResult.Header = "";
                miResult.IsEnabled = false;
            }
        }

        private void OnResultClick(object sender, RoutedEventArgs e)
        {
            string result = miResult.Header.ToString();
            if (result.Length > 0)
            {
                Text = result;
                CaretIndex = Text.Length;
            }

            menuMath.IsOpen = false;
        }

        private void OnMathClosed(object sender, RoutedEventArgs e)
        {
            menuMath.PreviewKeyDown -= OnMathPreviewKeyDown;
            menuMath.Closed -= OnMathClosed;
            miResult.Click -= OnResultClick;

            menuMath = null;
            miOperand = null;
            miResult = null;
        }

        #endregion Math
    }
}

License

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

Share

About the Author

Bruce Greene
Software Developer (Senior) Greene & Morehead Engineering, Inc.
United States United States
Motion Commander Foundation (MCF) is a set of .NET libraries, documentation and examples that enable the rapid creation of sophisticated and professional C# or Visual Basic machine control applications.

MCF provides the infrastructure (data management, plotting, alarms, message logging, user login, internationalization, Modbus, MTConnect, etc) - so that you can focus on the business logic and user interface for your machine!

MCF is designed around Microsoft .NET best practices to be intuitive for experienced developers, and examples are provided that will enable even novice .NET developers to get started easily.

You may also be interested in...

Comments and Discussions

 
GeneralMy vote of 4 Pin
VanessaHuber13-Dec-16 23:00
memberVanessaHuber13-Dec-16 23:00 
QuestionSuggestion... Pin
Frederico Barbosa15-Nov-16 2:19
memberFrederico Barbosa15-Nov-16 2:19 
AnswerRe: Suggestion... Pin
Bruce Greene15-Nov-16 3:49
memberBruce Greene15-Nov-16 3:49 
GeneralMy vote of 5 Pin
Forhad Reza15-Nov-16 1:15
memberForhad Reza15-Nov-16 1:15 
GeneralRe: My vote of 5 Pin
Bruce Greene15-Nov-16 3:50
memberBruce Greene15-Nov-16 3:50 

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.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.170915.1 | Last Updated 14 Nov 2016
Article Copyright 2016 by Bruce Greene
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid