2016-07-08 23 views
0

Ich habe einige angefügte Eigenschaften erstellt, um Indirektion einer Bindung zu ermöglichen (mit dem ich binde auf einen Wert, dessen Name von der angefügten Eigenschaft angegeben wird, anstatt als Literal in XAML angegeben).Binding Errors auf teilweise konstruierte Objekte

Einige der APs sind optional (zum Beispiel eines, die die DataContext überschreibt, die sonst in Kraft sein würden), und dies bedeutet, dass ich die Bindung zu schaffen versucht bin, wenn nicht alle APs eingestellt worden ist (weil in den PropertyChangedCallback I weiß nicht, ob die anderen eingestellt werden).

Das Ergebnis ist, dass die Bindung mehrmals, manchmal erfolglos erstellt werden kann, und dies führt zu Bindungsfehlern, die "unansehnlich" sind, für ein besseres Wort.

Gibt es eine Möglichkeit, Bindungsfehler zu unterdrücken, bis alle APs eines Elements zugewiesen wurden, oder innerhalb von PropertyChangedCallback herauszufinden, ob weitere APs der enthaltenen Klasse für dieses Element gesetzt werden?

bearbeiten

Ich habe Code gefragt. Ich habe gehofft, dies zu tun, ohne, aber hier ist die Klasse, über die ich frage, (weil es die Frage ziemlich lang gemacht hat!):

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); 
     } 
    } 
} 

Diese statische Klasse erstellt 3 hinzugefügte Eigenschaften und auch eine einzelne Methode enthält " BindingChanged() "das ist die propertyChangedCallback für alle drei APs. Wenn genügend Informationen gegeben wurden, um eine Bindung zu erstellen, wird dies getan, indem jede vorherige Bindung verworfen wird, die die APs zuerst verwendet haben.

Was es nicht macht (was eine Lösung sein könnte) ist, herauszufinden, ob die Bindung zuerst erfolgreich sein würde oder irgendwelche Fehler, die von der Bindungs-Engine erzeugt werden (können Sie das tun?). Es könnte eine Herausforderung sein, Binding-Fehler nicht zu unterdrücken, die angezeigt werden sollten (weil der Endbenutzer beispielsweise duff-Informationen angegeben hat). Hier

ist ein Beispiel für einen Anwendungsfall:

<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“ ist die Ansichtsmodell-Objekt der verschiedenen ListBox Artikel bearbeiten (jeweils ListBox Element aus ColumnCollection einen Editor für eine andere Eigenschaft des Objekts erzeugt).

Hoffentlich wird der Zweck der APs (hier in „TextTemplate“ verwendet) ist selbsterklärend (sie schaffen für die Text Eigenschaft des TextBox eine Bindung), aber beachten Sie, dass, obwohl alle drei hier notwendig sind, möchte ich auf die mindestens BindingSourceContext optional zu sein ... und das schafft das Problem: BindingChanged() weiß nicht, wie viele der APs eingestellt werden, so dass es nicht weiß, wann die Bindung zu erstellen ist. Daher muss jedes Mal, wenn eine Eigenschaft geändert wird, eine entsprechende Änderung vorgenommen werden. Wenn es noch mehr gibt, werden Binding-Fehler erzeugt.

+0

was meinst du mit "unansehnlich", sind diese verbindlichen Fehler nur in VSs Ausgabefenster und sie stören Sie? Kannst du etwas Code darüber zeigen, was du gerade machst und was genau die Fehler sind? –

+0

Ja, ich meine das VS-Ausgabefenster.Meiner Erfahrung nach haben Fehler normalerweise zur Folge, dass eine Bindung falsch spezifiziert wurde und ich die "UserControl", die ich erschaffe, lieber nicht selbst erzeugen würde (was sie gerade macht) und die Ausgabe mit falschen Positives verunreinigt. Die Fehler sind "System.Windows.Data Error: 40: BindingExpression path error" und treten in diesem Fall auf, weil die Binding-Engine versucht, einen Pfad auf dem geerbten 'DataContext' zu finden (falsch - es ist nicht da und ein anderer AP legt 'Binding.Source' fest, um dies zu korrigieren. –

+0

ok, kannst du uns also Code zeigen? –

Antwort

2

Sie können FallbackValue auf der Bindung verwenden, um diese Ausnahmen zu unterdrücken. Zum Beispiel:

+0

Danke Tim, aber die 'Binding's werden im Code erstellt und das System wird verallgemeinert, um eine Bindung für jede DP auf einem beliebigen Element zu erstellen. Der Code hat keine Ahnung, was ein geeigneter Rückfallwert (oder auch sein Typ) wäre, um einen Fehler zu vermeiden, und ich glaube nicht einmal, dass es am Einsatzort möglich wäre, in jedem Fall einen gültigen Wert anzugeben. –