'x:bind and data validation for numeric field
I'm struggling a bit with UWP, x:Bind
and data validation.
I've got a very simple use case: I want the user to input an int
in a TextBox
and display the number in a TextBlock
as soon as the user leaves the TextBox
.
I can set the InputScope="Number"
for the TextBox
, but that doesn't prevent someone who type with a keyboard to type an alpha char (or paste something).
Problem is, when I bind a field with the Mode=TwoWay
, it seems that you can't prevent a System.ArgumentException
if the field that you bind is declared as int
. I wanted to check in the set
method if the input was a number, but the exception occurs just before that.
My (very simple) ViewModel (no model here, I tried to keep it as simple as possible):
public class MyViewModel : INotifyPropertyChanged
{
private int _MyFieldToValidate;
public int MyFieldToValidate
{
get { return _MyFieldToValidate; }
set
{
this.Set(ref this._MyFieldToValidate, value);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisedPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return false;
}
else
{
storage = value;
this.RaisedPropertyChanged(propertyName);
return true;
}
}
}
My code behind:
public sealed partial class MainPage : Page
{
public MyViewModel ViewModel { get; set; } = new MyViewModel() { MyFieldToValidate = 0 };
public MainPage()
{
this.InitializeComponent();
}
}
And my whole XAML:
<Page
x:Class="SimpleFieldValidation.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SimpleFieldValidation"
xmlns:vm="using:SimpleFieldValidation.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="10*" />
<RowDefinition Height="*" />
<RowDefinition Height="10*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Row="1" Grid.Column="0" Text="{x:Bind ViewModel.MyFieldToValidate, Mode=TwoWay}" x:Name="inputText" InputScope="Number" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{x:Bind ViewModel.MyFieldToValidate, Mode=OneWay}" x:Name="textToDisplay" />
</Grid>
</Page>
If I type a numeric char in the TextBox
, everything's OK. But if I type a non-numeric value (say "d") (it doesn't even reach the breakpoint at the first bracket of the set
method for MyFieldToValidate
):
Is there a best practice to do what I want to do? The simplest solution would be preventing the user to type other char than numeric in the first place, but I've been searching for hours without finding a simple way... Another solution would be to validate the data on leaving the field, but I didn't find something relevant for UWP and x:Bind
(few things for WPF thought, but they can't be replicated with a UWP).
Thanks!
Solution 1:[1]
As @RTDev said, your exception is caused by the system can not convert string to int.
You can create a class that allows you to convert the format of your data between the source and the target by inheriting from IValueConverter.
You should always implement Convert(Object, TypeName, Object, String) with a functional implementation, but it's fairly common to implement ConvertBack(Object, TypeName, Object, String) so that it reports a not-implemented exception. You only need a ConvertBack(Object, TypeName, Object, String) method in your converter if you are using the converter for two-way bindings, or using XAML for serialization.
For more info, see IValueConverter Interface.
For example:
<Page.Resources>
<local:IntFormatter x:Key="IntConverter" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="10*" />
<RowDefinition Height="*" />
<RowDefinition Height="10*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Row="1" Grid.Column="0" Text="{x:Bind ViewModel.MyFieldToValidate, Mode=TwoWay,Converter={StaticResource IntConverter}}" x:Name="inputText" InputScope="Number" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{x:Bind ViewModel.MyFieldToValidate, Mode=OneWay}" x:Name="textToDisplay" />
</Grid>
The IntFormatter class:
internal class IntFormatter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value != null)
{
return value.ToString();
}
else
{
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
int n;
bool isNumeric = int.TryParse(value.ToString(), out n);
if (isNumeric)
{
return n;
}
else
{
return 0;
}
}
}
Solution 2:[2]
If you don't want the users to type alphanumerical characters, I think the most elegant solution is to create a new class NumberBox that inherits from the class InputBox and overload the OnKeyDown method to intercept the alphanumerical keystrokes, something like this:
using Windows.System;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
namespace MyProject.Controls
{
public sealed class NumberBox : TextBox
{
protected override void OnKeyDown(KeyRoutedEventArgs e)
{
if (e.Key >= VirtualKey.Number0 && e.Key <= VirtualKey.Number9 ||
e.Key >= VirtualKey.NumberPad0 && e.Key <= VirtualKey.NumberPad9 ||
e.Key >= VirtualKey.Left && e.Key <= VirtualKey.Down ||
e.Key == VirtualKey.Delete ||
e.Key == VirtualKey.Tab ||
e.Key == VirtualKey.Back ||
e.Key == VirtualKey.Enter)
base.OnKeyDown(e);
else
e.Handled = true;
}
}
}
Then in your XAML, add a namespace to reference the namespace where your NumberBox class is, and then replace InputBox with control:NumberBox, something like this:
<Page
x:Class="MyProject.View.CalibrarEnfoque"
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:local="using:MyProject.View"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:MyProject.Controls"
mc:Ignorable="d">
<Grid>
<controls:NumberBox Text="{x:Bind ViewModel.MyValue, Mode=TwoWay}"/>
</Grid>
</Page>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Community |
Solution 2 | joseangelmt |