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
, который вызывается, представляет собой деталь реализации механизма преобразования по умолчанию каркаса (и, насколько я знаю, недокументирован, он указывает только значения по умолчанию и резервные значения для определенных обстоятельств) и может измениться в любой момент.
Это правда, но почему WPF смотрит на объявленный тип собственности в первую очередь? Разве это не просто взгляд на то, что на самом деле возвращается? –
Отредактировано. InterfaceConverter ведет себя иначе, чем другие. Это из источника C# 4.0. Таким образом, мой первоначальный ответ был немного выключен, это связано с тем, что Type.IsInterface вернет разные значения для разных объявленных типов свойств. – MrDosu
Что-то интересное я заметил, что если я объявлю свое свойство как 'MyExampleCommand' (сделав этот тип' public' заранее), кнопка все равно останется пустой. Если 'MyExampleCommand' больше не реализует' ICommand', 'ToString' правильно вызывается. Как это связано, то есть почему реализация какого-либо интерфейса, который непосредственно не упоминается в декларации о себе, ничего не меняет? –