【问题标题】:How to create a Xaml FlowDocument with Page Headers and Footers when rendered to XPS?呈现到 XPS 时如何创建带有页眉和页脚的 Xaml FlowDocument?
【发布时间】:2017-12-04 18:28:13
【问题描述】:

我正在寻找一种简洁的通用方法来描述 XAML FlowDocument 中重复的页眉和页脚,而无需任何代码。它只需要在从 C# 渲染到 XPS 时正确显示。

【问题讨论】:

  • “不包含任何代码”是指您希望仅通过 XAML 指定页眉和页脚吗?我不确定这是否可能。将 FlowDocument 转换为 FixedDocument 的分页过程将剪切页面,因为它根据固定页面大小布置 FlowDocument 内容。除非您的 FlowDocument 假定页面大小并相应地以固定大小布置其内容,否则分页过程不一定会剪切页面或将页眉和页脚放置在您想要的位置。
  • @mcw0933 我指的是后面的文字代码,附加到 XAML 文件的 cs,我的要求可以假设页面大小,但当然可以将自定义数据放入 XAML 文件并读出在xps渲染阶段之前修改分页。

标签: c# wpf xaml .net-4.0 flowdocument


【解决方案1】:

几个月前我遇到了同样的问题,发现这些链接很有帮助: WPF Multipage Reports Part IV Pagination 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; }
    }
}

【讨论】:

  • 嗨,大卫,在您的系统中。您如何将多个流文档呈现到一个 XPS 文档中?
  • 嗨,已经有一段时间了,但我不记得需要这样做。这有帮助吗:stackoverflow.com/questions/9508173/…?
  • 嗨,我不明白的是,那些 DataTemplates 应该是什么样子。现在我的 Flow 文档中有段落。我想将它们移动到页眉,以便它们出现在每一页的开头。我不知道如何做到这一点。谁能给我一个线索?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-04-09
  • 2010-09-27
  • 2011-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多