动态的 WPF

用流文档和数据绑定创建灵活的 UI

Vincent Van Den Berghe

代码下载可从 MSDN 代码库
浏览代码联机

本文讨论:

  • 流文档
  • 数据绑定问题
  • 生成数据绑定控件
  • 使用模板
本文涉及以下技术:
WPF,.NET 3.5 SP 1

内容

目标
可绑定的运行
FrameworkContentElements 模板
ItemsContent 实现
结论

流文档是 理想选择用于显示只读数据在 Windows Presentation Foundation (WPF)。它们提供相当大的灵活地排列文本版式和分页。此外,流文档控件提供注释,打印支持和剪贴板功能几乎可用。执行此操作与传统的 WPF 控件 (TextBlock、 ItemsControl 和派生产品) 会更加很困难。

虽然有许多强大功能流文档中如果您的文档生成动态数据,您有一些问题: 存在不是支持流文档中的数据绑定。流文档元素 (节、 表、 运行、 段落和类似) 是依赖关系的对象,但不定义将允许您动态地更改或生成内容的任何依赖关系属性。

必须首先,生成流文档,因为流文档不支持直接绑定的数据的组件执行流文档的部分的任何更新。在这一方面流文档是比其他 WPF Framework 元素灵活性较差的。

添加缺少依赖关系属性的外观像一个简单的解决方案但遗憾的是,这不是这样。因为通常情况在 devil 位于此的详细信息,并且将必须获得相当多的详细信息权限,使其正常。

若要使此问题的更具体,请考虑以下简单 XML 文档示例 XmlDataProvider 中包含:

<XmlDataProvider x:Key="DataSource">
    <x:XData>
        <NorthwindNames >
            <Person FirstName="Nancy" LastName="Davolio" />
            <Person FirstName="Andrew" LastName="Fuller" />
            <Person FirstName="Janet" LastName="Leverling" />
            <Person FirstName="Margaret" LastName="Peacock" />
            <Person FirstName="Steven" LastName="Buchanan" />
        </NorthwindNames>       
    </x:XData>
</XmlDataProvider>

显示此数据使用正常的数据绑定方法和 trusty ListView 控件是一段的蛋糕。 下面的代码:

<ListView ItemsSource="{Binding Source={StaticResource DataSource},  XPath=NorthwindNames/Person}">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="First name"
              DisplayMemberBinding="{Binding XPath=@FirstName}" />
            <GridViewColumn Header="Last name" 
              DisplayMemberBinding="{Binding XPath=@LastName}" />
        </GridView>
    </ListView.View>
</ListView>

为预期结果是,正如您所看到中 图 1 .

fig01.gif

图 1 在 ListView 中的数据

现在,我想显示为一个流文档中的一个表。 理想情况下,我希望组件与 ListView 的理解绑定到某个项目的源,但它可以生成动态地在一个 FlowDocument 表内容。 换句话说,我需要一种类型的 ItemsControl,但流文档。 和与 InlineUIContainer 或 BlockUIContainer 没有 cheating: 尽管这使您可以嵌入任何 WPF 控件,如 ListView,您将丢失它们的内容复制到剪贴板时。 嵌入这种方式的控件不会 magically 将它们转换成流文档元素。

这样的组件不存在因此我需要自己编写。 我将调用它 ItemsContent,以反映事实它生成的数据项目的流内容。 若要将有关我需要设计好让我们考虑一下该组件的该标记应类似。 使功能 ItemsControl 提供,操作例如 图 2 会很好地适应物料清单。

图 2 ItemsContent 控件

<ItemsContent ItemsSource="{Binding Source={StaticResource DataSource},  XPath=NorthwindNames/Person}" >
    <ItemsContent.ItemsPanel>
        <DataTemplate>
            <Table BorderThickness="1" BorderBrush="Black">
                <TableRowGroup IsItemsHost="True">
                    <TableRow Background="LightBlue">
                        <TableCell>
                            <Paragraph>First name</Paragraph>
                        </TableCell>
                        <TableCell>
                            <Paragraph>Last name</Paragraph>
                        </TableCell>
                    </TableRow>
                </TableRowGroup>
            </Table>
        </DataTemplate>
    </ItemsContent.ItemsPanel>
    <ItemsContent.ItemTemplate>
        <DataTemplate>
            <TableRow>
                <TableCell>
                    <Paragraph>
                        <Run Text="{Binding XPath=@FirstName}"/>
                    </Paragraph>
                </TableCell>
                <TableCell>
                    <Paragraph>
                        <Run Text="{Binding XPath=@LastName}"/>
                    </Paragraph>
                </TableCell>
            </TableRow>
        </DataTemplate>
    </ItemsContent.ItemTemplate>
