Xamarin Forms IValueConverter: ошибка преобразования обработки

У меня есть IValueConverter, который преобразует byte[] в string и наоборот. Конвертер может преобразовать его из заданного пользователем string в byte только в том случае, если строка правильно отформатирована. Прямо сейчас я просто возвращаю исходный объект, когда преобразование не удается, что даст мне

Binding: '4' can not be converted to type 'System.Byte[]'.

ошибка в журнале. Это нормально, но я хотел бы сообщить пользователю, что написанная им строка имеет неправильный формат, показав красную рамку вокруг редактора и отключив кнопку «Отправить».

Можно ли сообщить пользовательскому интерфейсу по шаблону MVVM (PRISM), что преобразование не удалось? В WPF есть ValidationRule, которое можно использовать, для Xamarin я не нашел ничего подобного.

Преобразователь:

public class ByteArrayConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value is byte[] b)
            return BitConverter.ToString(b);//Success
        return value;//Failed
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value is string str && (str.Length - 2) % 3 == 0)
        {
            int len = (str.Length + 1) / 3;
            byte[] byteArray = new byte[len];

            for (int i = 0; i < len; i++)
                byteArray[i] = System.Convert.ToByte(str.Substring(3 * i, 2), 16);
            return byteArray;//Success
        }
        return value;//Failed
    }
}

XAML:

<?xml version = "1.0" encoding = "utf-8" ?>
<ContentPage xmlns = "http://xamarin.com/schemas/2014/forms"
             xmlns:x = "http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism = "http://prismlibrary.com"
             xmlns:vc = "clr-namespace:XXX.ValueConverter"
             prism:ViewModelLocator.AutowireViewModel = "True"
             x:Class = "XXX.Views.Device.SendMessagePage">
    <ContentPage.Resources>
        <vc:ByteArrayConverter x:Key = "byteArrayConverter"/>
    </ContentPage.Resources>
    <Editor Text = "{Binding Payload, Converter = {StaticResource byteArrayConverter}}"></Editor>
    
</ContentPage>
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
412
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Для Xamarin.Forms для этого можно использовать IValidationRule.

Во-первых, создание класса, производного от интерфейса IValidationRule, для указания правил проверки.

 public interface IValidationRule<T>
{
    string ValidationMessage { get; set; }
    bool Check(T value);
}

 public class IsNotNullOrEmptyRule<T> : IValidationRule<T>
{
    public string ValidationMessage { get; set; }

    public bool Check(T value)
    {
        if (value == null)
        {
            return false;
        }

        var str = $"{value }";
        return !string.IsNullOrWhiteSpace(str);
    }
}

public class HasValidAgeRule<T> : IValidationRule<T>
{
    public string ValidationMessage { get; set; }

    public bool Check(T value)
    {
        if (value is DateTime bday)
        {
            DateTime today = DateTime.Today;
            int age = today.Year - bday.Year;
            return (age >= 18);
        }

        return false;
    }
}

Во-вторых, добавление правил проверки к свойству.

public interface IValidatable<T> : INotifyPropertyChanged
{
    List<IValidationRule<T>> Validations { get; }

    List<string> Errors { get; set; }

    bool Validate();

    bool IsValid { get; set; }
}

 public class ValidatableObject<T> : IValidatable<T>
{
    public event PropertyChangedEventHandler PropertyChanged;

    public List<IValidationRule<T>> Validations { get; } = new List<IValidationRule<T>>();

    public List<string> Errors { get; set; } = new List<string>();

    public bool CleanOnChange { get; set; } = true;

    T _value;
    public T Value
    {
        get => _value;
        set
        {
            _value = value;

            if (CleanOnChange)
                IsValid = true;
        }
    }

    public bool IsValid { get; set; } = true;

    public virtual bool Validate()
    {
        Errors.Clear();

        IEnumerable<string> errors = Validations.Where(v => !v.Check(Value))
            .Select(v => v.ValidationMessage);

        Errors = errors.ToList();
        IsValid = !Errors.Any();

        return this.IsValid;
    }
    public override string ToString()
    {
        return $"{Value}";
    }
}

 public class validationmodel: INotifyPropertyChanged
{
    public ValidatableObject<string> FirstName { get; set; } = new ValidatableObject<string>();
    public ValidatableObject<string> LastName { get; set; } = new ValidatableObject<string>();
    public ValidatableObject<DateTime> BirthDay { get; set; } = new ValidatableObject<DateTime>() { Value = DateTime.Now };
    public validationmodel()
    {

        FirstName.Value = null;
        
        
        AddValidationRules();
        AreFieldsValid();
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void AddValidationRules()
    {
        FirstName.Validations.Add(new IsNotNullOrEmptyRule<string> { ValidationMessage = "First Name Required" });
        LastName.Validations.Add(new IsNotNullOrEmptyRule<string> { ValidationMessage = "Last Name Required" });
        BirthDay.Validations.Add(new HasValidAgeRule<DateTime> { ValidationMessage = "You must be 18 years of age or older" });
    }

    bool AreFieldsValid()
    {
        bool isFirstNameValid = FirstName.Validate();
        bool isLastNameValid = LastName.Validate();
        bool isBirthDayValid = BirthDay.Validate();
        return isFirstNameValid && isLastNameValid && isBirthDayValid;
    }



 }

Выделение элемента управления, содержащего недопустимые данные:

public class FirstValidationErrorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        ICollection<string> errors = value as ICollection<string>;
        return errors != null && errors.Count > 0 ? errors.ElementAt(0) : null;
    }

   

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

 public class InverseBoolConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!(value is bool))
        {
            throw new InvalidOperationException("The target must be a boolean");
        }

        return !(bool)value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return null;
    }
}

