Wpf 为什么绑定会特别处理ICommand属性?

Wpf 为什么绑定会特别处理ICommand属性?,wpf,binding,icommand,Wpf,Binding,Icommand,到目前为止,我的印象是WPF通常会查看它通过绑定或以任何其他方式获得的对象的实际类型,以确定要使用的模板、样式和表示。然而,我现在面临的情况,使它看起来像WPF也?出于某种原因查看声明的属性类型 这是一个示例性视图模型: using System; using System.Windows.Input; public class SimpleViewModel { private class MyExampleCommand : ICommand { publi

到目前为止,我的印象是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键入对象而不是ICommand,则测试将按预期显示为按钮上的标签

这里怎么了?WPF是否真的根据返回对象的属性的声明类型对对象进行了不同的处理?这可以解决吗?除了ICommand之外,还有其他类型受影响吗?

ToString是在对象上声明的,ICommand不是一个对象,而是一个接口。它只能分配给对象

正如您已经说过的,绑定系统不会对声明的类型进行区分。但是,在转换为字符串的情况下使用的默认IValueConverter不起作用

在内部,当没有提供用户定义的转换器时,框架使用DefaultValueConverter。在Create方法中,您可以看到为什么接口的行为会不同,而此处的对象则会查看sourceType.IsInterface的特定检查:

根据文档,当绑定到与要绑定的依赖项属性不同类型的属性时,您应该提供一个用户定义的IValueConverter,因为所调用的ToString是框架默认转换机制的一个实现细节,据我所知是未记录的,它只为定义良好的环境声明默认值和回退值,并且可以随时更改。

ToString是在对象上声明的,ICommand不是一个对象,而是一个接口。它只能分配给对象

正如您已经说过的,绑定系统不会对声明的类型进行区分。但是,在转换为字符串的情况下使用的默认IValueConverter不起作用

在内部,当没有提供用户定义的转换器时,框架使用DefaultValueConverter。在Create方法中,您可以看到为什么接口的行为会不同,而此处的对象则会查看sourceType.IsInterface的特定检查:


根据文档,当绑定到与要绑定的依赖项属性不同类型的属性时,您应该提供一个用户定义的IValueConverter,因为所调用的ToString是框架默认转换机制的一个实现细节,据我所知是未记录的,它只为定义良好的环境声明默认值和回退值,并且可能随时更改。

没错,但为什么WPF首先要查看声明的属性类型?它不应该只看实际返回的内容吗?编辑。InterfaceConverter的行为与其他人不同。这是来自C4.0的源代码。因此,我最初的回答有点不正确,这更多是因为Type.IsInterface将为不同的声明属性类型返回不同的值。我注意到,有趣的是,如果我将属性声明为MyExampleCommand,并事先公开该类型,则按钮仍将保持为空。如果MyExampleCommand不再实现ICommand,则正确调用ToString。这是如何关联的,也就是说,为什么实现propery声明中没有直接提到的一些接口会改变什么呢?因为您使用的都是未记录的内部框架实现。您可以深入研究每种情况下的代码,看看它能做什么,但它可以并且将改变每一个版本。我接受了这个答案,因为它指出了一个解决这个问题的可行方法,尽管MSDN的文章,如或建议,ToString在某些情况下确实被称为默认情况。我将执行一些进一步的测试来确定它是否真的只引用数据模板。这是真的,但是为什么WPF首先要查看声明的属性类型呢?它不应该只看实际返回的内容吗?编辑。InterfaceConverter的行为与其他人不同。这是来自C4.0的源代码。因此,我最初的回答有点不正确,这更多是因为Type.IsInterface将为不同的声明属性类型返回不同的值。我注意到,有趣的是,如果我将属性声明为MyExampleCommand,并事先公开该类型,则按钮仍将保持为空。如果MyExampleCommand不再实现ICommand,则正确调用ToString。这是如何关联的,也就是说,为什么实现一些在propery声明中没有直接提到的接口会改变什么呢?因为您使用的所有接口都是未记录的内部接口
框架实施。您可以深入研究每种情况下的代码,看看它能做什么,但它可以并且将改变每一个版本。我接受了这个答案,因为它指出了一个解决这个问题的可行方法,尽管MSDN的文章,如或建议,ToString在某些情况下确实被称为默认情况。我将执行一些进一步的测试,以确定这是否真的只涉及数据模板。
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;
}