</ ItemsContent>

正如您所看到标记严格遵循现有 ItemsControl 的模式。 还有一个 ItemsPanel 模板,它确定如何将嵌入生成的流文档内容。

在 ItemsPanel 告诉该控件中第一行使用浅蓝色背景生成一个两列的表格的固定的标题信息。 额外的行将追加到包含 IsItemsHost 的 TableRowGroup ="true"(这正好是我们头正常的同一个表行组)。 这与承载的一个 ItemsControl ItemsPanel 模板中项目的一个面板的标记方式类似。

ItemTemplate 将确定要为我们 ItemsSource 中的每个项目生成内容。

这些行将附加到在上面所述我们 ItemsPanel TableRowGroup。

执行对 XML 文档的此标记将生成 FlowDocument 片段所示 图 3 .

图 3 FlowDocument 摘要

<Table BorderThickness="1" BorderBrush="Black">
    <TableRowGroup>
        <TableRow Background="LightBlue">
            <TableCell><Paragraph>First name</Paragraph></TableCell>
            <TableCell><Paragraph>Last name</Paragraph></TableCell>
        </TableRow>
        <TableRow>
            <TableCell><Paragraph>Nancy</Paragraph></TableCell>
            <TableCell><Paragraph>Davolio</Paragraph></TableCell>
        </TableRow>
        <TableRow>
            <TableCell><Paragraph>Andrew</Paragraph></TableCell>
            <TableCell><Paragraph>Fuller</Paragraph></TableCell>
        </TableRow>
        <TableRow>
            <TableCell><Paragraph>Janet</Paragraph></TableCell>
            <TableCell><Paragraph>Leverling</Paragraph></TableCell>
        </TableRow>
        <TableRow>
            <TableCell><Paragraph>Margaret</Paragraph></TableCell>
            <TableCell><Paragraph>Peacock</Paragraph></TableCell>
        </TableRow>
        <TableRow>
            <TableCell><Paragraph>Steven</Paragraph></TableCell>
            <TableCell><Paragraph>Buchanan</Paragraph></TableCell>
        </TableRow>
    </TableRowGroup>
</Table>

该代码,反过来,呈现表中 图 4 .

fig04.gif

图 4 流文档中的数据

图 5 BindableRun

    public class BindableRun : Run
    {
        public static readonly DependencyProperty BoundTextProperty = 
          DependencyProperty.Register("BoundText", typeof(string), typeof(BindableRun), 
          new PropertyMetadata(OnBoundTextChanged));

        public BindableRun()
        {
            Helpers.FixupDataContext(this);
        }

        private static void OnBoundTextChanged(DependencyObject d, 
            DependencyPropertyChangedEventArgs e)
        {
            ((Run)d).Text = (string)e.NewValue;
        }

        public String BoundText
        {
            get { return (string)GetValue(BoundTextProperty); }
            set { SetValue(BoundTextProperty, value); }
        }
    }

岂不是很好如果我们有这样一件事情? 正如您所看到此组件是非常灵活。 除了数据绑定,它还允许使用的流文档中的模板。 它不解决每个流文档的数据绑定问题存在,但如果我们可以请求此关闭,看几乎普通在比较中所有其他数据绑定问题。

本文的其余,我将显示如何构建这样 beast,或至少内容的类似其密切。 此外,我希望提供足够的背景信息,您可以处理任何其他流文档的数据绑定问题。 阅读。

可绑定的运行

如果在搜索有关流文档和数据绑定示例网页,您绑是定 (因此为说) 到通过 BindableRun (fortes.com/2007/03/bindablerun) 遇到。 它提供在许多类型,和其最简单的窗体外观,如 图 5 中。

实现非常简单: 我们从运行继承,定义依赖关系属性 BoundText,并与其值同步运行的 Text 属性。 运行一个依赖项对象,但其文本内容不依赖属性。 BoundText 是文本的只是文本的依赖关系属性版本使我们可以使用数据绑定表达式。 很简单。

