如何修复 Xamarin.Forms 条目附加 属性 "Bleed Over"?
How to fix Xamarin.Forms Entry Attach Property "Bleed Over"?
我正在尝试使用 INotifyDataErrorInfo 和附加属性 (AP) 为 Xamarin.Forms(XF) 条目设置验证。该代码仅使用一个条目时按预期工作。当我为第二个条目初始化 AP 时出现问题。然后,如果 Entry1 发生错误,它会显示在 Entry2 中。
我在 WPF 中使用了 INotifyDataErrorInfo,它的工作原理非常棒。唯一的问题是我找不到任何关于如何为 WPF 实现 "Validation" 附加属性的文档。我已经查看了有关验证的其他替代方案,但似乎没有将 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}"/>
我期望发生的是我应该能够为多个 Entry 控件激活此 ValidationBehavior,然后显示每个控件的错误。
我猜这与 AP 是静态的这一事实有关 class 属性也是静态的,但是我有点迷路了,因为如果这是正确的那么什么是具有附加属性的点..
有没有人知道如何做到这一点?
恍然大悟.._属性Name和_view只是静态字段..
所以现在的解决方案是创建 _属性Name 作为附加 属性 并将 "ErrorInfo_ErrorsChanged" 方法编写为 lamda 表达式。
代码如下:
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);
}
}
};
}
}
这可能不是 "right way" 可以做到的,但它确实有效。
非常感谢对代码的任何见解或改进。
希望对大家有所帮助:)
我正在尝试使用 INotifyDataErrorInfo 和附加属性 (AP) 为 Xamarin.Forms(XF) 条目设置验证。该代码仅使用一个条目时按预期工作。当我为第二个条目初始化 AP 时出现问题。然后,如果 Entry1 发生错误,它会显示在 Entry2 中。
我在 WPF 中使用了 INotifyDataErrorInfo,它的工作原理非常棒。唯一的问题是我找不到任何关于如何为 WPF 实现 "Validation" 附加属性的文档。我已经查看了有关验证的其他替代方案,但似乎没有将 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}"/>
我期望发生的是我应该能够为多个 Entry 控件激活此 ValidationBehavior,然后显示每个控件的错误。
我猜这与 AP 是静态的这一事实有关 class 属性也是静态的,但是我有点迷路了,因为如果这是正确的那么什么是具有附加属性的点..
有没有人知道如何做到这一点?
恍然大悟.._属性Name和_view只是静态字段..
所以现在的解决方案是创建 _属性Name 作为附加 属性 并将 "ErrorInfo_ErrorsChanged" 方法编写为 lamda 表达式。
代码如下:
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);
}
}
};
}
}
这可能不是 "right way" 可以做到的,但它确实有效。 非常感谢对代码的任何见解或改进。
希望对大家有所帮助:)