Я создал некоторые прикрепленные свойства, чтобы разрешить косвенную привязку (под которой я подразумеваю привязку к значению, имя которого задано вложенным свойством, а не указано как литерал в 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()
не знает, сколько из точек доступа будет установлено, поэтому он не знает, когда создавать привязку. В результате у него есть пропуск каждый раз, когда свойство изменяется, если у него достаточно информации для этого. Если еще впереди, возникают ошибки привязки.
Что вы подразумеваете под «неприглядными», являются ли эти ошибки привязки только в окне выходных данных VS и они вас беспокоят? Помимо этого, можете ли вы показать какой-то код того, что вы пытаетесь сделать и какие именно ошибки? –
Да, я имею в виду окно вывода VS.По моему опыту, ошибки обычно означают, что привязка была указана некорректно, и я действительно не хочу, чтобы «UserControl», который я создаю, генерировал их как само собой разумеющееся (что в настоящее время делает) и загрязняет вывод ложными срабатываниями. Ошибки «Ошибка System.Windows.Data: 40: Ошибка пути BindingExpression» и происходят в этом случае, потому что механизм Binding пытается найти путь к унаследованному «DataContext» (неправильно - его нет и еще одна точка доступа устанавливает 'Binding.Source', чтобы исправить это). –
хорошо, так что вы можете показать нам какой-то код? –