渲染到 XPS 时如何创建带有页眉和页脚的 Xaml FlowDocument?

发布于 2024-10-20 00:37:32 字数 87 浏览 1 评论 0原文

我正在寻找一种干净通用的方法来描述 XAML FlowDocument 中重复的页眉和页脚,而无需任何代码隐藏。它只需要在从 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#.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

漫雪独思 2024-10-27 00:37:32

几个月前我遇到了同样的问题,发现这些链接非常有帮助:
WPF 多页报表第四部分分页
http://www.codeproject.com/KB/WPF/PimpedDocumentPaginator.aspx

我使用的基本技术是通过从 DynamicDocumentPaginator 派生来创建自定义分页器,如下所示:

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

在我的例子中,THeaderFooterModelTFooterModelPageNumberModel 类型,因为我需要页眉或页脚能够显示当前页码。

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

自定义分页器委托原始 XPS 分页器来完成其大部分工作,因此它将其存储在构造函数中。

THeaderModelTFooterModel 类型允许分页器检索每种类型的 XAML DataTemplates,这允许您指定页眉和页眉的布局。 XAML 中的页脚,无需求助于自定义绘图代码。

在我的代码中,页眉和页脚具有固定大小,因此当创建分页器时,它会检索页眉和页脚模板以确定要保留多少空间。

在提供的链接中的示例代码中,他们用来为页眉和页脚保留空间的技术是使用缩放变换来缩小原始内容。相反,我告诉原始分页器使用减小的页面大小,然后将原始分页器生成的页面添加到 ContainerVisual 并设置其 Offset。如果页眉和页脚的大小是动态的,您可能无法执行此操作,因为页数会不断变化。

我记得的唯一的其他复杂之处是,在添加页眉和页脚时需要使用 Dispatcher 队列(请参阅下面的 AddHeaderOrFooterToContainerAsync)。否则数据绑定不起作用。我们稍微颠覆了 WPF 渲染模型以使其发挥作用。

如果不包含代码,这一切都很难解释,因此我在下面附加了自定义渲染器代码。我已经删除了一些不相关的内容,因此如果它不能按原样编译,这可能就是原因:-)

请注意,传入页码偏移量是因为我们的 XPS 文档由多个 FlowDocument 部分组成,并且调用代码会跟踪当前总页码。

希望这有帮助!

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

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

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()
{
...
}

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

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

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.

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.

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.

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.

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

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.

Hope this helps!

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; }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文