2016-07-08 59 views
0

我創建了一些附加屬性以允許間接綁定(我的意思是綁定到名稱由附加屬性給出,而不是在XAML中指定爲文字的值)。在部分構造的對象上綁定錯誤

一些AP是可選的(例如覆蓋DataContext否則會生效),這意味着我在嘗試創建綁定時沒有設置所有AP(因爲在PropertyChangedCallback I不知道其他人是否會被設置)。

結果是,綁定可以創建多次,有時不成功,這導致綁定錯誤,「不美觀」,因爲想要一個更好的單詞。

有沒有一種方法可以抑制綁定錯誤,直到一個元素的所有AP都被分配爲止,或者從PropertyChangedCallback內部工作到是否會在該元素上設置更多包含類的AP?

編輯

我一直在問代碼。我希望能做到這一點沒有,但這裏是我問的是類(因爲它提出的問題相當長!):

public static class BindingIndirector 
{ 
    public static string GetBindingSource(DependencyObject dob) 
    { 
     return (string)dob.GetValue(BindingSourceProperty); 
    } 

    public static void SetBindingSource(DependencyObject dob, string value) 
    { 
     dob.SetValue(BindingSourceProperty, value); 
    } 

    /// <summary> 
    /// The "source" to be set on the binding. 
    /// Must be specified. 
    /// </summary> 
    public static readonly DependencyProperty BindingSourceProperty = 
     DependencyProperty.RegisterAttached(
      "BindingSource", 
      typeof(String), 
      typeof(BindingIndirector), 
      new PropertyMetadata(null, BindingChanged)); 


    public static object GetBindingSourceContext(DependencyObject dob) 
    { 
     return dob.GetValue(BindingSourceContextProperty); 
    } 

    public static void SetBindingSourceContext(DependencyObject dob, object value) 
    { 
     dob.SetValue(BindingSourceContextProperty, value); 
    } 

    /// <summary> 
    /// A DataContext type property. This overrides the inherited DataContext that would otherwise be 
    /// used for the binding. 
    /// Optional. 
    /// </summary> 
    public static readonly DependencyProperty BindingSourceContextProperty = 
     DependencyProperty.RegisterAttached(
      "BindingSourceContext", 
      typeof(object), 
      typeof(BindingIndirector), 
      new PropertyMetadata(null, BindingChanged)); 


    public static string GetBindingTarget(DependencyObject dob) 
    { 
     return (string)dob.GetValue(BindingTargetProperty); 
    } 

    public static void SetBindingTarget(DependencyObject dob, string value) 
    { 
     dob.SetValue(BindingTargetProperty, value); 
    } 

    /// <summary> 
    /// The binding target property. 
    /// Optional (defaults to "Content" if not specified 
    /// </summary> 
    public static readonly DependencyProperty BindingTargetProperty = 
     DependencyProperty.RegisterAttached(
      "BindingTarget", 
      typeof(String), 
      typeof(BindingIndirector), 
      new PropertyMetadata("Content", BindingChanged)); 

    private static void BindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     if (!(e.Property == BindingSourceContextProperty || e.NewValue is string)) 
      throw new ArgumentException("Property can only be set to string values", e.Property.ToString()); 

     // Check rules for attempting to set the binding are met 
     string source = GetBindingSource(d) as string; 
     string target = GetBindingTarget(d) as string; 
     object context = GetBindingSourceContext(d); 
     if (source == null)  // Source needs to be set - don't interfere with binding if it isn't 
      return; 

     // Clear any existing binding 
     var originalName = e.Property == 
      BindingSourceProperty ? 
       target : 
       e.OldValue as string; 

     if (originalName != null) 
     { 
      var existingDescriptor = 
       DependencyPropertyDescriptor.FromName(
        originalName, 
        d.GetType(), 
        d.GetType()); 

      if (existingDescriptor != null) 
       d.ClearValue(existingDescriptor.DependencyProperty); 
     } 

     // Create and assign new binding 
     var targetDescriptor = 
       DependencyPropertyDescriptor.FromName(
        target, 
        d.GetType(), 
        d.GetType()); 

