【问题标题】:How to generate and print large XPS documents in WPF?如何在 WPF 中生成和打印大型 XPS 文档?
【发布时间】:2011-01-21 12:09:45
【问题描述】:

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

没有OutOfMemoryException 怎么办?有没有办法可以分块编写文档?这通常是怎么做的?我不应该首先将 XPS 用于大文件吗?

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

【问题讨论】:

  • > OutOfMemeoryExcpetion 很可能是由...引起的,换句话说,您实际上并没有检查。我们需要更多信息来告诉您问题所在。
  • @bitbonk 我现在遇到了同样的问题。你找到解决方案了吗?

标签: wpf memory printing xps


【解决方案1】:

您不能使用单个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 的限制。

【讨论】:

    【解决方案2】:

    由于完全不了解所涉及的特定系统,我是否建议使用阿拉斯加狼栅调试技术来确定问题的根源?我建议这样做是因为其他响应者没有报告您遇到的相同问题。当处理易于重现的错误时,Wolf Fence 非常简单(它不适用于竞争条件等)。

    1. 在输入数据中选择一个中点,并尝试仅从该数据生成输出文档,仅此而已。
    2. 如果成功,请在输入中约 75% 处选择一个点并重试,否则在输入中约 25% 处选择一个点并重试。
    3. 起泡、冲洗、重复,每次都将窗口缩小到工作/失败线所在的位置。
    4. 您可能会发现您很快就识别出一个“有趣”的特定页面 - 或者可能是该页面上的一个特定对象。注意:您只需要执行 log2(N) 次,或者在这种情况下,考虑到您提到的 400 个页面,执行 9 次。

    现在你可能有了可以直接攻击的东西。祝你好运。

    【讨论】:

    • 狼栅栏调试方法很棒~
    【解决方案3】:

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

    也许您单独写出每个 XPS 页面(并确保每次都发布 FixedDocument)然后使用合并的方法可能会证明是富有成效的。

    你能单独写每一页吗?

    尼克。

    ps。请随时直接与我联系(info@nixps.com);我们在 NiXPS 做了很多 XPS 方面的工作,我很乐意帮助您解决这个问题。

    【讨论】:

      【解决方案4】:

      你有没有用 sos 查出是什么耗尽了所有的内存?

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

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

      【讨论】:

      • OutOfMemeoryExcpetion 很可能是由在内存中创建巨大的 FlowDocument 引起的。当我无法为其创建 FixedDocument 时,我应该如何创建 XPS?
      • 如果您的 FlowDocument 没有保留比它需要的更多的对象,那么也许您可以创建一个自定义 DocumentPaginator 来按需生成页面?不过,关于这个主题的信息似乎并不多。
      【解决方案5】:

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

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

      【讨论】:

      • 我应该如何设置页面?我以为分页器是为我做的。
      • 我没有采取任何特殊措施,只是举例说明。就像其他人指出的那样,您提供的信息太少了。你的代码或系统一定有什么不寻常的地方,但我们怎么能猜出它是什么——没有调用堆栈或代码的 sn-ps?
      【解决方案6】:

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

      我使用 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);
      }
      

      这不适合你吗?

      【讨论】:

      • 块在哪里。你的意思是这个例子有可能将多个 XPS 文档写入同一个 flowDocument 吗? Wehn 我重复使用同一个分页器,所有 XPS 文档最终会出现在同一个 XPS 文件中还是同一个打印文档中?
      • 块:您可以在循环中使用 writer.Write()。即使没有这个,我猜 writer.Write() 在分页器上使用流式方法,并且不需要将任何内容累积到内存中。我不知道您所说的“使用相同的分页器”是什么意思。您需要出示您的代码。
      • 我需要生成多个 FlowDocument 实例,对吗?由于分页器来自 FlowDocument 实例。
      • 这不起作用。我们仍然得到 OutOfMemoryExcpetions。必须有一个最佳实践来从代码生成非常大的 XPS(>400 页)文档。
      猜你喜欢
      • 2010-10-04
      • 1970-01-01
      • 2019-05-09
      • 2012-12-05
      • 2011-04-17
      • 2012-04-01
      • 2015-09-01
      • 1970-01-01
      • 2012-05-03
      相关资源
      最近更新 更多