WPF數字輸入框和IP地址輸入框


數字輸入框

簡介

在業務中,我們經常需要限制用戶的輸入,比如限制輸入長度,限制只能輸入數字等等。限制輸入長度WPF內置的TextBox已經幫我們解決了,但是限制輸入數字卻並未在WPF中內置解決方案。使用第三方的控件又要多增加一個引用,於是決定自己寫一個。

在寫的過程中發現需要考慮的問題比較多,比如限制輸入法、部分限制輸入小數點和負號、限制輸入字母和其它符號、粘貼時做特殊處理等等。值得一提的是,將文本綁定到Double型且將UpdateSourceTrigger設為PropertyChanged時,出現了界面上包含小數點,但是通過Text獲取的文本卻並不包含小數點的情況,猜測原因是因為綁定更新和自動類型轉換出現的問題。

數字輸入框提供了設置最小值(可用)、最大值(可用)、精度(小數點后位數)的功能。不合法的按鍵將會被過濾掉,輸入的值小於最小值或者大於最大值時,其輸入都將視為無效。

代碼

代碼較簡單,且有注釋,不再多說。

namespace YiYan127.WPF.Controls
{
    using System;
    using System.Globalization;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;

    /// <summary>
    /// 輸入數值的文本框
    /// </summary>
    public class NumbericTextBox : TextBox
    {
        #region Fields

        #region DependencyProperty

        /// <summary>
        /// 最大值的依賴屬性
        /// </summary>
        public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register(
            "MaxValue",
            typeof(double),
            typeof(NumbericTextBox),
            new PropertyMetadata(double.MaxValue));

        /// <summary>
        /// 最小值的依賴屬性
        /// </summary>
        public static readonly DependencyProperty MinValueProperty = DependencyProperty.Register(
            "MinValue",
            typeof(double),
            typeof(NumbericTextBox),
            new PropertyMetadata(double.MinValue));

        /// <summary>
        /// 精度的依賴屬性
        /// </summary>
        public static readonly DependencyProperty PrecisionProperty = DependencyProperty.Register(
            "Precision",
            typeof(ushort),
            typeof(NumbericTextBox),
            new PropertyMetadata((ushort)2));

        #endregion DependencyProperty

        /// <summary>
        /// 先前合法的文本
        /// </summary>
        private string lastLegalText;

        /// <summary>
        /// 是否為粘貼
        /// </summary>
        private bool isPaste;

        public event EventHandler<TextChangedEventArgs> PreviewTextChanged;

        #endregion Fields

        #region Constructor

        /// <summary>
        /// 構造函數
        /// </summary>
        public NumbericTextBox()
        {
            this.PreviewTextInput += this.NumbericTextBoxPreviewTextInput;
            this.TextChanged += this.NumbericTextBoxTextChanged;
            this.PreviewKeyDown += this.NumbericTextBox_PreviewKeyDown;
            this.LostFocus += this.NumbericTextBoxLostFocus;
            InputMethod.SetIsInputMethodEnabled(this, false);

            this.Loaded += this.NumbericTextBoxLoaded;
        }

        #endregion Constructor

        #region Properties

        /// <summary>
        /// 最大值,可取
        /// </summary>
        public double MaxValue
        {
            get { return (double)this.GetValue(MaxValueProperty); }
            set { this.SetValue(MaxValueProperty, value); }
        }

        /// <summary>
        /// 最小值,可取
        /// </summary>
        public double MinValue
        {
            get { return (double)this.GetValue(MinValueProperty); }
            set { this.SetValue(MinValueProperty, value); }
        }

        /// <summary>
        /// 精度,即精確到小數點后的位數
        /// </summary>
        public ushort Precision
        {
            get { return (ushort)this.GetValue(PrecisionProperty); }
            set { this.SetValue(PrecisionProperty, value); }
        }

        #endregion Properties

        protected virtual void OnPreviewTextChanged(TextChangedEventArgs e)
        {
            if (this.PreviewTextChanged != null)
            {
                this.PreviewTextChanged(this, e);
            }
        }

        #region Private Methods

