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

发布于 2024-08-22 17:03:33 字数 328 浏览 17 评论 0原文

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

如何在不出现 OutOfMemoryException 的情况下完成此操作?有没有办法可以将文档分成块?这通常是如何完成的?我一开始就不应该对大文件使用 XPS 吗?

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

I would like to generate (and then print or save) big XPS documents (>400 pages) from my WPF application. We have some large amount of in-memory data that needs to be written to XPS.

How can this be done without getting an OutOfMemoryException? Is there a way I can write the document in chunks? How is this usually done? Should I not be using XPS for large files in the first place?

The root cause of the OutOfMemoryException seems to be the creation of the huge FlowDocument. I am creating the full FlowDocument and then sending it to the XPS document writer. Is this the wrong approach?

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

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

发布评论

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

评论(6

晨与橙与城 2024-08-29 17:03:34

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

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

这对您不起作用吗?

How do you do it? You didn't show any code.

I use an XpsDocumentWriter to write in chunks, like this:

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

Does this not work for you?

萌能量女王 2024-08-29 17:03:34

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

  1. 在输入数据中选择一个中点,然后尝试仅根据该数据生成输出文档,不再使用其他数据。
  2. 如果成功,请选择输入中大约 75% 的点并重试,否则选择输入中大约 25% 的点并重试。
  3. 起泡沫,冲洗,重复,每次将窗口缩小到有效/失败线所在的位置。
  4. 您可能会发现您很快就识别出了一个“有趣”的特定页面,或者可能是该页面上的一个特定对象。注意:您只需执行此 log2(N) 次,或者在本例中,鉴于您提到的 400 页,执行 9 次。

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

Speaking from perfect ignorance of the specific system involved, might I suggest using the Wolf Fence in Alaska debugging technique to identify the source of the problem? I'm suggesting this because other responders are not reporting the same problem you are experiencing. When working with easy-to-reproduce bugs Wolf Fence is dead simple to do (it doesn't work so well with race conditions and the like).

  1. Pick a midpoint in your input data and try to generate your output document from only that data, no more.
  2. If it succeeds, pick a point about 75% into the input and try again, otherwise pick a point at about 25% of the way into the input and try again.
  3. Lather, rinse, repeat, each time narrowing the window to where the works/fails line is.
  4. You may find that you fairly quickly identify one specific page -- or perhaps one specific object on that page -- that is "funny." Note: you only have to do this log2(N) times, or in this case 9 times given the 400 pages you mention.

Now you probably have something you can attack directly. Good luck.

故事↓在人 2024-08-29 17:03:34

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

You cannot use a single FlowDocument for generating large documents because you will run out of memory. However if it is possible to generate your output as a sequence of FlowDocument or as an extremely tall ItemsControl, it is possible.

I've found the easiest way to do this is to subclass DocumentPaginator and pass an instance of my subclass to XpsDocumentWriter.Write:

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

The WidgetPaginator class itself is quite simple:

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

Of course you still have to write the code that actually creates the pages.

If you want to use a series of FlowDocuments to create your document

If you're using a sequence of FlowDocuments to lay out your document one section at a time instead of all at once, your custom paginator can work in two passes:

  • The first pass occurs as the paginator is constructed. It creates a FlowDocument for each section, then gets a DocumentPaginator to retrieve the number of pages. Each section's FlowDocument is discarded after the pages are counted.
  • The second pass occurs during actual document output: If the number passed to GetPage() is in the most recent FlowDocument created, GetPage() simply calls that document's paginator to get the appropriate page. Otherwise it discards that FlowDocument and creates a FlowDocument for the new section, gets its paginator, then calls GetPage() on the paginator.

This strategy allows you to continue to use FlowDocuments as you have been, as long as you can break the data into "sections" each with its own document. Your custom paginator then effectively treats all the individual FlowDocuments as one big document. This is similar to Word's "Master Document" feature.

If you can render your data as a sequence of vertically-stacked visuals

In this case, the same technique can be used. During the first pass, all visuals are generated in order and measured to see how many will fit on a page. A data structure is built to indicate which range of visuals (by index or whatever) are found on a given page. During this process each time a page fills up, the next visual is placed on a new page. Headers and footers would be handled in the obvious way.

During the actual document generation, the GetPage() method is implemented to regenerate the visuals previously decided to be on a given page and combine them using a vertical DockPanel or other panel of your choice.

I've found this technique more flexible in the long run because you don't have to deal with the limitations of FlowDocument.

淡淡的优雅 2024-08-29 17:03:34

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

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

I can confirm that XPS does not throw out-of-memory on long documents. Both in theory (because operations on XPS are page-based, it doesn't try to load whole document in memory), and in practice (I use XPS-based reporting, and seen run-away error messages add up to many thousands of pages).

Could it be that the problem is in a single particularly large page? A huge image, for example? Large page with high DPI resolution? If single object in document is too big to be allocated at once, it will lead to out-of-memory exception.

£烟消云散 2024-08-29 17:03:34

您是否使用过 sos 来找出是什么耗尽了所有内存?

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

Rico Mariani 的跟踪托管内存泄漏可能是的帮助。

Have you used sos to find out what is using up all the memory?

It could be that managed or unmanaged objects are being created during the production of your document, and they're not being released until the document is finished (or not at all).

Tracking down managed memory leaks by Rico Mariani could be of help.

长安忆 2024-08-29 17:03:34

就像你说的:可能内存中的固定文档消耗了太多的内存。

也许您单独编写 XPS 页面(并确保每次都发布固定文档),然后使用合并的方法可能会取得成果。

你能把每一页分开写吗?

缺口。

附:请随时直接与我联系([电子邮件受保护]);我们在 NiXPS 做了很多 XPS 工作,我非常有兴趣帮助您解决这个问题。

like you say: probably the in-memory FixedDocument is consuming too much memory.

Maybe an approach in which you write out the XPS pages each individually (and make sure the FixedDocument gets released each time), and then use a merger afterwards could prove fruitful.

Are you able to write each page separately?

Nick.

ps. Feel free to concact me directly ([email protected]); we do a lot of XPS stuff at NiXPS, and I'm very interested in helping you getting this issue resolved.

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