Улучшенный класс PrintPreviewDialog с выводом в формат PDF
Данная статья описывает реализацию улучшенного класса PrintPreviewDialog, который обеспечивает вывод в формате PDF в дополнение к стандартным возможностям печати и предварительного просмотра. Исходные данные Класс PrintPreviewDialog удобен и прост в использовании. Необходимо всего лишь создать экземпляр диалогового класса, присвоить объект PrintDocument свойству Document и вызвать метод ShowDialog. Тем не менее, у класса PrintPreviewDialog есть и некоторые недостатки, в том числе следующие.
Представленный здесь класс CoolPrintPreviewDialog исправляет данные недостатки. Его так же легко использовать, как стандартный класс PrintPreviewDialog, но при этом доступны следующие улучшения.
Использование кода Использовать класс CoolPrintPreviewDialog так же просто, как и PrintPreviewDialog. Необходимо реализовать элемент управления, задать для свойства Document значение PrintDocument для предварительного просмотра, а затем вызвать метод Show для диалога. Если существующий код использует класс PrintPreviewDialog, переход к CoolPrintPreviewDialog потребует изменения лишь одной строки кода. Например:
Создание изображений для просмотра В основе класса CoolPrintPreviewDialog лежит элемент управления CoolPreviewControl, который выполняет создание и отображение предварительного просмотра страницы. У объектаPrintDocument есть свойство PrintController, которое описывает объект, ответственный за создание объектов Graphics, в которых выполняется преобразование документа. Контроллер печати по умолчанию создает объекты Graphics для принтера по умолчанию и в данном случае он не рассматривается. Также .NET приводит определение класса PreviewPrintController для создания метафайлов. Эти файлы доступны тому, что вызвал метод, и отображаются в области предварительного просмотра. Элемент управленияCoolPreviewControl работает следующим образом: выполняется замена изначального контроллера печати документа на PreviewPrintController, затем для документа вызывается метод Print для получения изображений страниц в процессе преобразования документа. Изображения соответствуют страницам в документе. Выполнятся масштабирование и последующее отображение страниц в элементе управления, аналогично любому объекту типа Image (изображение). Для создания предварительного просмотра страниц используется следующий код (здесь приводится упрощенная версия, полную версию см. в источнике):
С помощью данного кода выполняется установка контроллера и подключение обработчиков событий, затем вызов метода Print для создания страниц и очистка после выполнения задачи. При вызове метода Print документ начинает инициировать события. Обработчики событий PrintPage и EndPrint захватывают страницы сразу после построения и добавляют их во внутренний список изображений. Обработчики изображения также вызывают метод Application.DoEvents, чтобы диалоговое окно реагировало на действия пользователя в процессе построения документа. Это позволяет пользователям переключать страницы, настраивать масштаб просмотра или отменить процесс создания документа. Без вызова данного метода диалоговое окно перестало бы реагировать на действия пользователя до окончания преобразования документа целиком. Эти действия выполняются следующим кодом.
Это ядро кода просмотра. Остальная часть кода занимается такими рутинными задачами, как масштабирование изображений для предварительного просмотра, обновление линеек прокрутки, управление кнопками навигации, мышкой, клавиатурой и т.д. Для получения подробных сведений о реализации обратитесь к источнику кода. Обновление макета страницы Диалоговое окно предварительного просмотра позволяет пользователю обновить макет страницы. Благодаря классу PageSetupDialog в .NET это легко реализуемо. Ниже приводится код, который исполняется при нажатии пользователем кнопки "Page Layout" (макет страницы).
Данный код используется для отображения диалогового окна PageSetupDialog, в котором пользователь может изменить размер, ориентацию и поля страницы. Внесенные пользователем изменения отражаются в свойстве документа DefaultPageSettings. При нажатии пользователем кнопки "ОК" мы предполагаем, что макет страницы был изменен и вызываем метод RefreshPreview для элемента управления предварительного просмотра. Данный метод выполняет повторное создание всех изображений для предварительного просмотра с новыми настройками, а пользователь получает возможность увидеть изменения в полях, ориентации страницы и т.п. Печать документа При нажатии пользователем кнопки "Print" (печать) отображается диалоговое окноPrintDialog, где пользователь может выбрать принтер, диапазон страниц или отменить печать. К сожалению, выбор диапазона страницы не учитывается при простом вызове методаPrint напрямую для документа. Чтобы обойти это упущение, диалоговое окно вызывает метод Print для улучшенного элемента управления предварительного просмотра. Такая реализация использует изображения страниц, которые уже хранятся в элементе управления, и учитывает диапазоны страниц, определенные в свойствах документа PrinterSettings. Ниже приводится код, который вызывается при нажатии пользователем кнопки "Print" (печать).
Сначала метод Print определяет в элементе управления предварительного просмотра диапазон страниц, которые необходимо подготовить. Это может быть документ полностью, определенный диапазон страниц или текущий выбор (страницы в предварительном просмотре). После определения диапазона страниц код создает класс поддержки DocumentPrinter для запуска собственно печати.
Класс DocumentPrinter достаточно прост. Он наследует свойства у класса PrintDocument и перекрывает метод OnPrintPage для печати только тех страниц, которые выбраны пользователем.
Данная реализация выполняет преобразование изображений страниц, предполагая, что у всех страниц одинаковые размер и ориентация, что актуально для большинства документов. Если документ содержит страницы различного размера или с разной ориентацией, эта простая реализация будет работать некорректно. Чтобы исправить это, перед печатью каждой страницы необходимо проверить, что текущие размер и ориентация страницы соответствуют размеру изображения в предварительном просмотре и, если необходимо, вести изменения в настройки принтера. Читатели могут использовать это как упражнение. Экспорт в PDF Формат PDF чрезвычайно популярен, так как документы в этом формате небольшого размера и легко поддаются переноске. Документы в формате PDF можно публиковать в сети, распространять по электронной почте, просматривать и печатать практически в любом месте. После того, как выполнено преобразование PrintDocument в серию изображений, мы можем использовать компонент C1PdfDocument для преобразования изображений в документ PDF. С помощью данного подхода, в любое приложение, которое использует класс PrintDocument для обеспечения печати и предварительного просмотра, можно добавить функцию экспорта в PDF с минимальными усилиями. Первым действием при реализации экспорта в PDF будет реализация обработчика событий, который вызывается при нажатии пользователем кнопки экспорта в PDF в PrintPreviewDialog.
Код использует диалоговое окно SaveFileDialog, чтобы запросить у пользователя имя файла PDF для последующего сохранения, а такжеPrintDocumentPdfExporter для создания файла PDF из изображений для предварительного просмотра. Класс PrintDocumentPdfExporter может быть использован независимо от класса C1PrintPreviewDialog. Например, для создании кнопки для экспорта в PDF в основном приложении, которая вообще не вызывает диалоговое окно предварительного просмотра. Ниже показана реализация класса PrintDocumentPdfExporter.
Конструктор просто сохраняет ссылку на C1PdfDocument, который будет использован для создания PDF-файла. Основной общий метод в классе называется RenderDocument. Сначала метод присваивает документу класс PreviewPrintController, который выполняет преобразование страниц в изображения метафайлов. При этом может использоваться как простейший контроллер PreviewPrintController, так и оболочка PrintControllerWithStatusDialog, в зависимости от значения параметра showProgressDialog. Оба этих класса входят в платформу .NET. После установки контроллера код выполняет соединение обработчиков событий для событий PrintPage иEndPrint. Эти обработчики событий отвечают за преобразование изображений страниц в документ PDF. После установки обработчиков событий код вызывает методPrint для преобразования документа. Метод возвращает логическое значение, которое обозначает, было ли закончено создание документа (и его следует сохранить) или пользователь отменил процесс, нажав кнопку "Cancel" (отмена) в дополнительном диалоговом окне, отображающем ход работы.
Ниже перечислены обработчики событий для документа. По мере обработки страниц обработчики вызывают метод DrawPage для преобразования изображений страниц (метафайлов) в документ PDF. Это выполняется с помощью метода C1PdfDocument.DrawImage. В методе C1PdfDocument.DrawImage перечислены команды GDI в метафайле. Затем метод выполняет преобразование каждой команды в соответствующую векторную команду PDF. При этом не выполняется преобразование метафайла в растровое изображение, то есть его содержимое остается компактным и доступным для масштабирования и поиска.
Предварительный просмотр очень длинных документов После публикации первой версии проекта я получил много ценных комментариев от других пользователей CodeProject. В одном из них встречалась проблема, с которой я справился некоторое время назад. Если в документе несколько тысяч страниц могут возникать проблемы при кэшировании образов всех этих страниц. ОС Windows налагает ограничение в 10 000 объектов GDI, а каждый образ представляет собой по крайней мере один такой объект. Если вы используете слишком много объектов GDI, приложение может завершиться с ошибкой или вызвать ошибку других программ. Что не очень хорошо... Самым простым способом решения этой проблемы становится конвертация изображений страниц в потоки. Затем вы можете хранить потоки и создавать изображения по запросу, только когда они необходимы для предварительного просмотра или печати. Код ниже представляет собой класс PageImageList, который выполняет данное задание. Его можно использовать как обычный классList за одним исключением: при получении или задании изображения оно будет автоматически преобразовано в или из массива байтов. Таким образом хранимые в списке изображения не являются объектами GDI и не приводят к исчерпанию ресурсов системы.
Следует принять во внимание, что метод Add избавляется от изображения после его сохранения. Обычно так не делается, так как тот, кто вызвал метод, является владельцем изображения и должен сам принимать решения об его удалении. Но именно в этом проекте мы можем заменить реализацию PageImageList обычным классом List, что удобно для тестирования и определения производительности. Также следует помнить, чтоGetBytes использует интерфейс API GetHenhMetaFileBits. Данный API приводит к появлению неверного файла, поэтому изображение не может быть использовано после вызова данного метода. Чтобы избежать разрушения изначального файла, метод сначала создает его клон, а затем получает биты из клона. Если вас волнуют проблемы производительности, дополнительные конвертации приводят к снижению производительности примерно на 10% при генерации документа. Я полагаю, что это достаточно небольшая цена за те преимущества, которые вы получаете при обработке документов из нескольких сотен или тысяч страниц. Если вы озабочены использованием памяти, рассмотрите возможность архивации массивов байтов при хранении. Архивацию легко выполнить методом C1Zip, а метафайлы вообще хорошо поддаются сжатию. Конечно, при этом производительность еще немного снизится. Заключение Мы выполнили реализацию улучшенного класса PrintPreviewDialog, который обеспечивает вывод PDF в дополнение к стандартным возможностям печати и предварительного просмотра. Если у вас есть запросы или предложения в отношении улучшения данного документа или приложения, вы можете всегда опубликовать их на сайте. |