因此,其用法是简单的。 而不是 < … 运行 / >,使用以下代码其中 flowdoc 是 XML 命名空间您定义在的位置:

<flowdoc:BindableRun BoundText="{Binding …}" />

遗憾的是,此类将有时会失败。 具体来说,异常"已修改集合 ; 枚举操作可能不执行"如果将会引发 BoundText 的绑定表达式取决于如 DataContext 的继承依赖关系属性的值和绑定评估执行期间继承传播。 (请参阅 有关数据流在 MSDN 论坛线程 有关该错误的说明)。

我们要开发的组件的根据值得要充分理解,导致这一例外的机制,因为会再次遇到。 为此,需要深入查看 WPF 若要查看属性值继承的对象的类型派生 FrameworkContentElement 时发生的情况。 我们可以限制了对从 FrameworkContentElement,派生,因为所有流文档元素都派生的类。 (有关在论述 FrameworkContentElement 和 FrameworkElement 请参见 wpfdisciples.wordpress.com/2008/10/09。

当像 DataContext 设置流文档元素的某些逻辑上级的可继承的依赖关系属性的值,下列操作发生:

  • DependencyObject.SetValue 调用以在上级对象上设置依赖关系属性的值。
  • DependencyObject 将调用其内部的 NotifyPropertyChange 方法。
  • NotifyPropertyChange 调用虚拟方法 FrameworkContent 元素被覆盖的 OnNotifyPropertyChange。
  • OnNotifyPropertyChange FrameworkContentElement 的实现实现的可继承的属性更改,,它需要通知的事实的所有其 (逻辑) 后代。

对于这一目的内部 TreeWalkHelper.InvalidateOnInheritable 方法调用的递归枚举 (使用内部类 DescendantsWalker) 的所有后代并访问每一项为每个调用 TreeWalkHelper.OnInheritablePropertyChanged。

疑问。 只要您使用的标准流文档元素,上述步骤将始终会成功。 但如果在继承链中的后代之一是在 BindableRun 早先定义并您 BoundText 取决继承的 DataContext 值然后值传播期间下发生:

  • 由于要在重新计算其关联的绑定表达式的更改依赖关系属性 BoundText 的值。
  • BindableRun.OnBoundTextChanged 触发,并将文本属性设置。
  • 文本的 setter 调用运行 TextContainer BeginChange,并 TextContainer 增加其生成号 (实质上是一个内部版本编号用于跟踪到容器的更改)。
  • 当前活动枚举 TreeWalkHelper 使用 (RangeContentEnumerator) 检测 MoveNext() 其下一个调用时的生成编号的更改,并将失败并导致异常"已修改集合 ; 枚举操作可能不执行"。

因为在上述过程 baked 到 WPF 和重要的类型而实现的方法是内部,您不能解决此问题中。 您可以但是,绕过问题的"断开"值传播。 执行此操作的一种是绑定到 FrameworkElement 上级的相应属性的 BindableRun 的 DataContext。 这是这样很有用我已将其置于自己的帮助器类:

public static void FixupDataContext(FrameworkContentElement element)
{
    Binding b = new Binding(
      FrameworkContentElement.DataContextProperty.Name);
    b.RelativeSource = new RelativeSource(
      RelativeSourceMode.FindAncestor,
      typeof(FrameworkElement), 1);
element.SetBinding(FrameworkContentElement.DataContextProperty, b);
}

可能,此替代方法需要应用的我们派生的所有流文档元素。

有效,只要一个 FrameworkElement 始终位于最在逻辑父链和依赖属性 (BoundText BindableRun 的情况下) 并不依赖于系统传播任何其他继承属性。

根据设计,第一个点将始终为 True (阅读下一节将使您了解原因)。 但是,您无法始终确保第二个的 ; 显然,某些数据绑定方案将导致此替代方法失败,并您必须重新访问其更高版本。

FrameworkContentElements 模板

要实现该控件,需要模板的灵活性。 理想情况下,您希望能够在数据模板中定义的流文档的片段。 与已经在您释放 BindableRun,您希望 图 6 中编写代码。 此处 flowdoc 是所选的命名空间定义新的类的。

图 6 flowdoc 中的新类

<DataTemplate>
    <TableRow>
        <TableCell>
            <Paragraph>
                <flowdoc:BindableRun BoundText="{Binding XPath=@FirstName}" />
            </Paragraph>
        </TableCell>
        <TableCell>
            <Paragraph>
                <flowdoc:BindableRun BoundText="{Binding XPath=@LastName}"/>
            </Paragraph>
        </TableCell>
    </TableRow>
</DataTemplate>

请尝试在您最喜欢的设计器 (使用 DataTemplate 或 ControlTemplate,无关紧要),您会收到以下错误:

"Property 'VisualTree' does not support values of type 'TableRow'."

VisualTree 为 FrameworkTemplate、 常见的基类型,DataTemplate 和 ControlTemplate 的该默认内容属性。 FrameworkTemplate 文档是稀疏,但其说明明确指出它使 FrameworkElement 和/或 FrameworkContentElement 对象的树的实例化。

fig07.gif

图 7 FrameworkElement 和 FrameworkContentElement

此说明是具有误导性或不完整最所示。 事实证明了 FrameworkTemplate 可以实例化的根节点是一个 FrameworkElement 一个树。 如果您需要实例化一个树的根是一个 FrameworkContentElement,您未能提供有用。 与该错误消息表示,VisualTree 属性键来了解这: 它指向一个 FrameworkElementFactory 可以只生成 FrameworkElements,至少为根节点的。

这,顺便,就是一个 FrameworkElement 始终位于最逻辑父链如前一节所述的原因。

您可能想知道为什么这样限制存在。 实际上,这不需要能概念限制,; 一个可以轻松地查看能够生成两个类的实例的模板的用途。 此外,公钥和受保护成员的两个类是非常相似,并且它们实现许多相同的接口 (IFrameworkInputElement、 IInputElement、 ISupportInitialize)。

阻止分解出其通用性的主要区别之一是它们在继承层次结构中的不同点发生如所示 图 7 .

此查找类似作业混合的中的类,但这需要不能在.NET 中的多重继承。 可能还有老话您不需要通常,多重继承,但如果这样做,需要错误中的事实。

仍然,一个 wonders 是否设计无法已更好。 FrameworkElementFactory 实现的进一步检查显示硬编码的类似网格,GridViewRowPresenter、 RowDefinition、 ColumnDefinition 和类型 Visual3D 知识。 一个期待构造类型的特定实例的知识,从其常见的工厂的实现抽象。 否则,您最终使用的此外,将强制您以重新对其进行访问,每次向系统添加另一种特殊类型,并应以某种方式了解哪些类型是特殊非常严格和 inextensible 工厂实现。 这 smells 像打开 / 关闭设计原则违反。

由于我们具有足够的信息,以确定应已完成并且我们仍不能更改一件事情),我将此处将讨论并关注一个解决方案: 我们如何定义 FrameworkContentElements 的模板? 最简单解决方案需要最少量,是代码的构建两个类型之间的网桥。

