11

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

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

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

4

6 回答 6

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

这对你不起作用吗?

于 2010-02-26T15:28:41.287 回答
4

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

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

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

于 2010-03-08T19:06:34.517 回答
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.

于 2010-03-09T11:11:57.493 回答
3

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

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

于 2010-03-03T10:21:27.577 回答
0

你有没有用 sos 找出是什么用完了所有的内存?

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

追踪Rico Mariani 的托管内存泄漏可能会有所帮助。

于 2010-03-03T16:17:55.093 回答
0

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

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

你能单独写每一页吗?

缺口。

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

于 2010-03-07T09:50:21.873 回答