CustomNumericUpDown.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:.View.NumericUpDown">
<BooleanToVisibilityConverter x:Key="b2v"/>
<Style TargetType="local:NumericUpDown">
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:NumericUpDown">
<Border Name="Bd" CornerRadius="2" SnapsToDevicePixels="True" Padding="0" BorderBrush="#FFDCDEE0" BorderThickness="1" Margin="0,0,0,0">
<Grid HorizontalAlignment="Stretch">
<local:NumberBox x:Name="TEXT" Margin="3,0,0,0" Focusable="True" HorizontalAlignment="Stretch" VerticalAlignment="Center" BorderThickness="0" FormatString="{TemplateBinding FormatString}" FontSize="14" Canvas.Left="1"
TextAlignment="{TemplateBinding TextAlignment}" BorderBrush="#FFDCDEE0" Value="{TemplateBinding Value}" >
</local:NumberBox>
<UniformGrid Rows="2" Columns="1" VerticalAlignment="Stretch" HorizontalAlignment="Right" Canvas.Right="0" Canvas.Bottom="0">
<RepeatButton x:Name="BTNUP" BorderThickness="1,0,0,0" Focusable="False" Interval="{TemplateBinding ButtonRepeatInterval}"
IsEnabled="{TemplateBinding CanIncrement}" Background="White" Height="Auto" Width="25" BorderBrush="#FFDCDEE0" VerticalContentAlignment="Center">
<Viewbox VerticalAlignment="Center" HorizontalAlignment="Center">
<Path Stroke="{TemplateBinding Foreground}" Width="12" Height="12" StrokeThickness="0.5" Data="M2,7.333 L6,3.333 L10,7.333" VerticalAlignment="Center" ></Path>
</Viewbox>
</RepeatButton>
<RepeatButton x:Name="BTNDOWN" BorderThickness="1,1,0,0" Focusable="False" Interval="{TemplateBinding ButtonRepeatInterval}"
IsEnabled="{TemplateBinding CanDecrement}" Background="White" Height="Auto" Width="25" BorderBrush="#FFDCDEE0" VerticalContentAlignment="Center">
<Viewbox VerticalAlignment="Center" HorizontalAlignment="Center">
<Path Stroke="{TemplateBinding Foreground}" Width="12" Height="12" StrokeThickness="0.5" Data="M2,4 L6,8 L10,4"
VerticalAlignment="Center" HorizontalAlignment="Center"></Path>
</Viewbox>
</RepeatButton>
</UniformGrid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Dictionary1.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:.View.NumericUpDown">
<BooleanToVisibilityConverter x:Key="b2v"/>
<Style TargetType="local:NumericUpDown">
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:NumericUpDown">
<DockPanel>
<UniformGrid Rows="2" Columns="1" DockPanel.Dock="Right" VerticalAlignment="Stretch">
<RepeatButton x:Name="BTNUP" BorderThickness="1" Padding="0" Focusable="False" Interval="{TemplateBinding ButtonRepeatInterval}"
IsEnabled="{TemplateBinding CanIncrement}" Background="White" Height="12" Width="25" BorderBrush="#FFDCDEE0" VerticalContentAlignment="Center">
<Viewbox VerticalAlignment="Center" HorizontalAlignment="Center">
<Path Stroke="{TemplateBinding Foreground}" Width="12" Height="8" StrokeThickness="0.5" Data="M2,5.333 L6,1.333 L10,5.333" VerticalAlignment="Center" ></Path>
</Viewbox>
</RepeatButton>
<RepeatButton x:Name="BTNDOWN" BorderThickness="1" Padding="0" Focusable="False" Interval="{TemplateBinding ButtonRepeatInterval}"
IsEnabled="{TemplateBinding CanDecrement}" Background="White" Height="12" Width="25" BorderBrush="#FFDCDEE0" VerticalContentAlignment="Center">
<Viewbox VerticalAlignment="Center" HorizontalAlignment="Center">
<Path Stroke="{TemplateBinding Foreground}" Width="12" Height="8" StrokeThickness="0.5" Data="M2,0.666 L6,4.666 L10,0.666"
VerticalAlignment="Center" HorizontalAlignment="Center"></Path>
</Viewbox>
</RepeatButton>
</UniformGrid>
<Border Name="Bd" CornerRadius="4" SnapsToDevicePixels="True" BorderBrush="#FFDCDEE0" BorderThickness="1,1,0,1">
<local:NumberBox x:Name="TEXT" Focusable="True" FormatString="{TemplateBinding FormatString}" FontSize="14"
TextAlignment="{TemplateBinding TextAlignment}" VerticalContentAlignment="Center" BorderBrush="#FFDCDEE0" >
</local:NumberBox>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
NumberBox.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace LabInOnePC.View.NumericUpDown
{
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";
this.MaxLength = 3;
}
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);
}
/// <summary>
/// 当前文本中是否包含指定的字符
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
private bool contains(char c)
{
if (this.Text == null)
{
return false;
}
foreach (var item in this.Text)
{
if (item == c)
{
return true;
}
}
return false;
}
/// <summary>
/// 静默更新值
/// </summary>
/// <param name="value"></param>
protected void updateValue(double value)
{
isUpdateValue = true;
try
{
this.Value = value;
}
catch (Exception)
{
throw;
}
finally
{
isUpdateValue = false;
}
}
/// <summary>
/// 静默更新文本
/// </summary>
/// <param name="text"></param>
protected void updateText(string text)
{
isUpdateText = true;
try
{
this.Text = text;
}
catch (Exception)
{
throw;
}
finally
{
isUpdateText = false;
}
}
/// <summary>
/// 当前值
/// </summary>
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));
/// <summary>
/// 数值的格式化字符串,默认最多保留小数点后6位。
/// </summary>
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.######"));
/// <summary>
/// 是否允许输入小数
/// </summary>
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));
}
}
/// <summary>
/// 在当前值改变后触发
/// </summary>
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);
}
}
}
NumericUpDown.cs
using System;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
namespace .View.NumericUpDown
{
/// <summary>
/// 具有调节按钮的数值框
/// </summary>
[TemplatePart(Name = "BTNUP", Type = typeof(ButtonBase))]
[TemplatePart(Name = "BTNDOWN", Type = typeof(ButtonBase))]
[TemplatePart(Name = "TEXT", Type = typeof(NumberBox))]
public class NumericUpDown : System.Windows.Controls.Primitives.RangeBase
{
static NumericUpDown()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown), new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}
public NumericUpDown()
{
}
private RepeatButton btnup, btndown;
private NumberBox textbox;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (this.textbox != null)
{
this.textbox.ValueChanged -= Textbox_ValueChanged;
this.textbox.LostFocus -= Textbox_LostFocus;
}
if (this.btnup != null)
{
this.btnup.Click -= Btnup_Click;
}
if (this.btndown != null)
{
this.btndown.Click -= Btndown_Click;
}
this.btnup = (RepeatButton)this.GetTemplateChild("BTNUP");
this.btndown = (RepeatButton)this.GetTemplateChild("BTNDOWN");
this.textbox = (NumberBox)this.GetTemplateChild("TEXT");
if (this.textbox != null)
{
this.textbox.Value = this.Value;
CheckAllowIncrementOrDecrement();
this.textbox.ValueChanged += Textbox_ValueChanged;
this.textbox.LostFocus += Textbox_LostFocus;
}
if (this.btnup != null)
{
this.btnup.Click += Btnup_Click;
}
if (this.btndown != null)
{
this.btndown.Click += Btndown_Click;
}
}
#region 实现
protected override void OnPreviewMouseWheel(MouseWheelEventArgs e)
{
base.OnPreviewMouseWheel(e);
if (e.Delta > 0)
{
this.OnIncrement();
}
else if (e.Delta < 0)
{
this.OnDecrement();
}
}
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (e.Key == Key.Up)
{
UpdateAndCheckValue(this.Value + this.SmallChange);
}
else if (e.Key == Key.Down)
{
UpdateAndCheckValue(this.Value - this.SmallChange);
}
}
private void Textbox_LostFocus(object sender, RoutedEventArgs e)
{//数值框失去焦点时将当前值写道数值框
if (this.textbox.Value != this.Value)
{
this.textbox.Value = this.Value;
}
}
private void Textbox_ValueChanged(object sender, EventArgs e)
{//数值框的值变化时使用数值框的值替换当前值
if (this.textbox.Value > this.Minimum && this.textbox.Value < this.Maximum)
{
this.Value = this.textbox.Value;
}
}
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
this.textbox?.Focus();
}
private void Btndown_Click(object sender, RoutedEventArgs e)
{
this.OnDecrement();
}
private void Btnup_Click(object sender, RoutedEventArgs e)
{
this.OnIncrement();
}
/// <summary>
/// 检查是否允许递增/递减
/// </summary>
protected void CheckAllowIncrementOrDecrement()
{
CanIncrement = (Value < Maximum);
CanDecrement = (Value > Minimum);
}
protected virtual void OnIncrement()
{
var value = this.Value + this.LargeChange;
this.UpdateAndCheckValue(value);
}
protected virtual void OnDecrement()
{
var value = this.Value - this.LargeChange;
this.UpdateAndCheckValue(value);
}
/// <summary>
/// 更新并且检查当前值,保证值不会超出<see cref="Minimum"/>和<see cref="Maximum"/>限定的范围。
/// </summary>
/// <param name="value"></param>
protected virtual void UpdateAndCheckValue(double value)
{
if (value > this.Maximum)
{
this.Value = Maximum;
}
else if (value < this.Minimum)
{
this.Value = Minimum;
}
else
{
this.Value = value;
}
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == ValueProperty)
{
CheckAllowIncrementOrDecrement();
if (this.textbox != null)
{
this.textbox.Value = this.Value;
}
}
}
#endregion
#region 属性
/// <summary>
/// 数字的格式化字符串,默认最多保留小数点后6位。
/// </summary>
public string FormatString
{
get { return (string)GetValue(FormatStringProperty); }
set { SetValue(FormatStringProperty, value); }
}
public static readonly DependencyProperty FormatStringProperty =
DependencyProperty.Register("FormatString", typeof(string), typeof(NumericUpDown), new PropertyMetadata("0.######"));
/// <summary>
/// 文字对齐方式
/// </summary>
public TextAlignment TextAlignment
{
get { return (TextAlignment)GetValue(TextAlignmentProperty); }
set { SetValue(TextAlignmentProperty, value); }
}
public static readonly DependencyProperty TextAlignmentProperty =
DependencyProperty.Register("TextAlignment", typeof(TextAlignment), typeof(NumericUpDown), new PropertyMetadata(TextAlignment.Left));
/// <summary>
/// 按钮重复触发Click事件的间隔时间,单位为毫秒,默认200毫秒。
/// </summary>
public int ButtonRepeatInterval
{
get { return (int)GetValue(ButtonRepeatIntervalProperty); }
set { SetValue(ButtonRepeatIntervalProperty, value); }
}
public static readonly DependencyProperty ButtonRepeatIntervalProperty =
DependencyProperty.Register("ButtonRepeatInterval", typeof(int), typeof(NumericUpDown), new PropertyMetadata(200));
/// <summary>
/// 允许递增
/// </summary>
protected internal bool CanIncrement
{
get { return (bool)GetValue(CanIncrementProperty); }
set { SetValue(CanIncrementProperty, value); }
}
protected internal static readonly DependencyProperty CanIncrementProperty =
DependencyProperty.Register("CanIncrement", typeof(bool), typeof(NumericUpDown), new PropertyMetadata(false));
/// <summary>
/// 允许递减
/// </summary>
protected internal bool CanDecrement
{
get { return (bool)GetValue(CanDecrementProperty); }
set { SetValue(CanDecrementProperty, value); }
}
protected internal static readonly DependencyProperty CanDecrementProperty =
DependencyProperty.Register("CanDecrement", typeof(bool), typeof(NumericUpDown), new PropertyMetadata(false));
#endregion
}
}
App.xaml
<ResourceDictionary Source="/View/NumericUpDown/CustomNumericUpDown.xaml"/>
调用
<local:NumericUpDown Maximum="1000" Minimum="0" Height="24" Width="68" Margin="108,89,174,56" x:Name="nudDay"></local:NumericUpDown>