UWP: x: привязка и проверка данных для числового поля

Я немного борюсь с UWP, x:Bind и проверка данных. У меня очень простой вариант использования: я хочу, чтобы пользователь ввел int в TextBox и отобразить номер в TextBlock как только пользователь покидает TextBox, Я могу установить InputScope="Number" для TextBox, но это не мешает тому, кто печатает с клавиатуры, вводить альфа-символ (или вставлять что-то). Проблема в том, когда я связываю поле с Mode=TwoWayкажется, что вы не можете предотвратить System.ArgumentException если связываемое вами поле объявлено как int, Я хотел проверить в set метод, если вход был числом, но исключение происходит непосредственно перед этим. Мой (очень простой) ViewModel (здесь нет модели, я старался сделать его максимально простым):

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;
        }
    }
}

Мой код позади:

public sealed partial class MainPage : Page
{
    public MyViewModel ViewModel { get; set; } = new MyViewModel() { MyFieldToValidate = 0 };

    public MainPage()
    {
        this.InitializeComponent();
    }
}

И весь мой 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>

Если я наберу числовой символ в TextBox, все хорошо. Но если я наберу нечисловое значение (скажем, "d") (оно даже не достигает точки останова в первой скобке set метод для MyFieldToValidate):

Imgur

Есть ли лучшая практика делать то, что я хочу делать? Самое простое решение - это, во-первых, запретить пользователю вводить другой символ, кроме числового, но я искал часы, не находя простого способа... Другим решением будет проверка данных при выходе из поля, но я не нашел что-то актуальное для UWP и x:Bind (Несколько вещей для WPF, но они не могут быть воспроизведены с помощью UWP). Спасибо!

1 ответ

Решение

Как сказал @RTDev, ваше исключение вызвано тем, что система не может преобразовать строку в int.

Вы можете создать класс, который позволит вам преобразовывать формат ваших данных между источником и целью путем наследования от IValueConverter.

Вы всегда должны реализовывать Convert(Object, TypeName, Object, String) с функциональной реализацией, но довольно часто реализуется ConvertBack(Object, TypeName, Object, String), чтобы он сообщал о неосуществленном исключении. Вам нужен только метод ConvertBack(Object, TypeName, Object, String) в конвертере, если вы используете конвертер для двусторонних привязок или XAML для сериализации.

Для получения дополнительной информации см. Интерфейс IValueConverter.

Например:

<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>

Класс IntFormatter:

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;
        }
    }
}

Если вы не хотите, чтобы пользователи вводили буквенно-цифровые символы, я думаю, что наиболее элегантным решением будет создание нового класса NumberBox, который наследуется от класса InputBox, и перегрузка метода OnKeyDown для перехвата буквенно-цифровых нажатий клавиш, примерно так:

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;
        }
    }
}

Затем в вашем XAML добавьте пространство имен для ссылки на пространство имен, в котором находится ваш класс NumberBox, а затем замените InputBox на control:NumberBox, примерно так:

<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>
Другие вопросы по тегам