        /// <summary>
        /// 處理粘貼的情況
        /// </summary>
        protected virtual void HandlePaste()
        {
            this.isPaste = false;

            // 處理符號的標志
            bool handledSybmol = false;

            // 處理小數點的標志
            bool handledDot = false;

            // 當前位對應的基數
            double baseNumber = 1;

            // 轉換后的數字
            double number = 0;

            // 上一次合法的數字
            double lastNumber = 0;

            // 小數點后的位數
            double precision = 0;
            foreach (var c in this.Text)
            {
                if (!handledSybmol && (c == '-'))
                {
                    baseNumber = -1;
                    handledSybmol = true;
                }

                if ((c >= '0') && (c <= '9'))
                {
                    int digit = c - '0';
                    if (!handledDot)
                    {
                        number = (number * baseNumber) + digit;
                        baseNumber = 10;
                    }
                    else
                    {
                        baseNumber = baseNumber / 10;
                        number += digit * baseNumber;
                    }

                    // 正負號必須位於最前面
                    handledSybmol = true;
                }

                if (c == '.')
                {
                    // 精度已經夠了
                    if (precision + 1 > this.Precision)
                    {
                        break;
                    }

                    handledDot = true;

                    // 此時正負號不能起作用
                    handledSybmol = true;
                    baseNumber = 0.1;
                    precision++;
                }

                if ((number < this.MinValue) || (number > this.MaxValue))
                {
                    this.Text = lastNumber.ToString(CultureInfo.InvariantCulture);
                    this.SelectionStart = this.Text.Length;
                    return;
                }

                lastNumber = number;
            }

            this.Text = number.ToString(CultureInfo.InvariantCulture);
            this.SelectionStart = this.Text.Length;
        }

        #endregion Private Methods

        #region Overrides of TextBoxBase

        #endregion

        #region Events Handling

        private void NumbericTextBoxLoaded(object sender, RoutedEventArgs e)
        {
            if (this.MinValue > this.MaxValue)
            {
                this.MinValue = this.MaxValue;
            }

            if (string.IsNullOrEmpty(this.Text))
            {
                double val = (this.MaxValue + this.MinValue) / 2;
                val = Math.Round(val, this.Precision);

                this.Text = val.ToString(CultureInfo.InvariantCulture);
            }

            this.isPaste = true;
        }

        /// <summary>
        /// The numberic text box preview text input.
        /// </summary>
        /// <param name="sender"> The sender.</param>
        /// <param name="e"> The e.</param>
        private void NumbericTextBoxPreviewTextInput(object sender, TextCompositionEventArgs e)
        {
            // 如果是粘貼不會引發該事件
            this.isPaste = false;

            short val;

            // 輸入非數字
            if (!short.TryParse(e.Text, out val))
            {
                // 小於0時,可輸入負號
                if ((this.MinValue < 0) && (e.Text == "-"))
                {
                    int minusPos = this.Text.IndexOf('-');

                    // 未輸入負號且負號在第一位
                    if ((minusPos == -1) && (0 == this.SelectionStart))
                    {
                        return;
                    }
                }

                // 精度大於0時,可輸入小數點
                if ((this.Precision > 0) && (e.Text == "."))
                {
                    // 解決UpdateSourceTrigger為PropertyChanged時輸入小數點文本與界面不一致的問題
                    if (this.SelectionStart > this.Text.Length)
                    {
                        e.Handled = true;
                        return;
                    }

                    // 小數點位置
                    int dotPos = this.Text.IndexOf('.');

                    // 未存在小數點可輸入
                    if (dotPos == -1)
                    {
                        return;
                    }

                    // 已存在小數點但處於選中狀態,也可輸入小數點
                    if ((this.SelectionStart >= dotPos) && (this.SelectionLength > 0))
                    {
                        return;
                    }
                }

                e.Handled = true;
            }
            else
            {
                int dotPos = this.Text.IndexOf('.');
                int cursorIndex = this.SelectionStart;

                // 已經存在小數點,且小數點在光標后
                if ((dotPos != -1) && (dotPos < cursorIndex))
                {
                    // 不允許輸入超過精度的數
                    if (((this.Text.Length - dotPos) > this.Precision) && (this.SelectionLength == 0))
                    {
                        e.Handled = true;
                    }
                }
            }
        }

