如何在WPF中生成和打印大型XPS文档?

时间:2010-02-26 15:23:09

标签: wpf memory printing xps

我想从我的WPF应用程序生成(然后打印或保存)大型XPS文档(> 400页)。我们有一些需要写入XPS的大量内存数据。

如果没有获得OutOfMemoryException怎么办呢?有没有办法可以把文件写成块?这通常是怎么做的?我不应该首先将XPS用于大文件吗?

OutOfMemoryException的根本原因似乎是创建巨大的FlowDocument。我正在创建完整的FlowDocument,然后将其发送到XPS文档编写器。这是错误的做法吗?

6 个答案:

答案 0 :(得分:5)

你是怎么做到的?您没有显示任何代码。

我使用XpsDocumentWriter来编写块,如下所示:

FlowDocument flowDocument =  . .. ..;

// write the XPS document
using (XpsDocument doc = new XpsDocument(fileName, FileAccess.ReadWrite))
{
    XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(doc);
    DocumentPaginator paginator = ((IDocumentPaginatorSource)flowDocument).DocumentPaginator;

    // Change the PageSize and PagePadding for the document
    // to match the CanvasSize for the printer device.
    paginator.PageSize = new Size(816, 1056);
    copy.PagePadding = new Thickness(72);  
    copy.ColumnWidth = double.PositiveInfinity;
    writer.Write(paginator);
}

这不适合你吗?

答案 1 :(得分:4)

完全不了解所涉及的具体系统,我是否可以建议在阿拉斯加调试技术中使用Wolf Fence来确定问题的根源?我建议这样做是因为其他响应者没有报告您遇到的同样问题。使用易于重现的bug时,Wolf Fence很容易做到(它在竞争条件等方面效果不佳)。

  1. 在输入数据中选择一个中点,然后尝试仅从该数据生成输出文档。
  2. 如果成功,请在输入中选择一个大约75%的点,然后再试一次,否则选择一个点大约25%的点进入输入并重试。
  3. 泡沫,冲洗,重复,每次将窗户缩小到工作/失败线的位置。
  4. 您可能会发现您可以很快地识别出一个特定页面 - 或者可能是该页面上的一个特定对象 - 这很“有趣”。注意:您只需要执行log2(N)次,或者在这种情况下,在您提到的400页的情况下进行9次。
  5. 现在你可能有一些你可以直接攻击的东西。祝你好运。

答案 2 :(得分:4)

您不能使用单个FlowDocument来生成大型文档,因为内存不足。但是,如果可以将您的输出生成为FlowDocument或非常高ItemsControl的序列,则可以。

我发现最简单的方法是子类DocumentPaginator并将我的子类的实例传递给XpsDocumentWriter.Write

var document = new XpsDocument(...);
var writer = XpsDocument.CreateXpsDocumentWriter(xpsDocument);
writer.Write(new WidgetPaginator { Widget = widgetBeingPrinted, PageSize = ... });

WidgetPaginator类本身很简单:

class WidgetPaginator : DocumentPaginator, IDocumentPaginatorSource
{
  Size _pageSize;

  public Widget Widget { get; set; }

  public override Size PageSize { get { return _pageSize; } set { _pageSize = value; } }
  public override bool IsPageCountValid { return true; }
  public override IDocumentPaginatorSource Source { return this; }
  public override DocumentPaginator DocumentPaginator { return this; }
  public override int PageCount
  {
   get
    {
      return ...; // Compute page count
    }
  }
  public override DocumentPage GetPaget(int pageNumber)
  {
    var visual = ...; // Compute page visual

    Rect box = new Rect(0,0,_pageSize.With, _pageSize.Height);
    return new DocumentPage(visual, _pageSize, box, box);
  }

当然,您仍然需要编写实际创建页面的代码。

如果您想使用一系列FlowDocuments来创建文档

如果您使用FlowDocuments序列一次布置一个部分而不是一次整理文档,那么您的自定义分页符可以分两次使用:

  • 第一次传递是在构造分页符时发生的。它为每个部分创建FlowDocument,然后获取DocumentPaginator以检索页数。在计算页数后,每个部分的FlowDocument都会被丢弃。
  • 在实际文档输出期间发生第二次传递:如果传递给GetPage()的数字位于最近创建的FlowDocument中,则GetPage()只需调用该文档的分页符即可获取相应的页面。否则它会丢弃该FlowDocument并为新部分创建FlowDocument,获取其分页器,然后在分页器上调用GetPage()

此策略允许您像以前一样继续使用FlowDocuments,只要您可以将数据分成带有自己文档的“部分”。然后,您的自定义分页器会将所有单个FlowDocuments视为一个大文档。这类似于Word的“主文档”功能。

如果您可以将数据渲染为一系列垂直堆叠的视觉效果

在这种情况下,可以使用相同的技术。在第一次传递期间,所有视觉效果都按顺序生成并进行测量,以查看页面上适合的数量。构建数据结构以指示在给定页面上找到哪个视觉范围(通过索引或其他)。在此过程中,每次页面填满时,下一个视觉都会放在新页面上。页眉和页脚将以明显的方式处理。

在实际文档生成期间,实现GetPage()方法以重新生成先前决定在给定页面上的视觉效果,并使用垂直DockPanel或您选择的其他面板组合它们。

从长远来看,我发现这种技术更加灵活,因为您不必处理FlowDocument的限制。

答案 3 :(得分:3)

我可以确认XPS在长文档中会丢失内存。两者在理论上(因为XPS上的操作是基于页面的,它不会尝试将整个文档加载到内存中),并且在实践中(我使用基于XPS的报告,并且看到失控的错误消息总计数千个页)。

问题是在一个特别大的页面中吗?例如,巨大的形象?具有高DPI分辨率的大页面?如果文档中的单个对象太大而无法立即分配,则会导致内存不足异常。

答案 4 :(得分:0)

您是否使用过sos来查找耗尽所有内存的内容?

可能是在文档制作过程中创建托管或非托管对象,并且在文档完成(或根本没有)之前它们不会被释放。

Rico Mariani的

Tracking down managed memory leaks可能会有所帮助。

答案 5 :(得分:0)

就像你说的那样:内存中的FixedDocument可能消耗了太多内存。

也许是一种单独写出XPS页面的方法(并确保每次都释放FixedDocument),然后使用合并可以证明是富有成效的。

您是否可以单独编写每个页面?

尼克。

PS。随意直接与我联系(info@nixps.com);我们在NiXPS上做了很多XPS工作,我非常有兴趣帮助你解决这个问题。