本文章是由機器翻譯。

動態的 WPF

使用非固定格式文件和資料繫結建立可變式 UI

Vincent Van Den Berghe

可從 MSDN 程式庫 的程式碼下載
瀏覽線上的程式碼

本文將告訴您:

  • 非固定格式文件
  • 資料繫結問題
  • 建立資料繫結控制項
  • 使用範本
本文將使用下列技術:
WPF,.NET 3.5 SP1

內容

目標
可繫結的執行
FrameworkContentElements 的範本
ItemsContent 實作
結論

非固定格式文件是 適合在 Windows Presentation Foundation (WPF) 中顯示唯讀資料。他們提供極大的彈性排列文字版面配置 」 和 「 重新編頁。此外,非固定格式文件的控制項提供您具有註釋列印支援和剪貼簿功能幾乎可用。這樣與傳統的 WPF 的控制項 (TextBlock,ItemsControl 和衍生性工具),會更要有困難。

雖然許多強大的功能中有非固定格式的文件如果您的文件會產生動態資料,您就會有一些問題: 有不是支援非固定格式文件中的資料繫結。流程文件項目 (區段、 資料表、 執行、 段落和類似) 會是相依性的物件,但沒有定義任何相依性屬性讓您動態地變更,或產生內容。

任何非固定格式文件的部分更新必須在元件後,非固定格式文件不支援直接繫結的資料,在開始就產生非固定格式文件所完成。在這方面,非固定格式文件,都是較不具彈性,比其他的 WPF Framework 項目。

這將遺失相依性內容,看起來像的簡單的解決方案,但不是,不是大小寫)。像通常,the devil 是在的詳細資料並您取得不少的詳細資料] 權限讓它正常運作。

這個問題進行更具體的請考慮的 XmlDataProvider 中包含下列簡單 XML 文件範例:

<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>

顯示此資料,使用一般的資料繫結的方法和我們可靠的筆記本的 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 資料

現在我要顯示為非固定格式文件中的資料表。 在理想的情況下,I would like 元件 ListView,類似,但的瞭解繫結至項目的來源,並的可以產生資料表內容,以動態方式在一個 FlowDocument。 也就是說,我需要一種的 ItemsControl,但非固定格式文件。 並沒有避開 InlineUIContainer 與 BlockUIContainer: 雖然這些可讓您內嵌任何 WPF 控制項,例如 ListView,您會遺失其內容複製到 [剪貼簿] 時。 這種方式內嵌控制項不會神奇地將他們變成非固定格式文件項目。

所以我將需要自行撰寫,不存在這類元件。 我會呼叫它 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"(這剛好是為我們標頭是 [確定],相同資料表資料列群組)。 這很類似您將裝載 ItemsPanel 範本中的項目,一個 ItemsControl 的面板的標記。

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

如果有一件事,不是很棒嗎? 您可以看到此元件是非常有彈性的。 除了資料繫結,它也可以使用非固定格式文件中的範本。 它不解決每個流程文件資料繫結問題有,但如果我們可以提取關閉此,看幾乎一般在比較所有其他的資料繫結問題。

在本文的其餘部分中,我會顯示如何建置這類猛獸],或至少資訊的類似它密切。 此外,我希望提供足夠的背景資訊,以處理任何其他非固定格式文件的資料繫結問題。 請繼續閱讀。

可繫結的執行

如果您搜尋網站中的,非固定格式文件] 和 [資料繫結範例,您繫是結 (因此,說中) 至 stumble 跨 BindableRun (fortes.com/2007/03/bindablerun)。 它是在許多類別,和其最簡單的表單看起來像 [圖 5 )。

實作很簡單: 我們在繼承自執行、 定義相依性屬性 BoundText,及同步處理 [執行] 的文字屬性,其值。 執行資料的依存物件,但其文字內容沒有相依性屬性。 BoundText 是文字的只要相依性屬性的版本可讓我們使用資料繫結運算式。 就是這麼簡單。

因此其使用方式是直接了當。 而不是 <...執行 / >,您使用下列程式碼的 flowdoc 是您定義的所在位置 XML 命名空間:

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

不幸的是,這個類別有時候會失敗。 特別是,例外狀況 「 已修改的集合 ; 列舉作業可能會不執行 」 如果將擲回 BoundText 的繫結運算式,會根據如 DataContext,繼承的相依性屬性的值而定,且繫結評估執行繼承傳用期間。 (請參魷 \ cs6 \ f1 \ cf6 \ lang1024 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 會呼叫 BeginChange 上執行的 TextContainer],並 TextContainer 遞增的層代編號 (基本上的內部版本編號用來追蹤變更至的容器)。
  • 使用目前現用的列舉值 TreeWalkHelper (RangeContentEnumerator) 會偵測 MoveNext() 它的下一個呼叫期間的層代編號的變更,並將失敗,例外狀況,「 已修改的集合 ; 列舉作業可能會不執行 」。

因為上述的處理程序燒到 WPF 和重要的型別,而且是內部的實作的方法,您都無法解決這個問題中。 您可以但是,規避問題的 「 中斷 」 值傳用。 這樣的一種方式是繫結至 FrameworkElement 衍生的祖系的對應屬性的 BindableRun 的 DataContext。 這是有用我已將它放在它自己的 Helper 類別:

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)。

