WPF上位机自定义控件系列:触摸屏数字输入键盘 WxNumericalKeyboard
WPF 上位机自定义控件系列
- 触摸屏数字输入键盘 WxNumericalKeyboard
实际项目中,客户在上位机触摸屏中输入数值时,希望有类似 PLC 触摸屏上的数字键盘输入效果,而不弹出系统自带的虚拟键盘
- 最大值、最小值
- 浮点数(两位小数)、整数
初始化时传入当前值,关闭窗体时获取最终值
xaml 文件
<Window x:Class="WpfControlsX.ControlX.WxNumericalKeyboard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:cx="clr-namespace:WpfControlsX.ControlX"
mc:Ignorable="d"
Background="{DynamicResource BrushRegion}"
BorderBrush="{DynamicResource BrushPrimary}"
BorderThickness="1"
WindowStyle="None"
ShowInTaskbar="False"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner"
Title="数值输入"
Height="230" Width="350">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../../Styles/Theme.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<StackPanel Orientation="Vertical">
<Grid Background="{DynamicResource BrushPrimary}">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<UniformGrid x:Name="PART_Double" Columns="2" Visibility="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=cx:WxNumericalKeyboard}, Path=IsDouble, Converter={StaticResource Bool2Visibility}}">
<cx:WxTextBox Title="MIN:" FontSize="14" Foreground="{DynamicResource BrushReversedText}" HorizontalContentAlignment="Left" BorderThickness="0" IsHitTestVisible="False"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=cx:WxNumericalKeyboard}, Path=Minimum, StringFormat={}{0:F2}}"/>
<cx:WxTextBox Title="MAX:" FontSize="14" Foreground="{DynamicResource BrushReversedText}" HorizontalContentAlignment="Left" BorderThickness="0" IsHitTestVisible="False"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=cx:WxNumericalKeyboard}, Path=Maximum, StringFormat={}{0:F2}}"/>
</UniformGrid>
<UniformGrid x:Name="PART_Int" Columns="2" Visibility="{Binding ElementName=PART_Double, Path=Visibility, Converter={StaticResource Visible2Reverse}}">
<cx:WxTextBox Title="MIN:" FontSize="14" Foreground="{DynamicResource BrushReversedText}"
HorizontalContentAlignment="Left" BorderThickness="0" IsHitTestVisible="False"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=cx:WxNumericalKeyboard}, Path=Minimum}"/>
<cx:WxTextBox Title="MAX:" FontSize="14" Foreground="{DynamicResource BrushReversedText}"
HorizontalContentAlignment="Left" BorderThickness="0" IsHitTestVisible="False"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=cx:WxNumericalKeyboard}, Path=Maximum}"/>
</UniformGrid>
<cx:WxButton Grid.Column="1" ButtonType="Icon" Icon="{DynamicResource IconClose}" IconSize="12" Padding="5" Margin="5"
HorizontalAlignment="Right" VerticalAlignment="Top" ToolTip="Close" IsTabStop="False"
Background="{DynamicResource BrushRegion}" Foreground="{DynamicResource BrushPrimary}" Click="WxButtonClose_Click"/>
</Grid>
<StackPanel Orientation="Horizontal">
<cx:WxTextBox BorderThickness="1" Background="{DynamicResource BrushDataGridBackgroundLight}" CornerRadius="5" Width="295"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=cx:WxNumericalKeyboard}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<cx:WxButton ButtonType="Icon" Icon="{DynamicResource IconLeftBold}" IconSize="18" Padding="0" Margin="0" Width="40" Height="32"
HorizontalAlignment="Right" VerticalAlignment="Center" ToolTip="Close" IsTabStop="False"
Background="{DynamicResource BrushDataGridBackgroundLight}" Foreground="{DynamicResource BrushPrimary}" Click="WxButtonBackspace_Click"/>
</StackPanel>
<UniformGrid Columns="5">
<cx:WxButton ButtonType="Text" Content="1" FontSize="22" HorizontalAlignment="Stretch" Margin="2" Background="{DynamicResource BrushDataGridBackgroundLight}"
Foreground="{DynamicResource BrushPrimary}" CornerRadius="5" Click="WxButton_Click"/>
<cx:WxButton ButtonType="Text" Content="2" FontSize="22" HorizontalAlignment="Stretch" Margin="2" Background="{DynamicResource BrushDataGridBackgroundLight}"
Foreground="{DynamicResource BrushPrimary}" CornerRadius="5" Click="WxButton_Click"/>
<cx:WxButton ButtonType="Text" Content="3" FontSize="22" HorizontalAlignment="Stretch" Margin="2" Background="{DynamicResource BrushDataGridBackgroundLight}"
Foreground="{DynamicResource BrushPrimary}" CornerRadius="5" Click="WxButton_Click"/>
<cx:WxButton ButtonType="Text" Content="0" FontSize="22" HorizontalAlignment="Stretch" Margin="2" Background="{DynamicResource BrushDataGridBackgroundLight}"
Foreground="{DynamicResource BrushPrimary}" CornerRadius="5" Click="WxButton_Click"/>
<cx:WxButton ButtonType="Text" Content="-" FontSize="22" HorizontalAlignment="Stretch" Margin="2" Background="{DynamicResource BrushDataGridBackgroundLight}"
Foreground="{DynamicResource BrushPrimary}" CornerRadius="5" Click="WxButton_Click"/>
</UniformGrid>
<UniformGrid Columns="5">
<cx:WxButton ButtonType="Text" Content="4" FontSize="22" HorizontalAlignment="Stretch" Margin="2" Background="{DynamicResource BrushDataGridBackgroundLight}"
Foreground="{DynamicResource BrushPrimary}" CornerRadius="5" Click="WxButton_Click"/>
<cx:WxButton ButtonType="Text" Content="5" FontSize="22" HorizontalAlignment="Stretch" Margin="2" Background="{DynamicResource BrushDataGridBackgroundLight}"
Foreground="{DynamicResource BrushPrimary}" CornerRadius="5" Click="WxButton_Click"/>
<cx:WxButton ButtonType="Text" Content="6" FontSize="22" HorizontalAlignment="Stretch" Margin="2" Background="{DynamicResource BrushDataGridBackgroundLight}"
Foreground="{DynamicResource BrushPrimary}" CornerRadius="5" Click="WxButton_Click"/>
<cx:WxButton ButtonType="Text" Content="·" FontSize="22" HorizontalAlignment="Stretch" Margin="2" Background="{DynamicResource BrushDataGridBackgroundLight}"
Foreground="{DynamicResource BrushPrimary}" CornerRadius="5" Click="WxButton_Click"/>
<cx:WxButton ButtonType="Text" Content="CLR" FontSize="22" HorizontalAlignment="Stretch" Margin="2" Background="{DynamicResource BrushDataGridBackgroundLight}"
Foreground="{DynamicResource BrushPrimary}" CornerRadius="5" Click="WxButtonClear_Click"/>
</UniformGrid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<UniformGrid Columns="3">
<cx:WxButton ButtonType="Text" Content="7" FontSize="22" HorizontalAlignment="Stretch" Margin="2" Background="{DynamicResource BrushDataGridBackgroundLight}"
Foreground="{DynamicResource BrushPrimary}" CornerRadius="5" Click="WxButton_Click"/>
<cx:WxButton ButtonType="Text" Content="8" FontSize="22" HorizontalAlignment="Stretch" Margin="2" Background="{DynamicResource BrushDataGridBackgroundLight}"
Foreground="{DynamicResource BrushPrimary}" CornerRadius="5" Click="WxButton_Click"/>
<cx:WxButton ButtonType="Text" Content="9" FontSize="22" HorizontalAlignment="Stretch" Margin="2" Background="{DynamicResource BrushDataGridBackgroundLight}"
Foreground="{DynamicResource BrushPrimary}" CornerRadius="5" Click="WxButton_Click"/>
</UniformGrid>
<cx:WxButton Grid.Column="1" ButtonType="Text" Content="Enter" FontSize="22" HorizontalAlignment="Stretch" Margin="2" Background="{DynamicResource BrushDataGridBackgroundLight}"
Foreground="{DynamicResource BrushPrimary}" CornerRadius="5" Click="WxButtonEnter_Click"/>
</Grid>
</StackPanel>
</Window>
后台文件
using System.Windows;
namespace WpfControlsX.ControlX
/// <summary>
/// WxNumericalKeyboard.xaml 的交互逻辑
/// </summary>
public partial class WxNumericalKeyboard : Window
/// <summary>
/// 当前值
/// </summary>
public string Value
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(WxNumericalKeyboard), new PropertyMetadata(null));
/// <summary>
/// 最小值
/// </summary>
public double Minimum
get { return (double)GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register("Minimum", typeof(double), typeof(WxNumericalKeyboard), new PropertyMetadata(0d));
/// <summary>
/// 最大值
/// </summary>
public double Maximum
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double), typeof(WxNumericalKeyboard), new PropertyMetadata(100d));
/// <summary>
/// 数据类型:浮点数、整数
/// </summary>
public bool IsDouble
get { return (bool)GetValue(IsDoubleProperty); }
set { SetValue(IsDoubleProperty, value); }
public static readonly DependencyProperty IsDoubleProperty =
DependencyProperty.Register("IsDouble", typeof(bool), typeof(WxNumericalKeyboard), new PropertyMetadata(true));
public double Result { get; set; } = 0;
public WxNumericalKeyboard(double value)
InitializeComponent();
Value = value.ToString();
Result = value;
private void WxButtonClose_Click(object sender, RoutedEventArgs e)
Close();
private void WxButtonBackspace_Click(object sender, RoutedEventArgs e)
if (Value.Length > 1)
Value = Value.Substring(0, Value.Length - 1);
Value = "";
private void WxButtonClear_Click(object sender, RoutedEventArgs e)
Value = "";
private void WxButtonEnter_Click(object sender, RoutedEventArgs e)
if (double.TryParse(Value, out double result))
Result = result;
Close();
private void WxButton_Click(object sender, RoutedEventArgs e)
string name = (sender as WxButton).Content.ToString();
name = name.Replace("·", ".");
Value += name;
// 浮点数
if (IsDouble)
// 首部不能连续两个 0
if (Value.StartsWith("00"))
WxButtonBackspace_Click(null, null);
// 首部不能单独 0
if (Value.StartsWith("0") && !Value.StartsWith("0."))
Value = Value.Substring(1, Value.Length - 1);
// 只能一个小数点
int idx1 = Value.IndexOf(".");
int idx2 = Value.LastIndexOf(".");
if (idx1 != idx2)
WxButtonBackspace_Click(null, null);
// 保留两位小数
int idx = Value.IndexOf(".");
if (idx > 0 && idx < Value.Length - 3)
WxButtonBackspace_Click(null, null);
// 整数
// 整数不能 0 和 . 开头
if (Value.StartsWith("0") || Value.StartsWith("."))
Value = Value.Substring(1, Value.Length - 1);
// 整数不能有小数点
if (Value.Contains("."))
WxButtonBackspace_Click(null, null);
// 尾部不能有 -
if (Value.Length > 1 && Value.EndsWith("-"))
WxButtonBackspace_Click(null, null);
// 限定范围
if (double.TryParse(Value, out double result))
if (result < Minimum || result > Maximum)
WxButtonBackspace_Click(null, null);
引用方法
/// <summary>
/// 数值输入
/// </summary>
/// <param name="idx"></param>
public static double NumericalKeyboard(double value, double min = 0, double max = 1000, bool isDouble = true)
WxNumericalKeyboard keyboard = new WxNumericalKeyboard(value)
Minimum = min,
Maximum = max,
IsDouble = isDouble,
Window win = null;
if (Application.Current.Windows.Count > 0)
win = Application.Current.Windows.OfType<Window>().FirstOrDefault(o => o.IsActive);
if (win == null)
keyboard.Show();
keyboard.Owner = win;
_ = keyboard.ShowDialog();
return keyboard.Result;
鼠标左键双击触发
<cx:WxTextBox Title="浮点数" IsReadOnly="True" Text="0.00" MouseDoubleClick="WxTextBox_MouseDoubleClick"/>
<cx:WxTextBox Title="整数" IsReadOnly="True" Text="0" MouseDoubleClick="WxTextBoxInt_MouseDoubleClick"/>
private void WxTextBox_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
WxTextBox txt = sender as WxTextBox;
double value = DialogHelper.NumericalKeyboard(double.Parse(txt.Text));
txt.Text = value.ToString("F2");
private void WxTextBoxInt_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)