Я хотел бы генерировать (и затем распечатать или сохранить) большие документы XPS (> 400 страниц) из моего приложения WPF. У нас есть некоторый большой объем данных в оперативной памяти, который должен быть записан в XPS.
Как это может быть сделано, не добираясь OutOfMemoryException
? Существует ли способ, которым я могу записать документ в блоках? Как это обычно делается? Разве я не должен использовать XPS для больших файлов во-первых?
Первопричина OutOfMemoryException
кажется, создание огромного FlowDocument
. Я создаю полное FlowDocument
и затем отправляя его устройству записи документа XPS. Действительно ли это - неправильный подход?
Я могу подтвердить, что 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);
}
У вас это не работает?
Использовали ли вы sos, чтобы выяснить, на что расходуется вся память?
Возможно, управляемые или неуправляемые объекты создаются во время работы над документом, и они не освобождаются до его завершения (или вообще не освобождаются).
Отслеживание утечек управляемой памяти Рико Мариани может быть полезно.
Говоря от совершенного незнания конкретной системы, могу ли я предложить использовать технику отладки "Волчий забор на Аляске" для определения источника проблемы? Я предлагаю это потому, что другие авторы ответов не сообщают о той же проблеме, с которой столкнулись вы. При работе с легко воспроизводимыми ошибками метод Wolf Fence очень прост (он не так хорошо работает с условиями гонки и тому подобным).
Теперь у вас, вероятно, есть что-то, что можно атаковать напрямую. Удачи.
Вы не можете использовать один 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
.
как вы говорите: вероятно, фиксированный документ в памяти потребляет слишком много памяти.
Может быть, подход, при котором вы записываете каждую страницу XPS по отдельности (и убедитесь, что FixedDocument выпускается каждый раз), а затем используете слияние, может оказаться плодотворным.
Можете ли вы писать каждую страницу отдельно?
Ник.
пс. Не стесняйтесь обращаться ко мне напрямую (скрыто), мы делаем много вещей XPS на NiXPS, и я очень заинтересован в том, чтобы помочь вам решить эту проблему.