如何在呈现时为XPS创建XAML中的FlowDocument与页眉和页脚?页眉、如何在、XAML、XPS

由网友(一眼心动)分享简介:我要寻找一个干净的通用的方式来描述重复的页眉和页脚在XAML FlowDocument的背后没有任何code的想法。它只需要正确地显示,当从C#呈现为XPS。I am looking for an idea of a clean generic way to describe repeating page heade...

我要寻找一个干净的通用的方式来描述重复的页眉和页脚在XAML FlowDocument的背后没有任何code的想法。它只需要正确地显示,当从C#呈现为XPS。

I am looking for an idea of a clean generic way to describe repeating page headers and footers in a XAML FlowDocument without any code behind. It only needs to show up correctly when rendered to XPS from C#.

推荐答案

我有同样的问题在几个月前,发现这些链接非常有用: http://janrep.blog.$c$cplant.net/post/WPF-Multipage-Reports-Part-IV-Pagination.aspx HTTP://www.$c$cproject.com/KB/WPF/PimpedDocumentPaginator.aspx

I had the same problem a few months ago, and found these links very helpful: http://janrep.blog.codeplant.net/post/WPF-Multipage-Reports-Part-IV-Pagination.aspx http://www.codeproject.com/KB/WPF/PimpedDocumentPaginator.aspx

的基本技术我用的是从 DynamicDocumentPaginator 导出创建自定义分页程序如下:

The basic technique I used was to create a custom paginator by deriving from DynamicDocumentPaginator as follows:

internal class HeaderFooterPaginator<THeaderModel, TFooterModel> : DynamicDocumentPaginator where THeaderModel : PageNumberModel, new() where TFooterModel : PageNumberModel, new()
{
...
}

在我的情况下, THeaderFooterModel TFooterModel PageNumberModel 为我所需要的页眉或页脚能够显示当前页码类型。

In my case, THeaderFooterModel and TFooterModel are subclasses of a PageNumberModel type as I needed the header or footer to be able to show the current page number.

public class PageNumberModel
{
    public int PageNumber { get; set; }
}

自定义分页程序委托给原来的XPS分页程序做了大部分的工作,所以它存储走在构造函数中。

The custom paginator delegates to the original XPS paginator to do the majority of its work, so it stores it away in the constructor.

THeaderModel TFooterModel 类型允许分页程序来检索XAML 的DataTemplates 为每个类型,这是允许你指定在XAML页眉和页脚的布局,而不诉诸自定义绘制code。

The THeaderModel and TFooterModel types allow the paginator to retrieve XAML DataTemplates for each type, which is what allows you to specify the layout of the header and footer in XAML without resorting to custom drawing code.

在我的code,页眉和页脚的大小是固定的,因此在创建分页程序时,它检索页眉和页脚模板,以确定有多少空间来保留。

In my code, the header and footer are of a fixed size, so when the paginator is created it retrieves the header and footer templates to determine how much space to reserve.

在示例code中提供的链接,他们所使用的预留空间页眉和​​页脚中的技术是使用一个尺度变换来缩小原始内容。相反,我告诉原来的分页程序使用缩小页面大小,然后添加到 ContainerVisual 原来的分页程序生成的页面,并设置其偏移。你可能无法做到这一点,如果页眉和页脚的大小是动态的,因为页数会不断变化。

In the example code in the links provided, the technique they use to reserve space for the header and footer is to use a scale transform to shrink the original content. Instead, I tell the original paginator to use a reduced page size and then add the page the original paginator generates to a ContainerVisual and set its Offset. You probably couldn't do this if the size of the headers and footers was dynamic because the page count would keep changing.

唯一的其他并发症我记得的是,在添加页眉和页脚(见的时候需要使用调度队列 AddHeaderOrFooterToContainerAsync 以下)。数据绑定不工作,否则。我们略微颠覆WPF渲染模型来得到这个工作。

