2016-07-08 2 views
0

Я создал некоторые прикрепленные свойства, чтобы разрешить косвенную привязку (под которой я подразумеваю привязку к значению, имя которого задано вложенным свойством, а не указано как литерал в XAML).Ошибки связывания на частично построенных объектах

Некоторые из точек доступа не являются обязательными (например, тот, который отменяет DataContext, которые могли бы быть в силе), а это означает, что я пытаюсь создать привязку, когда не все точки доступа были установлены (потому что в PropertyChangedCallback I не знаю, будут ли установлены другие).

Результат состоит в том, что привязка может быть создана несколько раз, иногда безуспешно, и это приводит к ошибкам привязки, которые являются «неприглядными», из-за отсутствия лучшего слова.

Есть ли способ подавления ошибок привязки до тех пор, пока не будут назначены все точки доступа элемента или не будут выполнены в пределах 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() ", который является 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» является ViewModel объекта различных ListBox элементов редактирования (каждый ListBox элемента из ColumnCollection генерирует редактор для другого свойства объекта).

Будет надеяться, что цель Л (здесь используется в «TextTemplate») говорит сама за себя (они создают привязки для Text имущества TextBox), но обратите внимание, что, хотя все три необходимо здесь, я хочу на по меньшей мере BindingSourceContext является необязательным ... и это создает проблему: BindingChanged() не знает, сколько из точек доступа будет установлено, поэтому он не знает, когда создавать привязку. В результате у него есть пропуск каждый раз, когда свойство изменяется, если у него достаточно информации для этого. Если еще впереди, возникают ошибки привязки.

+0

Что вы подразумеваете под «неприглядными», являются ли эти ошибки привязки только в окне выходных данных VS и они вас беспокоят? Помимо этого, можете ли вы показать какой-то код того, что вы пытаетесь сделать и какие именно ошибки? –

+0

Да, я имею в виду окно вывода VS.По моему опыту, ошибки обычно означают, что привязка была указана некорректно, и я действительно не хочу, чтобы «UserControl», который я создаю, генерировал их как само собой разумеющееся (что в настоящее время делает) и загрязняет вывод ложными срабатываниями. Ошибки «Ошибка System.Windows.Data: 40: Ошибка пути BindingExpression» и происходят в этом случае, потому что механизм Binding пытается найти путь к унаследованному «DataContext» (неправильно - его нет и еще одна точка доступа устанавливает 'Binding.Source', чтобы исправить это). –

+0

хорошо, так что вы можете показать нам какой-то код? –

ответ

2

Вы можете использовать FallbackValue для привязки для исключения этих исключений. Например:

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

Спасибо Tim, но 'Binding' создаются в коде, и система обобщается для создания привязки для любого DP на любом элементе. Код не имеет представления о том, какое подходящее резервное значение (или даже его тип) должно было бы избежать ошибки, и я даже не думаю, что в момент использования можно было бы указать допустимое значение в каждом случае. –

Смежные вопросы