     if (targetDescriptor != null) // don't interfere with binding if target invalid 
     { 
      Binding newBinding = new Binding(source) { Mode = BindingMode.TwoWay }; 
      if (context != null)  // Will fall back to DataContext of element in this case 
       newBinding.Source = context; 

      BindingOperations.SetBinding(d, targetDescriptor.DependencyProperty, newBinding); 
     } 
    } 
} 

這個靜態類創建3個附加屬性,也包含一個方法,「 BindingChanged()「,這是所有三個AP的propertyChangedCallback。如果已經提供了足夠的信息來嘗試創建綁定,則會丟棄之前綁定的AP用於首先創建的綁定。

它沒有做什麼(這可能是一個解決方案)是找出綁定是先成功還是捕獲綁定引擎產生的任何錯誤(你可以這樣做嗎?)。在不抑制應該顯示的綁定錯誤方面可能存在挑戰(例如,因爲最終用戶已經提供了duff信息)。

這裏是一個用例的一個示例:

<UserControl x:Class="UtilityControls.ListEditor" 
      ...> 

    <Grid x:Name="ControlContainer"> 
     <Grid.DataContext> 
      <local:LeViewModel x:Name="vm" /> 
     </Grid.DataContext> 

     <ListBox 
     x:Name="EditingArea" 
     ItemsSource="{Binding ColumnCollection, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:ListEditor}}}" 
     > 
      <ListBox.Resources> 

       <DataTemplate x:Key="TextTemplate"> 
        <StackPanel> 
         <TextBlock Text="{Binding DisplayName}" /> 
         <TextBox 
          local:BindingIndirector.BindingSourceContext="{Binding DataContext.CurrentEditing, ElementName=ControlContainer}" 
          local:BindingIndirector.BindingSource="{Binding PathName}" 
          local:BindingIndirector.BindingTarget="Text" 
          /> 
        </StackPanel> 
       </DataTemplate> 

       <DataTemplate x:Key="PickListTemplate" .. /> 
       <DataTemplate x:Key="BooleanTemplate" ... /> 

      </ListBox.Resources> 

      <ListBox.ItemTemplateSelector> 
       <local:DataTypeSelector 
        TextTemplate="{StaticResource TextTemplate}" 
        PickListTemplate="{StaticResource PickListTemplate}" 
        BooleanTemplate="{StaticResource BooleanTemplate}" 
        /> 
      </ListBox.ItemTemplateSelector> 

     </ListBox> 
    </Grid> 
</UserControl> 

「CurrentEditing」是視圖模型對象的各種ListBox項編輯(從ColumnCollection每個ListBox項產生針對該對象的一個​​不同的屬性的編輯器)。

希望AP的用途(這裏用在「TextTemplate」中)是不言自明的(他們創建了TextBoxText屬性的綁定),但請注意,儘管這三個都是必要的,但我希望在至少BindingSourceContext是可選的......並且這會產生問題:BindingChanged()不知道將設置多少接入點,因此它不知道何時創建綁定。因此,如果有足夠的信息來改變財產,那麼每次財產變更時都會有所變化。如果還有更多,則會產生綁定錯誤。

+0

你是什麼意思「難看」,這些綁定錯誤只是在VSs的輸出窗口,他們打擾你?除此之外,你可以展示一些你想要做什麼的代碼,以及錯誤是什麼? –

+0

是的,我的意思是VS輸出窗口。在我的經驗中,錯誤通常意味着一個綁定被錯誤地指定了,我真的不想讓我建立的'UserControl'生成它們(當前正在執行的)並以誤報污染輸出。錯誤是'System.Windows.Data Error:40:BindingExpression path error'並且在這種情況下發生,因爲Binding引擎試圖在繼承的'DataContext'上找到路徑(錯誤地 - 它不在那裏,另一個AP設置'Binding.Source'來解決這個問題)。 –

+0

好的,你能告訴我們一些代碼嗎? –

回答

2

您可以在Binding上使用FallbackValue來抑制這些異常。例如:

<Grid Visibility="{Binding SomeProperty, FallbackValue=Collapsed}"/> 
+0

感謝蒂姆,但'綁定'是在代碼中創建的,並且系統被推廣爲創建任何元素上的任何DP的綁定。代碼不知道合適的後備值(或甚至它的類型)將會避免錯誤,我甚至不認爲在每個情況下在使用點指定一個有效的值是可能的。 –