WPF 中已存在这样的桥: 在 BlockUIContainer 是流文档中嵌入 UIElements 的流文档元素。 因此一个的桥但我们需要在相反的方向。

另一个现有的网桥是 TextBlock: 一个 FrameworkElement 可以包含它是一个特殊类型的 FrameworkContentElement 的内嵌元素的集合。 如果为内嵌元素限制自己,完美,但您不希望此限制。 您的目的,网桥是太小。

事实证明桥类很容易编写。 让我们调用该片段,因为它将保存的流文档 (请参见 图 8 ) 的片段。

图 8 片段类

[ContentProperty("Content")]
public class Fragment : FrameworkElement
{
    private static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", 
    typeof(FrameworkContentElement), typeof(Fragment));

    public FrameworkContentElement Content
    {
        get
        {
            return (FrameworkContentElement)GetValue(ContentProperty);
        }
        set
        {
            SetValue(ContentProperty, value);
        }
    }
}

正如您所看到,有是 Nothing 地球 shattering 此处: 很简单类 (当然),在从 FrameworkElement 派生具有依赖关系属性存放 FrameworkContentElement 根作为默认内容。

使用此类,您现在可以编写如下:

<DataTemplate>
    <flowdoc:Fragment>
        <TableRow>…</TableRow>
    </flowdoc:Fragment>
