WPF Core实战提高教程(1.1)-自定义控件NumberBox

WPF自定义控件开发 - 第一节(NumberBox)
WPF由于ControlTemplate、Style、Adorner等神器的存在,自定义控件看起来已经没有了太大意义,我们可以利用现有的机制拼凑出任何功能的可复用的控件,但不可避免的我们经常需要自定义绘制、对现有的控件做功能性扩展。
首先解释下,我理解的“自定义控件”并不仅限于完全重写一个新的控件,包括对现有控件的扩展、组合。
对于控件的开发,个人总结出以下几条经验,可以避免多走弯路:

  1. 只有在确认现有控件无法满足需求的情况下才需要自定义控件。
  2. 对现有控件视觉上的变化尽量使用Style和ControlTemplate。而不是写个新的。
  3. 对现有控件的扩展尽量使用C#的扩展方法、XAML的Behavior、WPF的依赖事件/属性,而不是盲目的去继承现有控件生成新的。
  4. 重复造轮子是有意义的,对学习提高和加深理解很有帮助,纸上得来终觉浅。但也不要过度,有些看起来很简单的东西实现起来相当复杂,需要大把的时间和精力,而最后效果还不见得理想。

第一个示例,我们来实现一个NumberBox,继承自Textbox。
先分析需求

  • 它只允许用户输入数字,屏蔽其他输入。
  • 可以控制是否允许输入小数。
  • 可以指定一个格式化字符串,用于控制数值呈现的格式。
  • 提供一个double类型的可读写属性:Value
  • 提供一个事件:ValueChanged,用于通知值的变化。

思路
文本框的文字变化时,解析文字为double类型,更新Value。Value被改变时根据格式化字符串更新文本框的文字。很简单的逻辑,唯一的障碍是Text与Value修改时的相互更新会导致死循环,这需要一点小技巧来处理。
NumberBox不需要XAML,不过多赘述,直接上代码,一些小细节会在代码中注释:

 public class NumberBox : TextBox
    {
        static NumberBox()
        {
            //NumberBox基本不需要额外的样式,用Textbox的就够了
            //DefaultStyleKeyProperty.OverrideMetadata(typeof(NumberBox), new FrameworkPropertyMetadata(typeof(NumberBox)));
        }
        public NumberBox()
        {
            InputMethod.SetIsInputMethodEnabled(this, false);//禁用文本框的输入法 
            this.VerticalContentAlignment = VerticalAlignment.Center;
            this.Text = "0";
        }

        protected override void OnLostFocus(RoutedEventArgs e)
        {
            base.OnLostFocus(e);
            if (double.TryParse(this.Text, out double value))
            {//如果是有效的数字则替换当前值
                updateValue(value);
            }
            else
            {//失去焦点时如果不是有效的数字则用当前值替换用户的输入
                updateText(this.Value.ToString(this.FormatString));
            }
        }

        private bool isUpdateValue = false, isUpdateText = false;

        protected override void OnTextInput(TextCompositionEventArgs e)
        {
            var txt = e.Text;
            if (txt != null)
            {
                foreach (var item in txt)
                {
                    if (!(char.IsDigit(item) || item == '.' || item == '-'))
                    {
                        e.Handled = true;
                        break;
                    }
                    else
                    {
                        if (!AllowDecimals && item=='.')
                        {//不允许输入小数
                            e.Handled = true;
                            break;
                        }
                        if (item == '.' || item == '-')
                        {//小数点和负号只能出现一次
                            if (contains(item))
                            {
                                e.Handled = true;
                                break;
                            }
                        }
                    }

                }
            }
            else
            {
                e.Handled = true;
            }

            base.OnTextInput(e);
        }

        /// 
        /// 当前文本中是否包含指定的字符
        /// 
        /// 
        /// 
        private bool contains(char c)
        {
            if (this.Text == null)
            {
                return false;
            }
            foreach (var item in this.Text)
            {
                if (item == c)
                {
                    return true;
                }
            }
            return false;
        }


        /// 
        /// 静默更新值
        /// 
        /// 
        protected void updateValue(double value)
        {
            isUpdateValue = true;
            try
            {
                this.Value = value;
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                isUpdateValue = false;
            }
        }
        /// 
        /// 静默更新文本
        /// 
        /// 
        protected void updateText(string text)
        {
            isUpdateText = true;
            try
            {
                this.Text = text;
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                isUpdateText = false;
            }
        }

        /// 
        /// 当前值
        /// 
        public double Value
        {
            get { return (double)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(double), typeof(NumberBox), new PropertyMetadata(0d));


        /// 
        /// 数值的格式化字符串,默认最多保留小数点后6位。
        /// 
        public string FormatString
        {
            get { return (string)GetValue(FormatStringProperty); }
            set { SetValue(FormatStringProperty, value); }
        }

        public static readonly DependencyProperty FormatStringProperty =
            DependencyProperty.Register("FormatString", typeof(string), typeof(NumberBox), new PropertyMetadata("0.######"));


        /// 
        /// 是否允许输入小数
        /// 
        public bool AllowDecimals
        {
            get { return (bool)GetValue(AllowDecimalsProperty); }
            set { SetValue(AllowDecimalsProperty, value); }
        }

        public static readonly DependencyProperty AllowDecimalsProperty =
            DependencyProperty.Register("AllowDecimals", typeof(bool), typeof(NumberBox), new PropertyMetadata(true));



        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);
            if (e.Property == ValueProperty)
            {
                this.OnValueChanged();
            }
            else if (e.Property == FormatStringProperty)
            {
                updateText(this.Value.ToString(this.FormatString));
            }
        }
        /// 
        /// 在当前值改变后触发
        /// 
        public event EventHandler ValueChanged;

        protected virtual void OnValueChanged()
        {
            if (!isUpdateValue)
            {
                updateText(this.Value.ToString(this.FormatString));
            }
            this.ValueChanged?.Invoke(this, EventArgs.Empty);
        }
        protected override void OnTextChanged(TextChangedEventArgs e)
        {
            if (!isUpdateText)
            {
                if (double.TryParse(this.Text, out double value))
                {
                    updateValue(AllowDecimals? value:Convert.ToInt32(value));//不允许小数则将值强行取整
                }
            }
            base.OnTextChanged(e);
        }
    }

你可能感兴趣的:(wpf)