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

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

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

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

6 ответов


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

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


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

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

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


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

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

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


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


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

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

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


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

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

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

Ник.

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