WPF自定义控件开发 - 第一节(NumberBox)
WPF由于ControlTemplate、Style、Adorner等神器的存在,自定义控件看起来已经没有了太大意义,我们可以利用现有的机制拼凑出任何功能的可复用的控件,但不可避免的我们经常需要自定义绘制、对现有的控件做功能性扩展。
首先解释下,我理解的“自定义控件”并不仅限于完全重写一个新的控件,包括对现有控件的扩展、组合。
对于控件的开发,个人总结出以下几条经验,可以避免多走弯路:
第一个示例,我们来实现一个NumberBox,继承自Textbox。
先分析需求:
思路:
文本框的文字变化时,解析文字为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);
}
}