Xamarin Forms IValueConverter:处理转换错误
Xamarin Forms IValueConverter: Handle converting error
我有一个 IValueConverter 可以将 byte[]
转换为 string
,反之亦然。当字符串格式正确时,转换器只能将其从用户给定的 string
转换为 byte
。现在我只是 return 转换失败时的原始对象,这会给我一个
Binding: '4' can not be converted to type 'System.Byte[]'.
日志中有错误。这没关系,但我想通过在编辑器周围显示红色边框并禁用“发送”按钮来通知用户他写的字符串格式不正确。
是否可以通过 MVVM 模式 (PRISM) 告诉 UI 转换失败?在 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>
对于 Xamarin.Forms,您可以使用 IValidationRule 来执行此操作。
首先,创建一个派生自 IValidationRule 接口的 class 来指定验证规则。
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
}
MainPage.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>
您还可以在条目命令的属性更改时触发验证。
更详细的验证信息,请看:
https://docs.microsoft.com/en-us/xamarin/xamarin-forms/enterprise-application-patterns/validation
我有一个 IValueConverter 可以将 byte[]
转换为 string
,反之亦然。当字符串格式正确时,转换器只能将其从用户给定的 string
转换为 byte
。现在我只是 return 转换失败时的原始对象,这会给我一个
Binding: '4' can not be converted to type 'System.Byte[]'.
日志中有错误。这没关系,但我想通过在编辑器周围显示红色边框并禁用“发送”按钮来通知用户他写的字符串格式不正确。
是否可以通过 MVVM 模式 (PRISM) 告诉 UI 转换失败?在 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>
对于 Xamarin.Forms,您可以使用 IValidationRule 来执行此操作。
首先,创建一个派生自 IValidationRule 接口的 class 来指定验证规则。
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
}
MainPage.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>
您还可以在条目命令的属性更改时触发验证。
更详细的验证信息,请看:
https://docs.microsoft.com/en-us/xamarin/xamarin-forms/enterprise-application-patterns/validation