Я только что успешно реализовал этот пример ( Создание гибких пользовательских интерфейсов с потоковыми документами и привязкой данных ) и столкнулся с той же проблемой, что и вы.
Проблема заключалась в том, что DataContext
для BindableRun
установлен неправильно. Как объяснялось в статье, вам нужно исправить контекст (используя вспомогательный метод FixupDataContext
), установить DataContext
для FrameworkContentElement
и очистить контекст, который вы ранее «исправили» (используя UnFixupDataContext
вспомогательный метод). Порядок выполнения этих операторов является критическим . Перечитайте статью еще раз и убедитесь, что вы понимаете ее; Мне пришлось несколько раз перечитать его и изучить код, чтобы действительно понять, о чем он говорит.
Я сделал шаг вперед в реализации и добавил поддержку связывания данных с другими элементами в TableCell
. Изменения включают использование присоединенного свойства для идентификации элементов, у которых DataContext
«исправлено», а затем расширение вспомогательных методов для работы также с FrameworkElements
в дополнение к FrameworkContentElements
.
HTH,
Редактировать:
public static class DataContextHelper
{
#region UseAncestorDataContext
public static readonly DependencyProperty UseAncestorDataContextProperty = DependencyProperty.RegisterAttached("UseAncestorDataContext", typeof(bool), typeof(DataContextHelper),
new FrameworkPropertyMetadata(false, DataContextHelper.OnUseAncestorDataContextChanged));
public static bool GetUseAncestorDataContext(DependencyObject d)
{
return (bool)d.GetValue(UseAncestorDataContextProperty);
}
public static void SetUseAncestorDataContext(DependencyObject d, bool value)
{
d.SetValue(UseAncestorDataContextProperty, value);
}
private static void OnUseAncestorDataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
UseAncestorDataContext(d);
}
#endregion
/// <summary>
/// If you use a Bindable flow document element more than once, you may encounter a "Collection was modified"
/// exception. The error occurs when the binding is updated because of a change to an inherited dependency
/// property. The most common scenario is when the inherited DataContext changes. It appears that an inherited
/// properly like DataContext is propagated to its descendants. When the enumeration of descendants gets to
/// a Bindable, the dependency properties of that element change according to the new DataContext, which change
/// the (non-dependency) properties. However, for some reason, changing the flow content invalidates the enumeration
/// and raises an exception.
/// </summary>
public static void UseAncestorDataContext(DependencyObject element)
{
if (element is FrameworkContentElement)
{
FrameworkContentElement contentElement = (FrameworkContentElement)element;
Binding binding = new Binding(FrameworkContentElement.DataContextProperty.Name);
binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(FrameworkElement), 1);
contentElement.SetBinding(FrameworkContentElement.DataContextProperty, binding);
}
else if (element is FrameworkElement)
{
FrameworkElement frameworkElement = (FrameworkElement)element;
Binding binding = new Binding(FrameworkElement.DataContextProperty.Name);
binding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(FrameworkElement), 1);
frameworkElement.SetBinding(FrameworkContentElement.DataContextProperty, binding);
}
}
public static void ClearDataContextBinding(DependencyObject d)
{
while (RestoreDataContextRecursive(d));
}
private static bool RestoreDataContextRecursive(DependencyObject d)
{
if (d is FrameworkContentElement && GetUseAncestorDataContext(d))
{
Binding binding = BindingOperations.GetBinding(d, FrameworkContentElement.DataContextProperty);
if (binding != null && binding.Path != null && binding.Path.Path == FrameworkContentElement.DataContextProperty.Name
&& binding.RelativeSource != null && binding.RelativeSource.Mode == RelativeSourceMode.FindAncestor && binding.RelativeSource.AncestorType == typeof(FrameworkElement) && binding.RelativeSource.AncestorLevel == 1)
{
BindingOperations.ClearBinding(d, FrameworkContentElement.DataContextProperty);
return true;
}
}
else if (d is FrameworkElement && GetUseAncestorDataContext(d))
{
Binding binding = BindingOperations.GetBinding(d, FrameworkElement.DataContextProperty);
if (binding != null && binding.Path != null && binding.Path.Path == FrameworkElement.DataContextProperty.Name
&& binding.RelativeSource != null && binding.RelativeSource.Mode == RelativeSourceMode.FindAncestor && binding.RelativeSource.AncestorType == typeof(FrameworkElement) && binding.RelativeSource.AncestorLevel == 1)
{
BindingOperations.ClearBinding(d, FrameworkElement.DataContextProperty);
return true;
}
}
// As soon as we have disconnected a binding, return. Don't continue the enumeration, since the collection may have changed
foreach (object child in LogicalTreeHelper.GetChildren(d))
{
if (child is DependencyObject && RestoreDataContextRecursive((DependencyObject)child))
return true;
}
return false;
}
}
Использование
<DataTemplate x:Key="PercentCellTemplate">
<documents:ContentFragment>
<TableCell Style="{StaticResource TableCellStyle}" BorderThickness="0,0,1,0">
<Paragraph>
<Rectangle Width="14" Height="14" VerticalAlignment="Center" Margin="0,6,0,0" Fill="{Binding Path=Result, Mode=OneWay, Converter={StaticResource MappingConverterResultEnumToIconResource}}"
documents:DataContextHelper.UseAncestorDataContext="True"/>
<documents:DocumentRun Style="{StaticResource ReportDocument_NormalRunStyle}" Text="{Binding Path=Percent, Mode=OneWay, StringFormat={}{0:0.000}%}"
BaselineAlignment="Center" documents:DataContextHelper.UseAncestorDataContext="True" />
</Paragraph>
</TableCell>
</documents:ContentFragment>
</DataTemplate>