如,其中一個主要的差異,使其 commonalities 出將會是事實上它們會發生在繼承階層架構中, 不同點 [圖 7 .

這尋找像工作混合-在的類別,但這需要不可能在.NET 的多重繼承。 也許是真實中的舊的說您不需要多重繼承通常,但如果您執行這項需要它格式。

其中一個仍然,wonders 是否設計無法已經更好。 在仔細檢查 FrameworkElementFactory 實作的顯示硬式編碼的型別類似格線,GridViewRowPresenter、 RowDefinition、 ColumnDefinition 和 Visual3D 知識。 其中一個預期會抽象化,從其常用的 Factory 的實作的建構型別的特定執行個體知識。 否則,您會得到一個的此外,會強迫您重新造訪,每次您將另一個特殊的型別加入至系統與您應該以某種方式知道哪些型別有特殊非常嚴格且 inextensible Factory 實作。 這 smells 類似的開啟 / 關閉設計原則違規情形。

因為我們會有足夠資訊確定應該已完成 (而且我們還是無法變更一件事) 我將以下將討論,並著重於解決方案: 我們要如何定義 FrameworkContentElements 的範本? 最簡單解決方案需要最少的程式碼,是建置兩個型別之間橋接器。

這類的橋接器存在於 WPF: [BlockUIContainer 會是流程的文件項目的非固定格式文件中嵌入 UIElements。 因此橋接器,但是我們需要以相反方向。

另一個現有橋接器是在 TextBlock: 在 FrameworkElement 衍生,可以包含的是一種特殊類型的 FrameworkContentElement 的 inlines 集合。 如果您限制您自己到內嵌,這是完美,但不要此限制。 該橋接器為太小您的目的。

其實橋接器類別是很容易撰寫。 讓我們來呼叫它片段,因為它會保存 (請參閱 [圖 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);
        }
    }
}

您可以看到有什麼地球-shattering 這裡: 很簡單的類別 (當然),衍生自 FrameworkElement 衍生,具有相依性屬性保留為預設內容 FrameworkContentElement 根。

具有這種類別您現在可以撰寫如下:

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

可使用這些樣板定義的您必須能夠在有些時候,載入它。 您需要特製化樣板 (Template-載入機制中,並傳回內容片段的方法。 您可以請注意這一次,為所有在另一個 [圖 9 ] 所示的 Helper 函式。

[圖 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);

程式碼將會擲回例外狀況 「 修改集合,列舉型別 (Enumeration) 作業可能會無法執行 」 所述之前,和類似的原因: 將項目從一個文字容器 (TextBlock) 加入到另一個 (段落) 隱含地從移除導致其產生號碼,將在第一個文字容器。 後一項 TextElementEnumerator 像一個的 RangeContentEnumerator 也檢查其 TextContainer 層代編號中,相同的例外狀況會擲回。

不同之這種情況下和前一個之間處在您現在有足夠避開這個問題,在來源上的控制項。 這就是為何您可以撰寫下列完成相同的事,但不使用列舉值:

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

ItemsContent 實作

您幾乎有您的實作,拼圖的所有片段,但還有一個設計決策左進行中。 您必須決定是否您的流程內容項目將會是區塊層級的項目] 或 [層級內嵌的項目。 決定任意,但一個需要,因為沒有任何控制項可以是兩者,請讓它。

在這的篇文章 ItemsContent 控制項是區塊層級的項目。 內嵌 (Inline) 版本會保留為的練習,將讀取器。 我無法從區塊直接,衍生,但從區段,它會有,我會加入我的動態內容在方便的區塊集合屬性。 區段也不會套用任何預設的格式項目,它包含,這就是我需要自訂控制項。

請注意您從衍生的基底類別的相關。 您可能想要直接,衍生從區塊,但您無法在目前取得。 重要的協助程式類別,如 BlockCollection (泛型的 TextElementCollection <> CSimpleSoapAppService) 有內部的建構函式,因此程式自己碼中無法使用。

基本上,ItemsContent 的實作很簡單: Just 一連串的相依性內容及一般的認為追蹤他們所做的變更。 實際的動作發生在控制項的內容以及應得仔細檢查 GenerateContent 方法 (請參閱 [圖 10 ])。 (注意,程式碼的其餘部分可以找到在程式碼下載中)。

[圖 10: 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 函式的工作。 Helper 函式會周遊邏輯樹狀結構移除 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: 最終標記

<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 Electronic 發佈. 他目前的工作,包括開發使用WCF 和 WPF 的 LOB 應用程式。森會是 ACM (關聯的電腦運算的機制) Professional 的成員。