public class BehaviorBase<T> : Behavior<T>
where T : BindableObject
{
    #region Properties
    public T AssociatedObject
    {
        get;
        private set;
    }
    #endregion
    #region NormalMethods
    private void OnBindingContextChanged(object sender, EventArgs e)
    {
        OnBindingContextChanged();
    }
    #endregion
    #region Overrides
    protected override void OnAttachedTo(T bindable)
    {
        base.OnAttachedTo(bindable);
        AssociatedObject = bindable;
        if (bindable.BindingContext != null)
        {
            BindingContext = bindable.BindingContext;
        }

        bindable.BindingContextChanged += OnBindingContextChanged;
    }
    protected override void OnDetachingFrom(T bindable)
    {
        base.OnDetachingFrom(bindable);
        bindable.BindingContextChanged -= OnBindingContextChanged;
        AssociatedObject = null;
    }
    protected override void OnBindingContextChanged()
    {
        base.OnBindingContextChanged();
        BindingContext = AssociatedObject.BindingContext;
    }
    #endregion
}

 public class EntryLineValidationBehaviour : BehaviorBase<Entry>
{
    #region StaticFields
    public static readonly BindableProperty IsValidProperty = BindableProperty.Create(nameof(IsValid), typeof(bool), typeof(EntryLineValidationBehaviour), true, BindingMode.Default, null, (bindable, oldValue, newValue) => OnIsValidChanged(bindable, newValue));
    #endregion
    #region Properties
    public bool IsValid
    {
        get
        {
            return (bool)GetValue(IsValidProperty);
        }
        set
        {
            SetValue(IsValidProperty, value);
        }
    }
    #endregion
    #region StaticMethods
    private static void OnIsValidChanged(BindableObject bindable, object newValue)
    {
        if (bindable is EntryLineValidationBehaviour IsValidBehavior &&
             newValue is bool IsValid)
        {
            IsValidBehavior.AssociatedObject.PlaceholderColor = IsValid ? Color.Default : Color.Red;
        }
    }

    #endregion
}

Главная страница.xaml:

<ContentPage
x:Class = "validationapp.MainPage"
xmlns = "http://xamarin.com/schemas/2014/forms"
xmlns:x = "http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:behaviour = "clr-namespace:validationapp.Behaviors"
xmlns:converter = "clr-namespace:validationapp.converters"
xmlns:local = "clr-namespace:validationapp">
<ContentPage.Resources>
   
    <converter:InverseBoolConverter x:Key = "InverseBoolConverter" />
    <converter:FirstValidationErrorConverter x:Key = "FirstValidationErrorConverter" />
    <Style x:Key = "ErrorTextStyle" TargetType = "Label">
        <Setter Property = "TextColor" Value = "Red" />
        <Setter Property = "FontSize" Value = "12" />
    </Style>
</ContentPage.Resources>
<StackLayout>
    <!--  First Name  -->
    <Entry Placeholder = "First Name" Text = "{Binding FirstName.Value}">
        <Entry.Behaviors>
            <behaviour:EntryLineValidationBehaviour IsValid = "{Binding FirstName.IsValid}" />
        </Entry.Behaviors>
    </Entry>

    <Label
        IsVisible = "{Binding FirstName.IsValid, Converter = {StaticResource InverseBoolConverter}}"
        Style = "{StaticResource ErrorTextStyle}"
        Text = "{Binding FirstName.Errors, Converter = {StaticResource FirstValidationErrorConverter}}" />
    <!--  /First Name  -->

    <!--  Last Name  -->
    <Entry Placeholder = "Last Name" Text = "{Binding LastName.Value}">
        <Entry.Behaviors>
            <behaviour:EntryLineValidationBehaviour IsValid = "{Binding LastName.IsValid}" />
        </Entry.Behaviors>
    </Entry>

    <Label
        IsVisible = "{Binding LastName.IsValid, Converter = {StaticResource InverseBoolConverter}}"
        Style = "{StaticResource ErrorTextStyle}"
        Text = "{Binding LastName.Errors, Converter = {StaticResource FirstValidationErrorConverter}}" />
    <!--  /Last Name  -->


    <!--  Birthday  -->
    <DatePicker Date = "{Binding BirthDay.Value}" />
    <Label
        IsVisible = "{Binding BirthDay.IsValid, Converter = {StaticResource InverseBoolConverter}}"
        Style = "{StaticResource ErrorTextStyle}"
        Text = "{Binding BirthDay.Errors, Converter = {StaticResource FirstValidationErrorConverter}}" />
    <!--  Birthday  -->
</StackLayout>

Вы также можете запускать проверку при изменении свойств для команды Entry.

Более подробная информация о валидации, пожалуйста, посмотрите:

https://learn.microsoft.com/en-us/xamarin/xamarin-forms/enterprise-application-patterns/validation

Уфф, много кода для чего-то такого простого, как проверка пользовательского интерфейса. Я думаю, что вернусь к использованию строки в качестве свойства и проверю ее в ViewModel, когда пользователь ее введет. Но это действительно правильный ответ, спасибо.

Max R. 10.12.2020 12:07

Другие вопросы по теме