Wpf 为什么绑定会特别处理ICommand属性?
到目前为止,我的印象是WPF通常会查看它通过绑定或以任何其他方式获得的对象的实际类型,以确定要使用的模板、样式和表示。然而,我现在面临的情况,使它看起来像WPF也?出于某种原因查看声明的属性类型 这是一个示例性视图模型:Wpf 为什么绑定会特别处理ICommand属性?,wpf,binding,icommand,Wpf,Binding,Icommand,到目前为止,我的印象是WPF通常会查看它通过绑定或以任何其他方式获得的对象的实际类型,以确定要使用的模板、样式和表示。然而,我现在面临的情况,使它看起来像WPF也?出于某种原因查看声明的属性类型 这是一个示例性视图模型: using System; using System.Windows.Input; public class SimpleViewModel { private class MyExampleCommand : ICommand { publi
using System;
using System.Windows.Input;
public class SimpleViewModel
{
private class MyExampleCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
}
public override string ToString()
{
return "test";
}
}
private ICommand exampleCommand;
public ICommand ExampleCommand
{
get
{
if (exampleCommand == null)
{
exampleCommand = new MyExampleCommand();
}
return exampleCommand;
}
}
}
在窗口中将该类的实例用作数据上下文,并添加此按钮:
<Button>
<TextBlock Text="{Binding ExampleCommand}"/>
</Button>
在正在运行的应用程序中,按钮将为空。如果将SimpleViewModel.ExampleCommand键入对象而不是ICommand,则测试将按预期显示为按钮上的标签
这里怎么了?WPF是否真的根据返回对象的属性的声明类型对对象进行了不同的处理?这可以解决吗?除了ICommand之外,还有其他类型受影响吗?ToString是在对象上声明的,ICommand不是一个对象,而是一个接口。它只能分配给对象
正如您已经说过的,绑定系统不会对声明的类型进行区分。但是,在转换为字符串的情况下使用的默认IValueConverter不起作用
在内部,当没有提供用户定义的转换器时,框架使用DefaultValueConverter。在Create方法中,您可以看到为什么接口的行为会不同,而此处的对象则会查看sourceType.IsInterface的特定检查:
根据文档,当绑定到与要绑定的依赖项属性不同类型的属性时,您应该提供一个用户定义的IValueConverter,因为所调用的ToString是框架默认转换机制的一个实现细节,据我所知是未记录的,它只为定义良好的环境声明默认值和回退值,并且可以随时更改。ToString是在对象上声明的,ICommand不是一个对象,而是一个接口。它只能分配给对象
正如您已经说过的,绑定系统不会对声明的类型进行区分。但是,在转换为字符串的情况下使用的默认IValueConverter不起作用
在内部,当没有提供用户定义的转换器时,框架使用DefaultValueConverter。在Create方法中,您可以看到为什么接口的行为会不同,而此处的对象则会查看sourceType.IsInterface的特定检查:
根据文档,当绑定到与要绑定的依赖项属性不同类型的属性时,您应该提供一个用户定义的IValueConverter,因为所调用的ToString是框架默认转换机制的一个实现细节,据我所知是未记录的,它只为定义良好的环境声明默认值和回退值,并且可能随时更改。没错,但为什么WPF首先要查看声明的属性类型?它不应该只看实际返回的内容吗?编辑。InterfaceConverter的行为与其他人不同。这是来自C4.0的源代码。因此,我最初的回答有点不正确,这更多是因为Type.IsInterface将为不同的声明属性类型返回不同的值。我注意到,有趣的是,如果我将属性声明为MyExampleCommand,并事先公开该类型,则按钮仍将保持为空。如果MyExampleCommand不再实现ICommand,则正确调用ToString。这是如何关联的,也就是说,为什么实现propery声明中没有直接提到的一些接口会改变什么呢?因为您使用的都是未记录的内部框架实现。您可以深入研究每种情况下的代码,看看它能做什么,但它可以并且将改变每一个版本。我接受了这个答案,因为它指出了一个解决这个问题的可行方法,尽管MSDN的文章,如或建议,ToString在某些情况下确实被称为默认情况。我将执行一些进一步的测试来确定它是否真的只引用数据模板。这是真的,但是为什么WPF首先要查看声明的属性类型呢?它不应该只看实际返回的内容吗?编辑。InterfaceConverter的行为与其他人不同。这是来自C4.0的源代码。因此,我最初的回答有点不正确,这更多是因为Type.IsInterface将为不同的声明属性类型返回不同的值。我注意到,有趣的是,如果我将属性声明为MyExampleCommand,并事先公开该类型,则按钮仍将保持为空。如果MyExampleCommand不再实现ICommand,则正确调用ToString。这是如何关联的,也就是说,为什么实现一些在propery声明中没有直接提到的接口会改变什么呢?因为您使用的所有接口都是未记录的内部接口
框架实施。您可以深入研究每种情况下的代码,看看它能做什么,但它可以并且将改变每一个版本。我接受了这个答案,因为它指出了一个解决这个问题的可行方法,尽管MSDN的文章,如或建议,ToString在某些情况下确实被称为默认情况。我将执行一些进一步的测试,以确定这是否真的只涉及数据模板。
internal static IValueConverter Create(Type sourceType,
Type targetType,
bool targetToSource,
DataBindEngine engine)
{
TypeConverter typeConverter;
Type innerType;
bool canConvertTo, canConvertFrom;
bool sourceIsNullable = false;
bool targetIsNullable = false;
// sometimes, no conversion is necessary
if (sourceType == targetType ||
(!targetToSource && targetType.IsAssignableFrom(sourceType)))
{
return ValueConverterNotNeeded;
}
// the type convert for System.Object is useless. It claims it can
// convert from string, but then throws an exception when asked to do
// so. So we work around it.
if (targetType == typeof(object))
{
// The sourceType here might be a Nullable type: consider using
// NullableConverter when appropriate. (uncomment following lines)
//Type innerType = Nullable.GetUnderlyingType(sourceType);
//if (innerType != null)
//{
// return new NullableConverter(new ObjectTargetConverter(innerType),
// innerType, targetType, true, false);
//}
//
return new ObjectTargetConverter(sourceType, engine);
}
else if (sourceType == typeof(object))
{
// The targetType here might be a Nullable type: consider using
// NullableConverter when appropriate. (uncomment following lines)
//Type innerType = Nullable.GetUnderlyingType(targetType);
// if (innerType != null)
// {
// return new NullableConverter(new ObjectSourceConverter(innerType),
// sourceType, innerType, false, true);
// }
//
return new ObjectSourceConverter(targetType, engine);
}
// use System.Convert for well-known base types
if (SystemConvertConverter.CanConvert(sourceType, targetType))
{
return new SystemConvertConverter(sourceType, targetType);
}
// Need to check for nullable types first, since NullableConverter is a bit over-eager;
// TypeConverter for Nullable can convert e.g. Nullable<DateTime> to string
// but it ends up doing a different conversion than the TypeConverter for the
// generic's inner type, e.g. bug 1361977
innerType = Nullable.GetUnderlyingType(sourceType);
if (innerType != null)
{
sourceType = innerType;
sourceIsNullable = true;
}
innerType = Nullable.GetUnderlyingType(targetType);
if (innerType != null)
{
targetType = innerType;
targetIsNullable = true;
}
if (sourceIsNullable || targetIsNullable)
{
// single-level recursive call to try to find a converter for basic value types
return Create(sourceType, targetType, targetToSource, engine);
}
// special case for converting IListSource to IList
if (typeof(IListSource).IsAssignableFrom(sourceType) &&
targetType.IsAssignableFrom(typeof(IList)))
{
return new ListSourceConverter();
}
// Interfaces are best handled on a per-instance basis. The type may
// not implement the interface, but an instance of a derived type may.
if (sourceType.IsInterface || targetType.IsInterface)
{
return new InterfaceConverter(sourceType, targetType);
}
// try using the source's type converter
typeConverter = GetConverter(sourceType);
canConvertTo = (typeConverter != null) ? typeConverter.CanConvertTo(targetType) : false;
canConvertFrom = (typeConverter != null) ? typeConverter.CanConvertFrom(targetType) : false;
if ((canConvertTo || targetType.IsAssignableFrom(sourceType)) &&
(!targetToSource || canConvertFrom || sourceType.IsAssignableFrom(targetType)))
{
return new SourceDefaultValueConverter(typeConverter, sourceType, targetType,
targetToSource && canConvertFrom, canConvertTo, engine);
}
// if that doesn't work, try using the target's type converter
typeConverter = GetConverter(targetType);
canConvertTo = (typeConverter != null) ? typeConverter.CanConvertTo(sourceType) : false;
canConvertFrom = (typeConverter != null) ? typeConverter.CanConvertFrom(sourceType) : false;
if ((canConvertFrom || targetType.IsAssignableFrom(sourceType)) &&
(!targetToSource || canConvertTo || sourceType.IsAssignableFrom(targetType)))
{
return new TargetDefaultValueConverter(typeConverter, sourceType, targetType,
canConvertFrom, targetToSource && canConvertTo, engine);
}
// nothing worked, give up
return null;
}