Как исправить свойство Xamarin.Forms Entry Attach "Bleed Over"? - PullRequest
2 голосов
/ 09 июля 2019

Я пытаюсь настроить проверку для записи Xamarin.Forms (XF) с использованием INotifyDataErrorInfo и прикрепить свойства (AP). Код работает, как и ожидалось, используя только одну запись. Проблема возникает, когда я инициализирую AP для второй записи. Затем, если ошибка возникает в Entry1, она отображается в Entry2.

Я использовал INotifyDataErrorInfo в WPF, где он работает как шарм. Единственная проблема заключается в том, что я не могу найти никакой документации о том, как свойства присоединения «Проверка» реализованы для WPF. Я рассмотрел другие альтернативы в отношении валидации, но они не так прочны, как использование INotifyDataErrorInfo вместе с оболочками пользовательских моделей.

Расширение привязки (используется для получения привязки)

public static class BindingObjectExtensions
{
    public static Binding GetBinding(this BindableObject self, BindableProperty property)
    {
        var methodInfo = typeof(BindableObject).GetTypeInfo().GetDeclaredMethod("GetContext");
        var context = methodInfo?.Invoke(self, new[] { property });

        var propertyInfo = context?.GetType().GetTypeInfo().GetDeclaredField("Binding");
        return propertyInfo?.GetValue(context) as Binding;
    }

    public static object GetBindingExpression(this Binding self)
    {
        var fieldInfo = self?.GetType().GetTypeInfo().GetDeclaredField("_expression");
        return fieldInfo?.GetValue(self);
    }
}

Свойства прикрепления

using {YourNamespace}.Extensions;
using System;
using System.Collections;
using System.ComponentModel;
using System.Linq;
using Xamarin.Forms;

public static class ValidationBehavior
{
    // Fields
    public static string _propertyName;
    public static BindableObject _view;

    public static readonly BindableProperty IsActiveProperty;
    public static readonly BindableProperty HasErrorProperty;
    public static readonly BindableProperty ErrorsProperty;

    // Constructor
    static ValidationBehavior()
    {
        IsActiveProperty = BindableProperty.CreateAttached("IsActive", typeof(bool), typeof(ValidationBehavior), default(bool), propertyChanged: OnIsActivePropertyChanged);
        ErrorsProperty = BindableProperty.CreateAttached("Errors", typeof(IList), typeof(ValidationBehavior), null);
        HasErrorProperty = BindableProperty.CreateAttached("HasError", typeof(bool), typeof(ValidationBehavior), default(bool));
    }


    // Properties
    #region IsActive Property
    public static bool GetIsActive(BindableObject obj)
    {
        return (bool)obj.GetValue(IsActiveProperty);
    }
    public static void SetIsActive(BindableObject obj, bool value)
    {
        obj.SetValue(IsActiveProperty, value);
    }
    #endregion

    #region Errors Property
    public static IList GetErrors(BindableObject obj)
    {
        return (IList)obj.GetValue(ErrorsProperty);
    }
    public static void SetErrors(BindableObject obj, IList value)
    {
        obj.SetValue(ErrorsProperty, value);
    }
    #endregion

    #region HasError Property
    public static bool GetHasError(BindableObject obj)
    {
        return (bool)obj.GetValue(HasErrorProperty);
    }
    public static void SetHasError(BindableObject obj, bool value)
    {
        obj.SetValue(HasErrorProperty, value);
    }
    #endregion


    // Methodes
    private static void OnIsActivePropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        if ((bool)newValue)
        {
            var binding = bindable.GetBinding(Entry.TextProperty); 
            if (binding != null)
            {
                string bindingPath = binding.Path;
                _propertyName = bindingPath.Split('.').Last();
                bindable.BindingContextChanged += Bindable_BindingContextChanged;
            }
        }
        else
        {
            _propertyName = null;
            bindable.BindingContextChanged -= Bindable_BindingContextChanged;
        }
    }

    private static void Bindable_BindingContextChanged(object sender, EventArgs e)
    {
        var bindable = sender as BindableObject;
        if (bindable == null)
        {
            _view = null;
            return;
        }
        else
        {
            _view = bindable;
        }

        var errorInfo = bindable.BindingContext as INotifyDataErrorInfo;
        if (errorInfo == null)
            return;

        errorInfo.ErrorsChanged += ErrorInfo_ErrorsChanged; // NB! Not sure if this will create memory leak
    }

    private static void ErrorInfo_ErrorsChanged(object sender, DataErrorsChangedEventArgs e)
    {
        if (e.PropertyName != _propertyName)
            return;

        var errorInfo = sender as INotifyDataErrorInfo;
        if (errorInfo == null)
            return;

        if (!errorInfo.HasErrors)
        {
            SetErrors(_view, null);
            SetHasError(_view, false);
        }
        else
        {
            var foundErrors = errorInfo.GetErrors(e.PropertyName);
            if (foundErrors == null)
            {
                SetErrors(_view, null);
                SetHasError(_view, false);
            }
            else
            {
                SetErrors(_view, foundErrors.Cast<string>().ToList());
                SetHasError(_view, true);
            }
        }
    }
}

Стиль записи

