2013-07-18 2 views
2

До сих пор у меня создалось впечатление, что WPF обычно смотрит на фактический тип объекта, который получает через привязку или каким-либо другим способом, чтобы определить, какие шаблоны, стили и представление использовать. Тем не менее, теперь я столкнулся с ситуацией, которая кажется, что WPF (также?) По какой-то причине смотрит на объявленный тип свойства.Почему свойства ICommand обрабатываются специально при помощи привязок?

Это примерный вид модели:

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?

ответ

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, который вызывается, представляет собой деталь реализации механизма преобразования по умолчанию каркаса (и, насколько я знаю, недокументирован, он указывает только значения по умолчанию и резервные значения для определенных обстоятельств) и может измениться в любой момент.

+0

Это правда, но почему WPF смотрит на объявленный тип собственности в первую очередь? Разве это не просто взгляд на то, что на самом деле возвращается? –

+0

Отредактировано. InterfaceConverter ведет себя иначе, чем другие. Это из источника C# 4.0. Таким образом, мой первоначальный ответ был немного выключен, это связано с тем, что Type.IsInterface вернет разные значения для разных объявленных типов свойств. – MrDosu

+0

Что-то интересное я заметил, что если я объявлю свое свойство как 'MyExampleCommand' (сделав этот тип' public' заранее), кнопка все равно останется пустой. Если 'MyExampleCommand' больше не реализует' ICommand', 'ToString' правильно вызывается. Как это связано, то есть почему реализация какого-либо интерфейса, который непосредственно не упоминается в декларации о себе, ничего не меняет? –