</DataTemplate>

将使用这些模板定义,您需要能够在加载它。 您需要一种专用化模板加载机制,并返回片段的内容的方法。 您可以自动执行这一次和的所有 图 9 中显示的另一个帮助器函数中。

图 9 Helper 函数

public static FrameworkContentElement LoadDataTemplate     (DataTemplate dataTemplate)
{
    object content = dataTemplate.LoadContent();
    if (content is Fragment)
        return (FrameworkContentElement)((Fragment)content).Content;
    else if (content is TextBlock)
    {
        InlineCollection inlines = ((TextBlock)content).Inlines;
        if (inlines.Count == 1)
            return inlines.FirstInline;
        else
        {
            Paragraph paragraph = new Paragraph();
            while (inlines.FirstInline != null)
                paragraph.Inlines.Add(inlines.FirstInline);
            return paragraph;
        }
    }
    else
        throw new Exception("Data template needs to contain a <Fragment> or <TextBlock>");
}

此 Helper 函数还处理现有的 TextBlock 桥。 如果 TextBlock 包含只有一个内联元素,则返回该元素。 否则,整个嵌入集合是很好地包装在一个段落。 此包装则需要因为一个 InlineCollection 本身不是一个 FrameworkContentElement。

请注意不能解决从一个文本容器的内联元素移动到另一个明显的方式:

foreach (var inline in inlines)
    paragraph.Inlines.Add(inline);

该代码将引发异常"在修改集合,枚举操作可能不执行"描述之前,和由于类似原因: 从一个文本容器 (TextBlock) 添加到另一个的元素 (段落) 隐式地删除它导致若要更改其生成号在第一个文本容器。 因为 TextElementEnumerator,像一个的 RangeContentEnumerator 还检查其 TextContainer 生成编号在同一将异常。

这种情况下,在以前的一个,区别是现在具有足够控件,以绕过该问题在源。 这是为什么您可以编写在下完成同样的操作,但不使用枚举数:

while (inlines.FirstInline != null)
    paragraph.Inlines.Add(inlines.FirstInline);

ItemsContent 实现

您几乎具有您的实现填数游戏的所有代码段,但是还有一个设计决策左进行。 您需要决定您的流内容元素是块级别元素还是内联级别元素。 决定任意,但需要使,因为任何控件不可以同时。

在这篇文章,ItemsContent 控件将是块级别元素。 嵌入版本为读取器保留作为练习。 我不派生块直接,但从部分的我将向其中添加我的动态内容的方便的块集合属性。 部分也不应用任何默认格式对它所包含的元素的只我自定义控件的需要。

注意有关的基本类您派生。 您可能会直接,派生块,但可以将到目前为止获得。 BlockCollection (的泛型 TextElementCollection < > 派生) 的重要帮助器类有内部构造函数,因此在自己的代码中不可用。

基本上,ItemsContent 的实现是简单: 只是一组依赖关系属性和通常 suspects 来跟踪他们的更改。 实际操作发生在 GenerateContent 方法即控件的部分工具应得进一步的检查 (请参见 图 10 )。 (注意,代码的其余部分可以找到在代码下载)。

