Как создавать и печатать большие документы XPS в WPF?

Я хотел бы сгенерировать (а затем распечатать или сохранить) большие документы XPS ( > 400 страниц) из моего приложения WPF. У нас есть большое количество данных в памяти, которые нужно записать в XPS.

Как это можно сделать без получения OutOfMemoryException? Есть ли способ написать документ в кусках? Как это обычно делается? Должен ли я не использовать XPS для больших файлов в первую очередь?

Основной причиной OutOfMemoryException является создание огромного FlowDocument. Я создаю полный FlowDocument, а затем отправляю его в документ документа XPS. Это неправильный подход?

Ответ 1

Я могу подтвердить, что XPS не выдает из памяти лишние документы. И то, и другое в теории (поскольку операции с XPS основаны на страницах, он не пытается загрузить весь документ в памяти), и на практике (я использую отчеты на основе XPS, а сообщения об ошибках при запуске составляют до нескольких тысяч страниц).

Может быть, проблема заключается в одной особенно большой странице? Огромное изображение, например? Большая страница с высоким разрешением DPI? Если один объект в документе слишком большой, чтобы быть выделенным сразу, это приведет к исключению из памяти.

Ответ 2

Как вы это делаете? У вас не было никакого кода.

Я использую 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);
}

Это не работает для вас?

Ответ 3

Говоря о безупречном незнании конкретной конкретной системы, могу ли я предложить использовать метод Wolf Fence в методе отладки Аляски, чтобы определить источник проблемы? Я предлагаю это, потому что другие респонденты не сообщают о той же самой проблеме, которую вы испытываете. При работе с легковоспроизводимыми ошибками Wolf Fence очень прост в использовании (это не так хорошо работает с условиями гонки и т.п.).

  • Выберите среднюю точку в ваших входных данных и попытайтесь сгенерировать свой выходной документ только из этих данных, не более.
  • Если это удастся, выберите точку на 75% во вход и повторите попытку, в противном случае выберите точку примерно на 25% пути на вход и повторите попытку.
  • Намойте, промойте, повторите, каждый раз сужая окно, где находится строка works/fail.
  • Вы можете обнаружить, что достаточно быстро идентифицируете одну конкретную страницу - или, возможно, один конкретный объект на этой странице - это "смешно". Примечание: вам нужно только сделать это log2 (N) раз, или в этом случае 9 раз, учитывая 400 страниц, которые вы упомянули.

Теперь у вас, вероятно, есть что-то, что можно атаковать напрямую. Удачи.

Ответ 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, чтобы выкладывать свой документ по одной секции за раз, а не сразу, ваш пользовательский paginator может работать за два прохода:

  • Первый проход происходит по мере создания paginator. Он создает FlowDocument для каждого раздела, затем получает DocumentPaginator для извлечения количества страниц. Каждый раздел FlowDocument отбрасывается после подсчета страниц.
  • Второй проход происходит во время фактического вывода документа: если номер, переданный в GetPage(), находится в самом последнем FlowDocument, созданном, GetPage() просто вызывает этот документ-страницу для получения соответствующей страницы. В противном случае он отбрасывает этот FlowDocument и создает FlowDocument для нового раздела, получает его paginator, а затем вызывает GetPage() в paginator.

Эта стратегия позволяет вам продолжать использовать FlowDocuments, как вы были, до тех пор, пока вы можете разбить данные на "разделы", каждый со своим собственным документом. Затем ваш пользовательский paginator эффективно обрабатывает все отдельные FlowDocuments как один большой документ. Это похоже на функцию "Мастер-документ" Word.

Если вы можете визуализировать свои данные как последовательность вертикально-уложенных изображений

В этом случае можно использовать тот же метод. Во время первого прохода все изображения генерируются по порядку и измеряются, чтобы увидеть, сколько их поместит на странице. Структура данных построена, чтобы указать, какой диапазон визуальных эффектов (по индексу или что-то еще) находится на данной странице. Во время этого процесса каждый раз, когда страница заполняется, следующий визуал помещается на новую страницу. Верхние и нижние колонтитулы будут обрабатываться очевидным образом.

Во время создания фактического документа используется метод GetPage() для регенерации визуальных изображений, ранее принятых на данной странице, и их объединения с использованием вертикальной панели DockPanel или другой панели по вашему выбору.

Я нашел этот метод более гибким в долгосрочной перспективе, потому что вам не нужно иметь дело с ограничениями FlowDocument.

Ответ 5

Вы использовали sos, чтобы узнать, что использует всю память?

Возможно, что во время создания вашего документа создаются управляемые или неуправляемые объекты, и они не освобождаются до тех пор, пока документ не будет завершен (или вообще не будет).

Отслеживание утечек управляемой памяти Рико Мариани может помочь.

Ответ 6

как вы говорите: возможно, в памяти FixedDocument потребляется слишком много памяти.

Может быть, подход, при котором вы каждый раз пишете страницы XPS (и убедитесь, что FixedDocument будет выпущен каждый раз), а затем использование слияния впоследствии может оказаться плодотворным.

Вы можете писать каждую страницу отдельно?

Ник.

пс. Не стесняйтесь контактировать меня напрямую ([email protected]); мы делаем много вещей XPS в NiXPS, и я очень заинтересован в том, чтобы помочь вам решить эту проблему.