为什么ICommand属性通过绑定特殊处理?

时间:2013-07-18 15:57:54

标签: wpf binding icommand

到目前为止,我的印象是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受影响的其他类型吗?

1 个答案:

答案 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被调用是该实现的详细信息。框架默认转换机制(据我所知,它是未记录的,它只为明确定义的环境指出默认值和回退值),并且可能随时改变。