Why are ICommand properties handled specifically with Bindings?

So far, I have the impression that WPF usually looks at the actual type of object that it receives through binding or some other way to determine which templates, styles, and presentation to use. However, I came across a situation that seems like WPF (also?) Is looking at the declared property type for some reason.

This is an example 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 this class as the data context in the window and add this button:

 <Button> <TextBlock Text="{Binding ExampleCommand}"/> </Button> 

In the running application, the button will be blank. If SimpleViewModel.ExampleCommand is typed on object instead of ICommand , test will be displayed as a label on the button, as expected.

What is wrong here? Does WPF really handle objects differently based on the declared type of the property it returns? Is there a way around this, and are there other types next to ICommand ?

+2
source share
1 answer

ToString() declared on an object , and ICommand not an object , it is an interface. Only assigned to object .

The binding system, as you already said, is no different from the declared type. But the default IValueConverter used in case of conversion to string is executed.

Inside the framework, it uses the DefaultValueConverter if no custom converter is specified. In the Create method, you can see why the interfaces will act differently than the objects here (find the specific sourceType.IsInterface check):

 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 eg Nullable<DateTime> to string // but it ends up doing a different conversion than the TypeConverter for the // generic inner type, eg 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 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 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 specify a custom IValueConverter when binding to a property of a different type than the dependency property to which you bind, since relying on the called ToString is a detail of the implementation of the framework’s default conversion mechanism (and, as far as I know, is undocumented, it indicates only default values ​​and fallback values ​​for certain circumstances) and may change at any time.

+4
source

Source: https://habr.com/ru/post/952064/


All Articles