2013-07-18 27 views
2

到目前爲止,我的印象是WPF通常會通過綁定或其他方式查看它所獲取對象的實際類型,以確定要使用的模板,樣式和表示。不過,我現在面臨的情況是WPF(也?)出於某種原因查看聲明的屬性類型。爲什麼要通過綁定特別處理ICommand屬性?

這是一個示例性視圖模型:

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

ToString()objectICommand聲明不是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是一個框架默認轉換機制的實現細節(並且據我所知,它沒有記錄,它只爲明確定義的情況指定默認值和回退值),並且可能會改變任何時候。

+0

這是真的,但爲什麼WPF首先查看聲明的屬性類型?它不應該只是看看實際返回的內容嗎? –

+0

已編輯。 InterfaceConverter的行爲與其他行爲不同。這是來自C#4.0源代碼。所以我最初的答案是有點關閉,更多的是因爲Type.IsInterface將爲不同的聲明屬性類型返回不同的值。 – MrDosu

+0

我注意到的一件有趣的事情是,如果我將屬性聲明爲「MyExampleCommand」(預先設置「public」類型),則該按鈕仍然爲空。如果'MyExampleCommand'不再實現'ICommand',則正確調用'ToString'。這是如何相關的,也就是說爲什麼實現一些在Propery聲明中沒有直接提到的接口會改變什麼? –

相關問題