使用DataAnnotation在Xamarin中进行验证

Saf*_*afa 19 c# behavior data-annotations xamarin xamarin.forms

我想在Xamarin中添加验证.为此,我使用这篇文章作为参考点:使用Data Annotation进行验证.以下是我的行为.

public class EntryValidationBehavior : Behavior<Entry>
    {
        private Entry _associatedObject;

        protected override void OnAttachedTo(Entry bindable)
        {
            base.OnAttachedTo(bindable);
            // Perform setup       

            _associatedObject = bindable;

            _associatedObject.TextChanged += _associatedObject_TextChanged;
        }

        void _associatedObject_TextChanged(object sender, TextChangedEventArgs e)
        {
            var source = _associatedObject.BindingContext as ValidationBase;
            if (source != null && !string.IsNullOrEmpty(PropertyName))
            {
                var errors = source.GetErrors(PropertyName).Cast<string>();
                if (errors != null && errors.Any())
                {
                    var borderEffect = _associatedObject.Effects.FirstOrDefault(eff => eff is BorderEffect);
                    if (borderEffect == null)
                    {
                        _associatedObject.Effects.Add(new BorderEffect());
                    }

                    if (Device.OS != TargetPlatform.Windows)
                    {
                        //_associatedObject.BackgroundColor = Color.Red;
                    }
                }
                else
                {
                    var borderEffect = _associatedObject.Effects.FirstOrDefault(eff => eff is BorderEffect);
                    if (borderEffect != null)
                    {
                        _associatedObject.Effects.Remove(borderEffect);
                    }

                    if (Device.OS != TargetPlatform.Windows)
                    {
                        _associatedObject.BackgroundColor = Color.Default;
                    }
                }
            }
        }

        protected override void OnDetachingFrom(Entry bindable)
        {
            base.OnDetachingFrom(bindable);
            // Perform clean up

            _associatedObject.TextChanged -= _associatedObject_TextChanged;

            _associatedObject = null;
        }

        public string PropertyName { get; set; }
    }
Run Code Online (Sandbox Code Playgroud)

在我的行为中,我将背景和边框添加为红色.我想自动为此条目添加标签.所以我想在这个条目上面添加一个stacklayout并在其中添加一个标签和条目.为每个控件编写标签非常繁琐.是否有可能或可能是其他更好的方式?

更新的方法(效率不高):

 <Entry Text="{Binding Email}" Placeholder="Enter Email ID" Keyboard="Email" HorizontalTextAlignment="Center">
            <Entry.Behaviors>
                <validation:EntryValidationBehavior PropertyName="Email" />
            </Entry.Behaviors>
        </Entry>
        <Label Text="{Binding Errors[Email], Converter={StaticResource FirstErrorConverter}" 
               IsVisible="{Binding Errors[Email], Converter={StaticResource ErrorLabelVisibilityConverter}"  
               FontSize="Small" 
               TextColor="Red" />
        <Entry Text="{Binding Password}" Placeholder="Enter Password" Keyboard="Text" IsPassword="true" HorizontalTextAlignment="Center">
            <Entry.Behaviors>
                <validation:EntryValidationBehavior PropertyName="Password" />
            </Entry.Behaviors>
        </Entry>
        <Label Text="{Binding Errors[Password], Converter={StaticResource FirstErrorConverter}" 
               IsVisible="{Binding Errors[Password], Converter={StaticResource ErrorLabelVisibilityConverter}"  
               FontSize="Small" 
               TextColor="Red" />
        <Entry Text="{Binding ConfirmPassword}" Placeholder="Confirm Password" Keyboard="Text" IsPassword="true" HorizontalTextAlignment="Center">
            <Entry.Behaviors>
                <validation:EntryValidationBehavior PropertyName="ConfirmPassword" />
            </Entry.Behaviors>
        </Entry>
Run Code Online (Sandbox Code Playgroud)

变流器

public class FirstErrorConverter : 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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

验证器:

public class ValidationBase : BindableBase, INotifyDataErrorInfo
    {
        private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();
        public Dictionary<string, List<string>> Errors
        {
            get { return _errors; }
        }


        public ValidationBase()
        {
            ErrorsChanged += ValidationBase_ErrorsChanged;
        }

        private void ValidationBase_ErrorsChanged(object sender, DataErrorsChangedEventArgs e)
        {
            OnPropertyChanged("HasErrors");
            OnPropertyChanged("Errors");
            OnPropertyChanged("ErrorsList");
        }

        #region INotifyDataErrorInfo Members

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        public IEnumerable GetErrors(string propertyName)
        {
            if (!string.IsNullOrEmpty(propertyName))
            {
                if (_errors.ContainsKey(propertyName) && (_errors[propertyName].Any()))
                {
                    return _errors[propertyName].ToList();
                }
                else
                {
                    return new List<string>();
                }
            }
            else
            {
                return _errors.SelectMany(err => err.Value.ToList()).ToList();
            }
        }

        public bool HasErrors
        {
            get
            {
                return _errors.Any(propErrors => propErrors.Value.Any());
            }
        }

        #endregion

        protected virtual void ValidateProperty(object value, [CallerMemberName] string propertyName = null)
        {
            var validationContext = new ValidationContext(this, null)
            {
                MemberName = propertyName
            };

            var validationResults = new List<ValidationResult>();
            Validator.TryValidateProperty(value, validationContext, validationResults);

            RemoveErrorsByPropertyName(propertyName);

            HandleValidationResults(validationResults);
            RaiseErrorsChanged(propertyName);
        }

        private void RemoveErrorsByPropertyName(string propertyName)
        {
            if (_errors.ContainsKey(propertyName))
            {
                _errors.Remove(propertyName);
            }

           // RaiseErrorsChanged(propertyName);
        }

        private void HandleValidationResults(List<ValidationResult> validationResults)
        {
            var resultsByPropertyName = from results in validationResults
                                        from memberNames in results.MemberNames
                                        group results by memberNames into groups
                                        select groups;

            foreach (var property in resultsByPropertyName)
            {
                _errors.Add(property.Key, property.Select(r => r.ErrorMessage).ToList());
               // RaiseErrorsChanged(property.Key);
            }
        }

        private void RaiseErrorsChanged(string propertyName)
        {
            ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
        }

        public IList<string> ErrorsList
        {
            get
            {
                return GetErrors(string.Empty).Cast<string>().ToList();
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

此解决方案的问题是每次任何一个属性更改时,都会为页面中的每个属性调用FirstErrorConverter.例如,有10个属性需要验证.该方法将被调用10次.其次红色边框大约需要一秒钟才能显示出来.

Die*_*uza 6

这种方法看起来很神奇,并为改进提供了很多可能性.

只是为了不让它没有答案,我想你可以尝试创建一个组件来包装你想要处理的视图,并公开你需要在外面使用的事件和属性.它将是可重用的,它可以解决问题.

所以,一步一步是:

  1. 创建包装器组件;
  2. 针对您的行为定位此控件;
  3. 公开/处理您打算使用的属性和事件;
  4. 更换简单Entry通过这CheckableEntryView对你的代码.

这是组件的XAML代码:

<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         x:Class="MyApp.CheckableEntryView">
<ContentView.Content>
    <StackLayout>
        <Label x:Name="lblContraintText" 
               Text="This is not valid"
               TextColor="Red"
               AnchorX="0"
               AnchorY="0"
               IsVisible="False"/>
        <Entry x:Name="txtEntry"
               Text="Value"/>
    </StackLayout>
</ContentView.Content>
Run Code Online (Sandbox Code Playgroud)

它是代码隐藏的代码:

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CheckableEntryView : ContentView
{
    public event EventHandler<TextChangedEventArgs> TextChanged;

    private BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(CheckableEntryView), string.Empty);
    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue( TextProperty, value); }
    }

    public CheckableEntryView ()
    {
        InitializeComponent();

        txtEntry.TextChanged += OnTextChanged;
        txtEntry.SetBinding(Entry.TextProperty, new Binding(nameof(Text), BindingMode.Default, null, null, null, this));
    }

    protected virtual void OnTextChanged(object sender, TextChangedEventArgs args)
    {
        TextChanged?.Invoke(this, args);
    }

    public Task ShowValidationMessage()
    {
        Task.Yield();
        lblContraintText.IsVisible = true;
        return lblContraintText.ScaleTo(1, 250, Easing.SinInOut);
    }

    public Task HideValidationMessage()
    {
        Task.Yield();
        return lblContraintText.ScaleTo(0, 250, Easing.SinInOut)
            .ContinueWith(t => 
                Device.BeginInvokeOnMainThread(() => lblContraintText.IsVisible = false));
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经改变了行为的事件逻辑,使其更简单.仅供参考,它是:

void _associatedObject_TextChanged(object sender, TextChangedEventArgs e)
{
    if(e.NewTextValue == "test")
        ((CheckableEntryView)sender).ShowValidationMessage();
    else
        ((CheckableEntryView)sender).HideValidationMessage();
}
Run Code Online (Sandbox Code Playgroud)

要使用它,你基本上会做同样的事情:

<local:CheckableEntryView HorizontalOptions="FillAndExpand">
    <local:CheckableEntryView.Behaviors>
        <local:EntryValidationBehavior PropertyName="Test"/><!-- this property is not being used on this example -->
    </local:CheckableEntryView.Behaviors>
</local:CheckableEntryView>
Run Code Online (Sandbox Code Playgroud)

这是它的样子:

gif样本

我没有在此示例代码上绑定验证消息,但您可以保持相同的想法.

我希望它对你有所帮助.


Ben*_*enl 5

使用Xamarin.FormsEnterprise Application Patterns电子书和下面的组件在Enterprise Apps中使用验证EntryLabelView,XAML可能如下所示:

xmlns:local="clr-namespace:View"
...
<local:EntryLabelView ValidatableObject="{Binding MyValue, Mode=TwoWay}"
                      ValidateCommand="{Binding ValidateValueCommand}" />
Run Code Online (Sandbox Code Playgroud)

视图模型:

private ValidatableObject<string> _myValue;

public ViewModel()
{
  _myValue = new ValidatableObject<string>();

  _myValue.Validations.Add(new IsNotNullOrEmptyRule<string> { ValidationMessage = "A value is required." });
}

public ValidatableObject<string> MyValue
{
  get { return _myValue; }
  set
  {
      _myValue = value;
      OnPropertyChanged(nameof(MyValue));
  }
}

public ICommand ValidateValueCommand => new Command(() => ValidateValue());

private bool ValidateValue()
{
  return _myValue.Validate(); //updates ValidatableObject.Errors
}
Run Code Online (Sandbox Code Playgroud)

类实施方式中引用的,包括ValidatableObjectIsNotNullOrEmptyRuleEventToCommandBehavior,和FirstValidationErrorConverter可以在中找到eShopOnContainers样品。

EntryLabelView.xaml:(请注意的使用Source={x:Reference view}

<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:converters="clr-namespace:Toolkit.Converters;assembly=Toolkit"
         xmlns:behaviors="clr-namespace:Toolkit.Behaviors;assembly=Toolkit"
         x:Name="view"
         x:Class="View.EntryLabelView">
  <ContentView.Resources>
    <converters:FirstValidationErrorConverter x:Key="FirstValidationErrorConverter" />
  </ContentView.Resources>
  <ContentView.Content>
    <StackLayout>
      <Entry Text="{Binding ValidatableObject.Value, Mode=TwoWay, Source={x:Reference view}}">
        <Entry.Behaviors>
          <behaviors:EventToCommandBehavior 
                            EventName="TextChanged"
                            Command="{Binding ValidateCommand, Source={x:Reference view}}" />
        </Entry.Behaviors>
      </Entry>
      <Label Text="{Binding ValidatableObject.Errors, Source={x:Reference view},
                        Converter={StaticResource FirstValidationErrorConverter}}" />
    </StackLayout>
  </ContentView.Content>
</ContentView>
Run Code Online (Sandbox Code Playgroud)

EntryLabelView.xaml.cs:(请注意使用OnPropertyChanged)。

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class EntryLabelView : ContentView
{
    public EntryLabelView ()
    {
        InitializeComponent ();
    }

    public static readonly BindableProperty ValidatableObjectProperty = BindableProperty.Create(
        nameof(ValidatableObject), typeof(ValidatableObject<string>), typeof(EntryLabelView), default(ValidatableObject<string>),
        BindingMode.TwoWay,
        propertyChanged: (b, o, n) => ((EntryLabelView)b).ValidatableObjectChanged(o, n));

    public ValidatableObject<string> ValidatableObject
    {
        get { return (ValidatableObject<string>)GetValue(ValidatableObjectProperty); }
        set { SetValue(ValidatableObjectProperty, value); }
    }

    void ValidatableObjectChanged(object o, object n)
    {
        ValidatableObject = (ValidatableObject<string>)n;
        OnPropertyChanged(nameof(ValidatableObject));
    }

    public static readonly BindableProperty ValidateCommandProperty = BindableProperty.Create(
        nameof(Command), typeof(ICommand), typeof(EntryLabelView), null,
        propertyChanged: (b, o, n) => ((EntryLabelView)b).CommandChanged(o, n));

    public ICommand ValidateCommand
    {
        get { return (ICommand)GetValue(ValidateCommandProperty); }
        set { SetValue(ValidateCommandProperty, value); }
    }

    void CommandChanged(object o, object n)
    {
        ValidateCommand = (ICommand)n;
        OnPropertyChanged(nameof(ValidateCommand));
    }
}
Run Code Online (Sandbox Code Playgroud)