<Style TargetType="Entry" x:Key="EntryInput">
    <Setter Property="b:ChangeValidationBehavior.IsActive" Value="True"/>
    <Style.Triggers>
        <Trigger Property="b:ChangeValidationBehavior.IsChanged" Value="True" TargetType="Entry">
            <Setter Property="BackgroundColor" Value="SteelBlue"/>
        </Trigger>
        <Trigger Property="b:ChangeValidationBehavior.HasError" Value="True" TargetType="Entry">
            <Setter Property="BackgroundColor" Value="LightCoral"/>
        </Trigger>
    </Style.Triggers>
</Style>

Записи в представлении

<Entry BindingContext="{Binding Project}" Text="{Binding Name, Mode=TwoWay}"
       Grid.Column="1" Style="{StaticResource EntryInput}"/>

<Entry BindingContext="{Binding Project}" Text="{Binding Description, Mode=TwoWay}"
       Grid.Row="3" Grid.Column="1" Style="{StaticResource EntryInput}"/>

Я ожидал, что я смогу активировать этот ValidationBehavior для нескольких элементов управления Entry, а затем отобразить ошибки для каждого из них.

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

Кто-нибудь имеет хоть малейшее представление о том, как это сделать?

1 Ответ

0 голосов
/ 10 июля 2019

Внезапно меня осенило. _PropertyName и _view - это только статические поля.

Итак, на данный момент решение состоит в том, чтобы создать _propertyName в качестве свойства присоединения и записать метод ErrorInfo_ErrorsChanged в качестве lamda.expression.

Вот код:

using {YourNamespace}.Extensions;
using System;
using System.Collections;
using System.ComponentModel;
using System.Linq;
using Xamarin.Forms;

public static class ValidationBehavior
{
    // Fields
    public static readonly BindableProperty IsActiveProperty;
    public static readonly BindableProperty HasErrorProperty;
    public static readonly BindableProperty ErrorsProperty;
    public static readonly BindableProperty PropertyNameProperty;

    // Constructor
    static ValidationBehavior()
    {
        IsActiveProperty = BindableProperty.CreateAttached("IsActive", typeof(bool), typeof(ValidationBehavior), default(bool), propertyChanged: OnIsActivePropertyChanged);
        ErrorsProperty = BindableProperty.CreateAttached("Errors", typeof(IList), typeof(ValidationBehavior), null);
        HasErrorProperty = BindableProperty.CreateAttached("HasError", typeof(bool), typeof(ValidationBehavior), default(bool));
        PropertyNameProperty = BindableProperty.CreateAttached("PropertyName", typeof(string), typeof(ChangeValidationBehavior), default(string));
    }


    // Properties
    #region IsActive Property
    public static bool GetIsActive(BindableObject obj)
    {
        return (bool)obj.GetValue(IsActiveProperty);
    }
    public static void SetIsActive(BindableObject obj, bool value)
    {
        obj.SetValue(IsActiveProperty, value);
    }
    #endregion

    #region Errors Property
    public static IList GetErrors(BindableObject obj)
    {
        return (IList)obj.GetValue(ErrorsProperty);
    }
    public static void SetErrors(BindableObject obj, IList value)
    {
        obj.SetValue(ErrorsProperty, value);
    }
    #endregion

    #region HasError Property
    public static bool GetHasError(BindableObject obj)
    {
        return (bool)obj.GetValue(HasErrorProperty);
    }
    public static void SetHasError(BindableObject obj, bool value)
    {
        obj.SetValue(HasErrorProperty, value);
    }
    #endregion

    #region PropertyName Property
    public static string GetPropertyName(BindableObject obj)
    {
        return (string)obj.GetValue(PropertyNameProperty);
    }
    public static void SetPropertyName(BindableObject obj, string value)
    {
        obj.SetValue(PropertyNameProperty, value);
    }
    #endregion


    // Methodes
    private static void OnIsActivePropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        if ((bool)newValue)
        {
            var binding = bindable.GetBinding(Entry.TextProperty); 
            if (binding != null)
            {
                string bindingPath = binding.Path;
                string propertyName = bindingPath.Split('.').Last();
                SetPropertyName(bindable, propertyName);
                bindable.BindingContextChanged += Bindable_BindingContextChanged;
            }
        }
        else
        {
            SetPropertyName(bindable, null);
            bindable.BindingContextChanged -= Bindable_BindingContextChanged;
        }
    }

    private static void Bindable_BindingContextChanged(object sender, EventArgs e)
    {
        var bindable = sender as BindableObject;
        if (bindable == null)
            return;

        var errorInfo = bindable.BindingContext as INotifyDataErrorInfo;
        if (errorInfo == null)
            return;

        // NB! Not sure if this will create memory leak
        errorInfo.ErrorsChanged += (s, ea) =>
        {
            if (ea.PropertyName != GetPropertyName(bindable))
                return;

            var info = s as INotifyDataErrorInfo;
            if (info == null)
                return;

            if (!info.HasErrors)
            {
                SetErrors(bindable, null);
                SetHasError(bindable, false);
            }
            else
            {
                var foundErrors = info.GetErrors(ea.PropertyName);
                if (foundErrors == null)
                {
                    SetErrors(bindable, null);
                    SetHasError(bindable, false);
                }
                else
                {
                    SetErrors(bindable, foundErrors.Cast<string>().ToList());
                    SetHasError(bindable, true);
                }
            }
        };
    }
}

Возможно, это не «правильный путь», но это работает.Мы будем благодарны за любые идеи или улучшения кода.

Надеюсь, это кому-нибудь поможет:)

...