        /// <summary>
        /// The numberic text box text changed.
        /// </summary>
        /// <param name="sender"> The sender.</param>
        /// <param name="e"> The e.</param>
        private void NumbericTextBoxTextChanged(object sender, TextChangedEventArgs e)
        {
            if (this.lastLegalText == this.Text)
            {
                return;
            }

            this.OnPreviewTextChanged(e);

            // 允許為空
            if (string.IsNullOrEmpty(this.Text))
            {
                return;
            }

            // 粘貼而來的文本
            if (this.isPaste)
            {
                this.HandlePaste();
                this.lastLegalText = this.Text;

                return;
            }

            double val;
            if (double.TryParse(this.Text, out val))
            {
                // 保存光標位置
                int selectIndex = this.SelectionStart;
                if ((val > this.MaxValue) || (val < this.MinValue))
                {
                    this.Text = this.lastLegalText;
                    this.SelectionStart = selectIndex;
                    return;
                }

                this.lastLegalText = this.Text;
            }

            this.isPaste = true;
        }

        /// <summary>
        /// The numberic text box_ preview key down.
        /// </summary>
        /// <param name="sender"> The sender.</param>
        /// <param name="e"> The e.</param>
        private void NumbericTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            // 過濾空格
            if (e.Key == Key.Space)
            {
                e.Handled = true;
            }
        }

        /// <summary>
        /// The numberic text box_ lost focus.
        /// </summary>
        /// <param name="sender"> The sender.</param>
        /// <param name="e"> The e.</param>
        private void NumbericTextBoxLostFocus(object sender, RoutedEventArgs e)
        {
            if (string.IsNullOrEmpty(this.Text))
            {
                this.Text = this.lastLegalText;
            }
        }

        #endregion Events Handling
    }
}

IP地址輸入框

IP地址輸入框使用了上面的數字輸入框,按點時可跳轉到IP地址下一部分輸入,支持IP地址的粘貼。代碼較簡單,且有注釋,不再多說。

XAML

<UserControl x:Class="YiYan127.WPF.Controls.IpAddressControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:controls="clr-namespace:YiYan127.WPF.Controls">
    <UniformGrid Columns="4" TextBoxBase.GotFocus="TextBox_OnGotFocus">
        <DockPanel Margin="5,2">
            <TextBlock VerticalAlignment="Center" DockPanel.Dock="Right" Text="." />
            <controls:NumbericTextBox x:Name="IPPart1" MaxValue="255" MinValue="0"
                                      Precision="0" />
        </DockPanel>
        <DockPanel Margin="0,2,5,2">
            <TextBlock VerticalAlignment="Center" DockPanel.Dock="Right" Text="." />
            <controls:NumbericTextBox x:Name="IPPart2" MaxValue="255" MinValue="0"
                                      Precision="0" />
        </DockPanel>
        <DockPanel Margin="0,2,5,2">
            <TextBlock VerticalAlignment="Center" DockPanel.Dock="Right" Text="." />
            <controls:NumbericTextBox x:Name="IPPart3" MaxValue="255" MinValue="0"
                                      Precision="0" />
        </DockPanel>
        <controls:NumbericTextBox x:Name="IPPart4" Margin="0,2,5,2" MaxValue="255"
                                  MinValue="0" Precision="0" />
    </UniformGrid>
</UserControl>

后台代碼

