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

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

Как это может быть сделано, не добираясь OutOfMemoryException? Существует ли способ, которым я могу записать документ в блоках? Как это обычно делается? Разве я не должен использовать XPS для больших файлов во-первых?

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

10
задан Zach Johnson 6 March 2010 в 21:22
поделиться

6 ответов

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

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

3
ответ дан 3 December 2019 в 21:59
поделиться

Как вы это делаете? Вы не показали никакого кода.

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

У вас это не работает?

5
ответ дан 3 December 2019 в 21:59
поделиться

Использовали ли вы sos, чтобы выяснить, на что расходуется вся память?

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

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

0
ответ дан 3 December 2019 в 21:59
поделиться

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

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

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

4
ответ дан 3 December 2019 в 21:59
поделиться

Вы не можете использовать один 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 .

4
ответ дан 3 December 2019 в 21:59
поделиться

как вы говорите: вероятно, фиксированный документ в памяти потребляет слишком много памяти.

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

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

Ник.

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

0
ответ дан 3 December 2019 в 21:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: