Xaml中的Binding语法是WPF的中的灵魂,但是很多业务场景下,我们都需要通过IValueConverter接口来实现一个转换器。
这种实现方式会导致我们编码的过程中会存在越来越多的转换器,每写一个转换器,命名或许都是个头疼的问题。尤其是针对多路Binding的时候,xaml的代码编写量会比较多,严重降低编码效率。
为了优化转化器的使用,我们可以通过Xaml扩展,自定义一个Binding的扩展方法。暂且命名为“XBinding”。
具体实现思路如下:
我们自定义一个Binding,该Binding本质还是原来的那个Binding,只是我们在原来的Binding的基础上做了一些逻辑精简。Binding所使用的转换器为IMultiValueConverter。考虑到Button按钮还存在Command的Binding场景,所以在XBinding中也增加了对Command的支持。
具体实现看代码:
XBinding扩展代码实现
[ContentProperty("Bindings")]
public class XBinding : MarkupExtension
{
public XBinding()
{
}
public string Method { get; set; }
[ConstructorArgument("Path")]
/// <summary>
///
/// </summary>
public string Path { get; set; }
/// <summary>
/// 多路绑定使用
/// </summary>
public MultiBinding Bindings { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (Method == null)
{
throw new ArgumentNullException("method");
}
IProvideValueTarget provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
var rootObjectProvider = (IRootObjectProvider)serviceProvider.GetService(typeof(IRootObjectProvider));
var rootObject = rootObjectProvider?.RootObject;
if (rootObject != null)
{
MethodInfo method = rootObject.GetType().GetMethod(Method);
bool isCommand = false;
var targetObj = provideValueTarget.TargetObject;
if (targetObj is CommandBinding)
{
throw new NotSupportedException("XBinding不支持CommandBinding");
}
else
{
var targetProperty = provideValueTarget.TargetProperty as DependencyProperty;
if (targetProperty != null && targetProperty.PropertyType == typeof(ICommand))
{
isCommand = true;
}
}
if (Bindings != null)
{
Bindings.Converter = new XBindingMultiValueConverter() { Target = rootObject, ConvertMethod = method, IsCommand = isCommand };
return Bindings.ProvideValue(serviceProvider);
}
else
{
var multiBinding = new MultiBinding() { Converter = new XBindingMultiValueConverter() { Target = rootObject, ConvertMethod = method, IsCommand = isCommand } };
multiBinding.ConverterParameter = ConverterParameter;
string[] propertyPaths;
if (string.IsNullOrWhiteSpace(Path))
{
multiBinding.Bindings.Add(new Binding());
}
else
{
propertyPaths = Path.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var propertyPath in propertyPaths)
{
multiBinding.Bindings.Add(new Binding(propertyPath));
}
}
return multiBinding.ProvideValue(serviceProvider);
}
}
return null;
}
#region Binding Properties
[DefaultValue(BindingMode.Default)]
public BindingMode Mode { get; set; }
[DefaultValue("")]
public RelativeSource RelativeSource { get; set; }
public object Source { get; set; }
[DefaultValue("")]
public string ElementName { get; set; }
[DefaultValue("")]
public string StringFormat { get; set; }
[DefaultValue("")]
public object ConverterParameter { get; set; }
#endregion
}
多路绑定的转换器实现:
/// <summary>
/// 多路绑定
/// </summary>
internal class XBindingMultiValueConverter : IMultiValueConverter
{
/// <summary>
/// 要执行多路转换的实例对象
/// </summary>
internal object Target { get; set; }
/// <summary>
/// 转换的方法
/// </summary>
internal MethodInfo ConvertMethod { get; set; }
/// <summary>
/// 反转转换的方法
/// </summary>
internal MethodInfo ConvertBackMethod { get; set; }
/// <summary>
/// 是否为Command
/// </summary>
internal bool IsCommand { get; set; }
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (ConvertMethod != null)
{
if (IsCommand)
{
return new XCommand((p) =>
{
if (p is object[] objs)
{
Execute(objs, parameter, targetType);
}
else
{
Execute(new object[1] { p }, parameter, targetType);
}
});
}
else
{
return Execute(values, parameter, targetType);
}
}
else
{
#if DEBUG
//throw new ArgumentNullException(nameof(ConvertMethod));
#endif
return DependencyProperty.UnsetValue;
}
}
private object Execute(object[] values, object parameter, Type targetType)
{
var parameterCount = ConvertMethod.GetParameters().Length;
if (parameterCount == 0)
{
return ConvertMethod.Invoke(Target, new object[] { });
}
else
{
var count = Math.Min(parameterCount, values.Length + 1);
var objs = new object[count];
if (values == null)
{
return ConvertMethod.Invoke(Target, objs);
}
else
{
for (int i = 0; i < count; i++)
{
if (i == values.Length)
{
objs[i] = parameter;
}
else if (i > values.Length)
{
//默认null
}
else
{
if (values[i] != DependencyProperty.UnsetValue)
{
objs[i] = values[i];
}
}
}
if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{
if (targetType.IsValueType)
{
return Activator.CreateInstance(targetType);
}
else
{
return null;
}
}
else
{
//无法转换类型
return ConvertMethod.Invoke(Target, objs);
}
}
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
if (ConvertBackMethod != null)
{
var objs = new object[] { value };
return (object[])ConvertBackMethod.Invoke(Target, objs);
}
else
{
throw new ArgumentNullException(nameof(ConvertBackMethod));
}
}
}
ICommand的实现:
/// <summary>
/// 表示XBinding所支持的ICommand
/// </summary>
public class XCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<bool> _canExecute;
public XCommand(Action<object> execute) : this(execute, null)
{
}
public XCommand(Action<object> execute, Func<bool> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute();
}
public void Execute(object parameter)
{
if (_execute != null)
{
_execute(parameter);
}
}
}
具体使用方法:
// 参考语法
// 单路绑定:
PlayStatus="{XBinding Path=IsSpeaking,Method=GetExpectPlayStatus}"
// 多路绑定:
PlayStatus="{XBinding Path=IsSpeaking Sex,Method=GetExpectPlayStatus}"
<SvgaPlayerControl.SourceUri>
<XBinding Method = "GetSpeakingSvga" >
< MultiBinding >
< Binding Path="IsSpeaking"/>
<Binding Path = "Player.Sex" />
< Binding RelativeSource="{RelativeSource AncestorType=SvgaPlayerControl}"/>
</MultiBinding>
</XBinding>
</SvgaPlayerControl.SourceUri>
对应的Mothod需要在xaml对应的cs代码中编写。
结合以上代码,XBinding就可以应用到我们Xaml中了,对开发效率的提升还是有不少的。当然了,如果可以梳理一些通用转换器,那效率会更高。针对灵活处理的转换器,那么可以试试XBinding。
博客地址:https://huchengv5.github.io/
微信公众号:
欢迎转载分享,如若转载,请标注署名。
本文会经常更新,请阅读原文: https://huchengv5.gitee.io//post/NET-WPF%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0-%E4%B8%8D%E7%94%A8%E5%86%99%E8%BD%AC%E6%8D%A2%E5%99%A8%E7%9A%84Binding.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名胡承(包含链接: https://huchengv5.gitee.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。