namespace YiYan127.WPF.Controls
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Text.RegularExpressions;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;

    /// <summary>
    /// IP地址輸入框
    /// </summary>
    public partial class IpAddressControl
    {
        #region Fields

        /// <summary>
        /// IP地址的依賴屬性
        /// </summary>
        public static readonly DependencyProperty IPProperty = DependencyProperty.Register(
                "IP",
                typeof(string),
                typeof(IpAddressControl),
                new FrameworkPropertyMetadata(DefaultIP, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, IPChangedCallback));

        /// <summary>
        /// IP地址的正則表達式
        /// </summary>
        public static readonly Regex IpRegex = new
            Regex(@"^((2[0-4]\d|25[0-5]|(1\d{2})|([1-9]?[0-9]))\.){3}(2[0-4]\d|25[0-4]|(1\d{2})|([1-9][0-9])|([1-9]))$");

        /// <summary>
        /// 默認IP地址
        /// </summary>
        private const string DefaultIP = "127.0.0.1";

        private static readonly Regex PartIprRegex = new Regex(@"^(\.?(2[0-4]\d|25[0-5]|(1\d{2})|([1-9]?[0-9]))\.?)+$");

        /// <summary>
        /// 輸入框的集合
        /// </summary>
        private readonly List<NumbericTextBox> numbericTextBoxs = new List<NumbericTextBox>();

        /// <summary>
        /// 當前活動的輸入框
        /// </summary>
        private NumbericTextBox currentNumbericTextBox;

        #endregion Fields

        #region Constructors

        public IpAddressControl()
        {
            InitializeComponent();
            this.numbericTextBoxs.Add(this.IPPart1);
            this.numbericTextBoxs.Add(this.IPPart2);
            this.numbericTextBoxs.Add(this.IPPart3);
            this.numbericTextBoxs.Add(this.IPPart4);
            this.KeyUp += this.IpAddressControlKeyUp;

            this.UpdateParts(this);

            foreach (var numbericTextBox in this.numbericTextBoxs)
            {
                numbericTextBox.PreviewTextChanged += this.NumbericTextBox_OnPreviewTextChanged;
            }

            foreach (var numbericTextBox in this.numbericTextBoxs)
            {
                numbericTextBox.TextChanged += this.TextBoxBase_OnTextChanged;
            }
        }

        #endregion Constructors

        #region Properties

        public string IP
        {
            get
            {
                return (string)GetValue(IPProperty);
            }

            set
            {
                SetValue(IPProperty, value);
            }
        }

        #endregion Properties

        #region Private Methods

        /// <summary>
        /// IP值改變的響應
        /// </summary>
        /// <param name="dependencyObject"></param>
        /// <param name="dependencyPropertyChangedEventArgs"></param>
        private static void IPChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            if (dependencyPropertyChangedEventArgs.NewValue == null)
            {
                throw new Exception("IP can not be null");
            }

            var control = dependencyObject as IpAddressControl;
            if (control != null)
            {
                control.UpdateParts(control);
            }
        }

        private void UpdateParts(IpAddressControl control)
        {
            string[] parts = control.IP.Split(new[] { '.' });
            control.IPPart1.Text = parts[0];
            control.IPPart2.Text = parts[1];
            control.IPPart3.Text = parts[2];
            control.IPPart4.Text = parts[3];
        }

        #endregion Private Methods

        #region Event Handling

        /// <summary>
        /// 按鍵松開的響應
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void IpAddressControlKeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.OemPeriod || e.Key == Key.Decimal)
            {
                if (this.currentNumbericTextBox != null)
                {
                    int index = this.numbericTextBoxs.IndexOf(this.currentNumbericTextBox);
                    int next = (index + 1) % this.numbericTextBoxs.Count;
                    this.numbericTextBoxs[next].Focus();
                    this.numbericTextBoxs[next].SelectionStart = this.numbericTextBoxs[next].Text.Length;
                }
            }
        }

        /// <summary>
        /// 獲得焦點的響應
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TextBox_OnGotFocus(object sender, RoutedEventArgs e)
        {
            this.currentNumbericTextBox = e.OriginalSource as NumbericTextBox;
        }

        private void NumbericTextBox_OnPreviewTextChanged(object sender, TextChangedEventArgs e)
        {
            var numbericTextBox = sender as NumbericTextBox;
            Contract.Assert(numbericTextBox != null);

            if (PartIprRegex.IsMatch(numbericTextBox.Text))
            {
                var ips = numbericTextBox.Text.Split('.');

                if (ips.Length == 1)
                {
                    return;
                }

                int index = this.numbericTextBoxs.IndexOf(numbericTextBox);
                int pointer2Ips = 0;
                for (int i = index; i < this.numbericTextBoxs.Count; i++)
                {
                    while (pointer2Ips < ips.Length && string.IsNullOrEmpty(ips[pointer2Ips]))
                    {
                        pointer2Ips++;
                    }

                    if (pointer2Ips >= ips.Length)
                    {
                        return;
                    }

                    this.numbericTextBoxs[i].Text = ips[pointer2Ips];
                    pointer2Ips++;
                }
            }
        }

        private void TextBoxBase_OnTextChanged(object sender, TextChangedEventArgs e)
        {
            var ip = string.Format(
                "{0}.{1}.{2}.{3}",
                this.IPPart1.Text,
                this.IPPart2.Text,
                this.IPPart3.Text,
                this.IPPart4.Text);
            if (IpRegex.IsMatch(ip))
            {
                this.IP = ip;
            }
        }

        #endregion     Event Handling
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM