到目前为止,我的印象是WPF通常会通过绑定或以任何其他方式查看对象的实际类型,以确定要使用的模板,样式和表示形式。但是,我现在面临的情况是,由于某些原因,WPF(也是?)看起来像是声明了属性类型。
这是一个示例性视图模型:
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
键入object
而不是ICommand
,则test
将按预期显示为按钮上的标签。
这里有什么问题? WPF是否真的根据返回它们的属性的声明的类型对待对象?这可以解决,ICommand
受影响的其他类型吗?
答案 0 :(得分:4)
ToString()
在object
上声明,ICommand
不是object
它是一个接口。它只能可分配到object
。
正如您所说,绑定系统不区分声明的类型。但是,转换为IValueConverter
时使用的默认string
确实存在。
在没有给出用户定义的转换器的情况下,框架内部使用DefaultValueConverter
。在Create
方法中,您可以看到为什么接口的行为与此处的对象不同(查找sourceType.IsInterface
的特定检查):
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;
}
根据文档,您应该在绑定到与您绑定的依赖项属性不同的类型的属性时提供用户定义的IValueConverter
,因为依赖于ToString
被调用是该实现的详细信息。框架默认转换机制(据我所知,它是未记录的,它只为明确定义的环境指出默认值和回退值),并且可能随时改变。