Как создавать и печатать большие документы XPS в WPF? - PullRequest
10 голосов
/ 26 февраля 2010

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

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

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

Ответы [ 6 ]

5 голосов
/ 26 февраля 2010

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

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

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

4 голосов
/ 09 марта 2010

Вы не можете использовать один 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() просто вызывает paginator этого документа для получения соответствующей страницы. В противном случае он отбрасывает этот FlowDocument и создает FlowDocument для нового раздела, получает его paginator, а затем вызывает GetPage() для paginator.

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

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

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

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

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

4 голосов
/ 08 марта 2010

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

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

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

3 голосов
/ 03 марта 2010

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

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

0 голосов
/ 07 марта 2010

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

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

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

Ник.

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

0 голосов
/ 03 марта 2010

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

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

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

...