The only other complication I can recall was that you need to use the Dispatcher queue when adding headers and footers (see AddHeaderOrFooterToContainerAsync below). Data binding doesn't work otherwise. We are slightly subverting the WPF rendering model to get this to work.

这都将是十分困难而不包括code,所以我已经把它贴在下面的自定义渲染code解释。我已经剥夺了一些无关痛痒的东西,所以,如果它不编译成的是,这可能是为什么: - )

This would all be quite hard to explain without including the code so I've attached the custom renderer code below. I've stripped out some irrelevant stuff so if it doesn't compile as is that's probably why :-)

请注意,该页面数偏移传递,因为我们的XPS文档是由多重的FlowDocument节和调用code跟踪目前整体的页码。

Note that the page number offset is passed in because our XPS document is comprised of multiple FlowDocument sections and the calling code keeps track of the current overall page number.

希望这有助于!

internal class HeaderFooterPaginator<THeaderModel, TFooterModel> : DynamicDocumentPaginator where THeaderModel : PageNumberModel, new() where TFooterModel : PageNumberModel, new()
{
    private readonly double _footerHeight;
    private readonly DataTemplate _footerTemplate;
    private readonly double _headerHeight;
    private readonly DataTemplate _headerTemplate;
    private Size _newPageSize;
    private Size _originalPageSize;
    private readonly DynamicDocumentPaginator _originalPaginator;
    private readonly int _pageNumberOffset;
    private readonly FlowDocument _paginatorSource;
    private const double HeaderAndFooterMarginHeight = 5;
    private const double HeaderAndFooterMarginWidth = 10;

    public HeaderFooterPaginator(int pageNumberOffset, FlowDocument document)
    {
        if (document == null)
        {
            throw new ArgumentNullException("document");
        }

        _paginatorSource = document;

        if (_paginatorSource == null)
        {
            throw new InvalidOperationException("Could not create a clone of the document being paginated.");
        }

        _originalPaginator = ((IDocumentPaginatorSource) _paginatorSource).DocumentPaginator as DynamicDocumentPaginator;

        if (_originalPaginator == null)
        {
            throw new InvalidOperationException("The paginator must be a DynamicDocumentPaginator.");
        }

        _headerTemplate = GetModelDataTemplate(typeof (THeaderModel));
        _footerTemplate = GetModelDataTemplate(typeof (TFooterModel));

        var headerSize = GetModelContentSize(new THeaderModel { PageNumber = int.MaxValue }, _headerTemplate, _originalPaginator.PageSize);
        var footerSize = GetModelContentSize(new TFooterModel { PageNumber = int.MaxValue }, _footerTemplate, _originalPaginator.PageSize);

        _headerHeight = double.IsInfinity(headerSize.Height) ? 0 : headerSize.Height;
        _footerHeight = double.IsInfinity(footerSize.Height) ? 0 : footerSize.Height;

        _pageNumberOffset = pageNumberOffset;

        SetPageSize(new Size(document.PageWidth, document.PageHeight));
    }

    private void AddHeaderOrFooterToContainerAsync<THeaderOrFooter>(ContainerVisual container, double areaWidth, double areaHeight, Vector areaOffset, FrameworkTemplate template, int displayPageNumber)  where THeaderOrFooter : PageNumberModel, new()
    {
        if (template == null)
        {
            return;
        }
        var visual = GetModelContent(new THeaderOrFooter { PageNumber = displayPageNumber }, template);

        if (visual != null)
        {
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() =>
            {
                visual.Measure(_originalPageSize);
                visual.Arrange(new Rect(0, 0, areaWidth, areaHeight));
                visual.UpdateLayout();

                var headerContainer = new ContainerVisual { Offset = areaOffset };
                headerContainer.Children.Add(visual);
                container.Children.Add(headerContainer);
            }));
        }
    }

    public override void ComputePageCount()
    {
        _originalPaginator.ComputePageCount();
    }

    private static void FlushDispatcher()
    {
        Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.ApplicationIdle, new DispatcherOperationCallback(delegate { return null; }), null);
    }

    private static FrameworkElement GetModelContent(object dataModel, FrameworkTemplate modelTemplate)
    {
        if (modelTemplate == null)
        {
            return null;
        }

        var content = modelTemplate.LoadContent() as FrameworkElement;
        if (content == null)
        {
            return null;
        }

        content.DataContext = dataModel;

        return content;
    }

    private static Size GetModelContentSize(object dataModel, FrameworkTemplate modelTemplate, Size availableSize)
    {
        var content = GetModelContent(dataModel, modelTemplate);
        if (content == null)
        {
            return Size.Empty;
        }

        FlushDispatcher();
        content.Measure(availableSize);
        return content.DesiredSize;
    }

    private DataTemplate GetModelDataTemplate(Type modelType)
    {
        var key = new DataTemplateKey(modelType);
        return _paginatorSource.TryFindResource(key) as DataTemplate;
    }

    public override ContentPosition GetObjectPosition(object value)
    {
        return _originalPaginator.GetObjectPosition(value);
    }

    public override DocumentPage GetPage(int pageNumber)
    {
        if (!_originalPaginator.IsPageCountValid)
        {
            ComputePageCount();
        }

        var originalPage = _originalPaginator.GetPage(pageNumber);

        var newPage = new ContainerVisual();

        var displayPageNumber = _pageNumberOffset + pageNumber;
        var internalWidth = _originalPageSize.Width - 2*HeaderAndFooterMarginWidth;
        AddHeaderOrFooterToContainerAsync<THeaderModel>(newPage, internalWidth, _headerHeight, new Vector(HeaderAndFooterMarginWidth, HeaderAndFooterMarginHeight), _headerTemplate, displayPageNumber);

        var smallerPage = new ContainerVisual();
        smallerPage.Children.Add(originalPage.Visual);
        smallerPage.Offset = new Vector(HeaderAndFooterMarginWidth, HeaderAndFooterMarginHeight + _headerHeight);
        newPage.Children.Add(smallerPage);

        AddHeaderOrFooterToContainerAsync<TFooterModel>(newPage, internalWidth, _footerHeight, new Vector(HeaderAndFooterMarginWidth, _originalPageSize.Height - HeaderAndFooterMarginHeight - _footerHeight), _footerTemplate, displayPageNumber);

        return new DocumentPage(newPage, _originalPageSize, originalPage.BleedBox, originalPage.ContentBox);
    }

    public override int GetPageNumber(ContentPosition contentPosition)
    {
        return _originalPaginator.GetPageNumber(contentPosition);
    }

    public override ContentPosition GetPagePosition(DocumentPage page)
    {
        return _originalPaginator.GetPagePosition(page);
    }

    private void SetPageSize(Size pageSize)
    {
        _originalPageSize = pageSize;

        // Decrease the available page size by the height of the header and footer. The page is offset by the header height later on.
        var sizeRect = new Rect(pageSize);
        sizeRect.Inflate(-HeaderAndFooterMarginWidth, -(HeaderAndFooterMarginHeight + _footerHeight/2 + _headerHeight/2));

        _originalPaginator.PageSize = _newPageSize = sizeRect.Size;

        // Change page size of the document to the size of the content area
        _paginatorSource.PageHeight = _newPageSize.Height;
        _paginatorSource.PageWidth = _newPageSize.Width;
    }

    public override bool IsPageCountValid
    {
        get { return _originalPaginator.IsPageCountValid; }
    }

    public override int PageCount
    {
        get { return _originalPaginator.PageCount; }
    }

    public override Size PageSize
    {
        get { return _newPageSize; }
        set { SetPageSize(value); }
    }

    public override IDocumentPaginatorSource Source
    {
        get { return _originalPaginator.Source; }
    }
}