问题
So far, I had the impression that WPF generally looks at the actual type of an object it gets via a binding or in any other way to determine what templates, styles and representation to use. However, I am now confronted with a situation which makes it seem like WPF (also?) looks at the declared property type for some reason.
This is an exemplary view model:
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;
}
}
}
Use an instance of that class as a data context in a window and add this button:
<Button>
<TextBlock Text="{Binding ExampleCommand}"/>
</Button>
In the running application, the button will be empty. If SimpleViewModel.ExampleCommand
is typed to object
instead of ICommand
, test
will be shown as the label on the button as expected.
What is wrong here? Does WPF really treat objects differently based on the declared type of the property that returned them? Can this be worked around, and are any other types beside ICommand
affected?
回答1:
ToString()
is declared on object
and ICommand
is not an object
it is an interface. It is only assignable to object
.
The binding system does not, as you already said, differentiate on the declared type. But the default IValueConverter
used in the case of conversion to string
does.
Internally the framework uses a DefaultValueConverter
when no user defined converter is given. In the Create
method you can see why interfaces will act differently then objects here (look for the specific check of 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;
}
According to the documentation you should provide a user defined IValueConverter
when binding to a property of different type than the dependency property you are binding to as relying on ToString
being called is an implementation detail of the frameworks default conversion mechanism (and is undocumented as far as i know, it only states default and fallback values for well defined circumstances) and could change at any moment.
来源:https://stackoverflow.com/questions/17728436/why-are-icommand-properties-treated-specially-by-bindings