2

到目前为止,我的印象是 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吗?

4

1 回答 1

4

ToString()声明在object并且ICommand不是一个object它是一个接口。它只能分配object

正如您已经说过的,绑定系统不会区分声明的类型。但是在IValueConverter转换为的情况下使用的默认值string确实如此。

DefaultValueConverter当没有给出用户定义的转换器时,框架在内部使用 a 。在该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被调用是框架默认转换机制的实现细节(据我所知,没有记录,它仅说明定义明确的情况下的默认值和回退值)并且可能随时更改。

于 2013-07-18T16:04:42.313 回答