图 10 </a0>-GenerateContent 方法

    private void GenerateContent(DataTemplate itemsPanel, DataTemplate   itemTemplate, IEnumerable itemsSource)
    {
        Blocks.Clear();
        if (itemTemplate != null && itemsSource != null)
        {
            FrameworkContentElement panel = null;

            foreach (object data in itemsSource)
            {
                if (panel == null)
                {
                    if (itemsPanel == null)
                        panel = this;
                    else
                    {
                        FrameworkContentElement p = Helpers.LoadDataTemplate(itemsPanel);
                        Blocks.Add((Block)p);
                        panel = Attached.GetItemsHost(p);
                        if (panel == null)
                            throw new Exception("ItemsHost not found. Did you forget to specify 
                                                Attached.IsItemsHost?");
                    }
                }
                FrameworkContentElement element = Helpers.LoadDataTemplate(itemTemplate);
                element.DataContext = data;
                Helpers.UnFixupDataContext(element);
                if (panel is Section)
                    ((Section)panel).Blocks.Add(Helpers.ConvertToBlock(data, element));
                else if (panel is TableRowGroup)
                    ((TableRowGroup)panel).Rows.Add((TableRow)element);
                else
                    throw new Exception(String.Format("Don't know how to add an instance of {0} 
                    to an instance of {1}", element.GetType(), panel.GetType()));
            }
        }
    }

    private void GenerateContent()
    {
        GenerateContent(ItemsPanel, ItemTemplate, ItemsSource);
    }

    private void OnItemsSourceChanged(IEnumerable newValue)
    {
        if (IsLoaded)
            GenerateContent(ItemsPanel, ItemTemplate, newValue);
    }

    private void OnItemTemplateChanged(DataTemplate newValue)
    {
        if (IsLoaded)
            GenerateContent(ItemsPanel, newValue, ItemsSource);
    }

    private void OnItemsPanelChanged(DataTemplate newValue)
    {
        if (IsLoaded)
            GenerateContent(newValue, ItemTemplate, ItemsSource);
    }

    private static void OnItemsSourceChanged(DependencyObject d, 
        DependencyPropertyChangedEventArgs e)
    {
        ((ItemsContent)d).OnItemsSourceChanged((IEnumerable)e.NewValue);
    }

    private static void OnItemTemplateChanged(DependencyObject d, 
        DependencyPropertyChangedEventArgs e)
    {
        ((ItemsContent)d).OnItemTemplateChanged((DataTemplate)e.NewValue);
    }

    private static void OnItemsPanelChanged(DependencyObject d, 
        DependencyPropertyChangedEventArgs e)
    {
        ((ItemsContent)d).OnItemsPanelChanged((DataTemplate)e.NewValue);
    }

    private void GenerateContent(DataTemplate itemsPanel, DataTemplate     itemTemplate, 
        IEnumerable itemsSource)
    {
        Blocks.Clear();
        if (itemTemplate != null && itemsSource != null)
        {
            FrameworkContentElement panel = null;

            foreach (object data in itemsSource)
            {
                if (panel == null)
                {
                    if (itemsPanel == null)
                        panel = this;
                    else
                    {
                        FrameworkContentElement p = Helpers.LoadDataTemplate(itemsPanel);
                        if (!(p is Block))
                            throw new Exception("ItemsPanel must be a block element");
                        Blocks.Add((Block)p);
                        panel = Attached.GetItemsHost(p);
                        if (panel == null)
                            throw new Exception("ItemsHost not found. Did you forget to specify 
                            Attached.IsItemsHost?");
                    }
                }
                FrameworkContentElement element = Helpers.LoadDataTemplate(itemTemplate);
                element.DataContext = data;
                Helpers.UnFixupDataContext(element);
                if (panel is Section)
                    ((Section)panel).Blocks.Add(Helpers.ConvertToBlock(data, element));
                else if (panel is TableRowGroup)
                    ((TableRowGroup)panel).Rows.Add((TableRow)element);
                else
                    throw new Exception(String.Format("Don't know how to add an instance of {0} to 
                    an instance of {1}", element.GetType(), panel.GetType()));
            }
        }
    }
}    }

生成或重新生成内容时第一个订单的业务是清除块集合。 简单的 blocks.clear() ; 有效地清除节控件的内容。

接下来,您需要确定要承载您的内容将用于面板。 如果没有指定一个 ItemsPanel 模板,ItemsContent 控件本身将主机:

if (itemsPanel == null)
    panel = this;

否则为 ItemsPanel 模板将加载并添加到在最终的内容,指定 IsItemsHost 的元素将能用作在面板来承载内容。 请注意因为控件是一个块元素,面板还假定为块元素。

FrameworkContentElement p = Helpers.LoadDataTemplate(itemsPanel);
if (!(p is Block))
   throw new Exception("ItemsPanel must be a block element");
Blocks.Add((Block)p);
panel = Attached.GetItemsHost(p);
if (panel == null)
   throw new Exception("ItemsHost not found. Did you forget to specify Attached.IsItemsHost?");

在 IsItemsHost 被定义为一个附加的属性,因为您要能够为项目主机指定任何 (禁止) 元素。 这已被巧妙封装附加的类我不在此处讨论的简单实现中。

所有这些执行只需一次性如果在 ItemsSource 中有至少一个项目。 对于项目源中的每个项,加载 ItemTemplate,,并数据上下文元素的设置到我们当前的项目:

FrameworkContentElement element = Helpers.LoadDataTemplate(itemTemplate);
element.DataContext = data;
Helpers.UnFixupDataContext(element);

在每次迭代重要的一点是将数据环境设置为"当前项目。 这将使熟悉每个项目的绑定方案。 工作分配是必需的但没有足够: 请记住为 BindableRun 类似的项目我们已更改后台 DataContext 绑定。 因此,这些项目将 DataContext 属性将永远不会具有预期的值。

要使正常,必须删除所有这些绑定。 这是 Helpers.UnFixupDataContext 函数的任务。 该帮助器函数遍历逻辑树删除设置的 Helpers.FixupDataContext 所有绑定。

请注意执行顺序是非常重要。 如果分配数据上下文之前调用了 Helpers.UnFixupDataContext,"已修改集合 ; 枚举操作可能不执行"异常会再次引发其丑陋头。 您需要先,设置在 DataContext,并移除所有您隐式绑定,一次一项。

这样 Helpers.UnFixupDataContext 的实现有点不寻常逻辑树遍历是注意足够不要使用任何可能失败的枚举器。 还有一个绑定并返回的一个函数。 它的图 11 所示。

图 11 InternalUnFixupDataContext

private static bool InternalUnFixupDataContext(DependencyObject dp)
{
// only consider those elements for which we've called
// FixupDataContext():
// they all belong to this namespace
if (dp is FrameworkContentElement && dp.GetType().Namespace ==
    typeof(Helpers).Namespace)
    {
        Binding binding = BindingOperations.GetBinding(dp,
            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(dp, FrameworkContentElement.
                       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(dp))
        if (child is DependencyObject)
            if (InternalUnFixupDataContext((DependencyObject)child))
                return true;
    return false;
}

撤消绑定是否则函数返回 true,否则返回 False。 直到无绑定离开撤消,(如下所示,不断地调用该函数:

public static void UnFixupDataContext(DependencyObject dp)
{
    while (InternalUnFixupDataContext(dp))
        ;
}

它是有点低效,但不破坏任何枚举器过程中。

结论

我打算编写一个控件,将使您能够流文档中使用数据绑定和模板。 甚至,我编写了标记我想要使用。 在开发控件,并 图 12 显示最后一个标记,例如。

图 12 </a0>-最终标记

<flowdoc:ItemsContent ItemsSource="{Binding Source=  {StaticResource DataSource},XPath=FortressGuys/Person}" >
    <flowdoc:ItemsContent.ItemsPanel>
        <DataTemplate>
            <flowdoc:Fragment>
                <Table BorderThickness="1" BorderBrush="Black">
                    <TableRowGroup flowdoc:Attached.IsItemsHost="True">
                        <TableRow Background="LightBlue">
                            <TableCell>
                                <Paragraph>First name</Paragraph>
                            </TableCell>
                            <TableCell>
                                <Paragraph>Last name</Paragraph>
                            </TableCell>
                        </TableRow>
                    </TableRowGroup>
                </Table>
            </flowdoc:Fragment>
        </DataTemplate>
    </flowdoc:ItemsContent.ItemsPanel>
    <flowdoc:ItemsContent.ItemTemplate>
        <DataTemplate>
            <flowdoc:Fragment>
                <TableRow>
                    <TableCell>
                        <Paragraph>
                            <flowdoc:BindableRun BoundText="{Binding XPath=@FirstName}" />
                        </Paragraph>
                    </TableCell>
                    <TableCell>
                        <Paragraph>
                            <flowdoc:BindableRun BoundText="{Binding XPath=@LastName}"/>
                        </Paragraph>
                    </TableCell>
                </TableRow>
            </flowdoc:Fragment>
        </DataTemplate>
    </flowdoc:ItemsContent.ItemTemplate>
</flowdoc:ItemsContent>

正如您所看到的此标记将是非常接近于我希望最初的。改进都可能,,但我们需要编写的代码的量,这是确实很好结果。完成的任务。代码下载中随附的代码显示该组件中。

Vincent van den Berghe 包含计算的科学和人工智能中的主控形状度。他是为软件工程师 部门 van Dijk 电子发布. 其当前的工作涉及开发使用 WCF 和 WPF 的 LOB 应用程序。Vincent 是 ACM (关联的计算机械) 专业成员。