Insert separator (blank line) in databound flowdocument

In my MVVM WPF application I'm using a databound flowdocument. I used the technique described here to be able bind my data to the flowdocument. My flowdocument is bound to a public property in my viewmodel. The property is an ienumerable of a custom type, like this:

public IEnumerable<Person> Persons
{
    get { return _persons; }
    set { _persons = value; }
}

The flowdocument is shown in a FlowDocumentScrollViewer control. The document looks like:

code    name    lastname
----    -----   ---------
1       john    johnson
1       peter   peterson
2       jane    jane
3       john    doe

The binding works fine however I want to add a blank line after each distinct code:

code    name    lastname
----    -----   ---------
1       john    johnson
1       peter   peterson

2       jane    jane

3       john    doe

My Xaml in the view is:

<FlowDocumentScrollViewer>
  <FlowDocument>
    <flowdoc:ItemsContent ItemsSource="{Binding Path=Persons}">
      <flowdoc:ItemsContent.ItemsPanel>
        <DataTemplate>
          <flowdoc:Fragment>
            <Table BorderThickness="1" BorderBrush="Black">
              <TableRowGroup flowdoc:Attached.IsItemsHost="True">
                <TableRow Background="LightBlue">
                  <TableCell>
                   <Paragraph>ronde</Paragraph>
              </TableCell>
              <TableCell>
                <Paragraph>hal</Paragraph>
              </TableCell>
              <TableCell>
                <Paragraph>datum</Paragraph>
              </TableCell>
            </TableRow>
          </TableRowGroup>
        </Table>
      </flowdoc:Fragment>
    </DataTemplate>
  </flowdoc:ItemsContent.ItemsPanel>
  <flowdoc:ItemsContent.ItemTemplate>
    <DataTemplate>
      <flowdoc:Fragment>
        <TableRow>
          <TableCell>
            <Paragraph>
              <flowdoc:BindableRun BoundText="{Binding Path=Code}" />
            </Paragraph>
          </TableCell>
          <TableCell>
            <Paragraph>
              <flowdoc:BindableRun BoundText="{Binding Path=Name}" />
            </Paragraph>
          </TableCell>
          <TableCell>
            <Paragraph>
              <flowdoc:BindableRun BoundText="{Binding Path=LastName}" />
            </Paragraph>
          </TableCell>
        </TableRow>
      </flowdoc:Fragment>
    </DataTemplate>
  </flowdoc:ItemsContent.ItemTemplate>
</flowdoc:ItemsContent>

Any suggestions on how to do this without breaking the MVVM rules? It seems like databinding a flowdocument comes with the price of inflexible layout.

Thanks in advance


In order to group your data, use a CollectionViewSource like this:

<Window.Resources>
    <CollectionViewSource x:Key="groupView" Source="{Binding Path=Persons}">
        <CollectionViewSource.GroupDescriptions>
            <PropertyGroupDescription PropertyName="Code"/>
        </CollectionViewSource.GroupDescriptions>
    </CollectionViewSource>
</Window.Resources>
<Grid>
    <FlowDocumentScrollViewer>
        <FlowDocument>
            <flowdoc:ItemsContent ItemsSource="{Binding Source={StaticResource groupView}}">
               ....
            </flowdoc:ItemsContent>
        </FlowDocument>
    </FlowDocumentScrollViewer>
</Grid>

The CollectionViewSource element above sorts the Persons list by code and determines the groups.

Furthermore, you must adapt the ItemsContent class, because the one from the MSDN article does not yet support grouping. Here is a very simple example how you could achieve that:

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

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

        // *** START NEW CODE ***
        ICollectionView view = itemsSource as ICollectionView;
        if (view != null)
        {
            foreach (object group in view.Groups)
            {
                GenerateContentForUngroupedItems(itemsPanel, itemTemplate, ((CollectionViewGroup)group).Items, panel);
                if (panel is TableRowGroup)
                {
                    TableRow row = new TableRow();
                    row.Cells.Add(new TableCell());
                    ((TableRowGroup)panel).Rows.Add(row);
                }
            }
        }
        else
        {
            GenerateContentForUngroupedItems(itemsPanel, itemTemplate, itemsSource, panel);
        }
        // *** END NEW CODE ***
    }
}

private void GenerateContentForUngroupedItems(DataTemplate itemsPanel, DataTemplate itemTemplate,
                                                IEnumerable itemsSource, FrameworkContentElement panel)
{
    foreach (object data in itemsSource)
    {
        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()));
        }
    }
}

Most of the code above is from the original MSDN article; the only changes I made are these:

  • Moved the foreach loop to the new method GenerateContentForUngroupedItems .
  • Moved the panel creation code (" if (panel==null) ") outside the loop.
  • Added the code marked NEW CODE .
  • What the modified code does is this: if the items source is not a collection view, it works exactly like the original MSDN code. If the items source is a collection view, two nested loops are called:

  • The outer loop iterates over all groups.
  • The inner loop iterates over all items in the current group.
  • The inner loop effectively is the same as the original foreach loop from the MSDN article.

    In the added code above, the empty table row is added by these lines:

    TableRow row = new TableRow();
    row.Cells.Add(new TableCell());
    ((TableRowGroup)panel).Rows.Add(row);
    

    This code, of course, is not very generic. For a generic solution, add a new dependency property to the ItemsContent control which contains a data template, just like the ItemsPanel or ItemTemplate properties. Then, you have the possibility to insert arbitrary text at the end of the group.

    The result looks like this:


    What you need is grouping. It stays within the MVVM rules.

    Having said that, a quick google for grouping on flowdocuments didnt immediately show me anything. Maybe have a look around for flowdocument wpf grouping.

    One solution may be to use a datagrid. you can definitely group on that with mvvm and make it look like the layout you have described above.

    链接地址: http://www.djcxy.com/p/50468.html

    上一篇: 将WPF只读视图绑定到视图模型

    下一篇: 在数据绑定flowdocument中插入分隔符(空白行)