Перейти к содержимому

Printdialog что это за программа

  • автор:

2.5. Диалоговое окно PrintDialog и PrintPreviewDialog.

Добавьте в форму компонент PrintDialogиз окна с инструментами. Измените его свойствоNameнаprintDialog, а свойствоDocument — наprintDocument.

Создайте обработчик событий «щелчок мышью» для меню Print. Добавьте в него следующий код:

private void printToolStripMenuItem_Click(object sender, EventArgs e)

Теперь, в результате нажатия на пункт меню Print будет появлятся стандартое диалоговое окно печати.

Добавьте в программу разработки Windows Forms компонент PrintPreviewDialog. Присвойте ему имяPreviewDialog, а свойствуDocument— значениеprintDocument. Добавьте и реализуйте обработчик событияclickдля вхождения меню Print Preview

private void printPToolStripMenuItem_Click(object sender, EventArgs e)

2.6. Использование диалоговых окон FontDialog и ColorDialog.

Данное диалоговое окно может использоваться точно так же, как и все предыдущие. В программе разработки Windows Forms это окно можно перенести из окна с инструментами и поместить в форму таким образом, что это приведет к созданию экземпляра FontDialog.

Код, предназначенный для использования FontDialog, может выглядеть следующим образом:

if (fontDialog.ShowDialog() == DialogResult.OK)

Этот код можно поместить в обработчик нажатия на пункт меню.

Диалоговое окно FontDialogвыводится с помощью методаShowDialog(). Если пользователь завершает диалог нажатием кнопки ОК, то этим методом возвращается значениеDialogResult.ОК. Выбранный пользователем шрифт можно определить, воспользовавшись свойствомFontклассаFontDialog; в дальнейшем этот шрифт передается свойствуFontклассаTextbox.

Диалоговое окно выбора цветов ColorDialogможет быть перенесено мышью из окна с инструментами и помещено в программу разработки Windows Forms точно так же, как и другие. МетодShowDialog()будет выводить этот диалог на экран до тех пор, пока не будет нажата кнопка ОК или кнопка Cancel. Узнать, какой цвет выбран пользователем, можно, считав значение свойства color данного диалога, как это продемонстрировано в следующем примере:

if (colorDialog.ShowDialog() == DialogResult.OK)

Этот код можно поместить в обработчик нажатия на пункт меню.

Этим мы примером завершаем рассмотрение стандартных диалоговых окон. Приложение SimpleEditorготово и по своей функциональности приближается к блокноту.

3. Задания

1. Создайте программу, в которой при выборе пункта меню «Калькулятор», появляется диалоговое окно, аналогичное стандартному калькулятору Windows/

2. Создайте программу, в которой при выборе пункта меню появляется диалоговое окно, показанное на рис. 7.15. В соответствии с выбранными радиокнопками при нажатии ОК в рабочей области главного окна программы должно быть нарисован эллипс указанного цвета, возможно закрашенный.

Рис. 7. 15. Диалоговое окно выбора цвета эллипса.

3. Написать программу Simple Editor

4. Предусмотрите возможность для пользователя изменять шрифт текстового окна. Для реализации такой возможности введите новое вхождение основного меню — F&ormat, и субменю Format — &Font. Включите в приложение диалоговое окно FontDialog. Выведите это окно с помощью обработчика событий меню и присвойте выбранный шрифт в качестве значения свойству Font текстового окна. Вам также придется внести изменения в реализацию метода onPrintPage (), которые позволят использовать выбранный шрифт при выводе на печать. В предшествующей реализации нами был создан новый объект Font В методе DrawString() объекта Graphics. В данном случае мы вместо этого используем шрифт объекта textBox1, считывая значение свойства Font. Также необходимо учитывать возможность возникновения проблем, если пользователь выбирает слишком большой шрифт. Чтобы избежать ситуацию, при которой текущая строка будет частично налагаться на другую строку, расположенную ниже или выше, следует внести соответствующие изменения в фиксированное значение, которое использовалось для определения позиции строк по вертикали. Наилучшим способом будет использование в качестве величины, на которую увеличивается вертикальная координата строки, значение размера шрифта: исспользуйте свойство Height класса Font.

5. Включите в приложение возможности изменять цвет шрифта. Добавьте второе подменю во вхождение меню Format — Color. . Добавьте обработчик для данного вхождения меню, в котором осуществляется открытие диалогового окна ColorDialog. Если пользователь нажимает кнопку ОК, присвойте выбранный в окне ColorDialog цвет свойству ForeColor текстового окна. В методе onPrintPage() убедитесь в том, что выбранный цвет используется только в том случае, если принтер поддерживает цветную печать. Проверить, поддерживает ли принтер цветную печать, можно с помощью свойства PageSettings. color аргумента PrintPageEventArgs. Можно создать объект типа кисти того цвета, который используется в текстовом окне, с помощью следующего кода: Brush brush = new SolidBrush(textBoxEdit.ForeColor);Эта кисть может использоваться в качестве аргумента метода DrawString() вместо черной кисти, применявшейся в предыдущем примере.

Обзор компонента PrintDialog (Windows Forms .NET)

Печать в Windows Forms состоит в основном из использования PrintDocument компонента для печати пользователем. Элемент PrintPreviewDialog управления PrintDialog и PageSetupDialog компоненты предоставляют знакомый графический интерфейс для пользователей операционной системы Windows.

Компонент PrintDialog — это предварительно настроенное диалоговое окно, используемое для выбора принтера, выбора страниц для печати и определения других параметров, связанных с печатью, в приложениях под управлением Windows. Это простое решение для параметров принтера и печати вместо настройки собственного диалогового окна. Пользователям можно предоставить различные варианты печати документов: печать всех страниц, выбранного диапазона страниц или выделенного фрагмента текста. Использование стандартных диалоговых окон Windows помогает создавать приложения, основные функциональные возможности которых хорошо знакомы пользователям. Компонент PrintDialog наследует от класса CommonDialog.

Как правило, создается новый экземпляр PrintDocument компонента и задаются свойства, описывающие печать с помощью PrinterSettings и PageSettings классов. Print Вызов метода фактически выводит документ.

Работа с компонентом

Используйте метод PrintDialog.ShowDialog, чтобы отобразить диалоговое окно во время выполнения. Этот компонент имеет свойства, относящиеся либо к одному заданию печати (класс PrintDocument), либо к параметрам отдельного принтера (класс PrinterSettings). Один из двух, в свою очередь, может совместно использоваться несколькими принтерами.

Метод диалогового окна «Показать» помогает добавить диалоговое окно печати в форму. Компонент PrintDialog отображается в области в нижней части конструктора Windows Forms в Visual Studio.

Как записать входные данные пользователя из PrintDialog во время выполнения

Параметры, связанные с печатью во время разработки, можно задать. Иногда может потребоваться изменить эти параметры во время выполнения, скорее всего, из-за выбора, сделанного пользователем. Данные, введенные пользователем для печати документа, можно захватывать с помощью компонентов PrintDialog и PrintDocument. Ниже показано, как отобразить диалоговое окно печати для документа:

  1. Добавьте в форму компоненты PrintDialog и PrintDocument.
  2. Присвойте свойству Document объекта PrintDialog значение PrintDocument, которое было добавлено в форму.

PrintDialog1.Document = PrintDocument1 
printDialog1.Document = printDocument1; 
If PrintDialog1.ShowDialog() = DialogResult.OK Then PrintDocument1.Print() End If 
// display show dialog and if user selects "Ok" document is printed if (printDialog1.ShowDialog() == DialogResult.OK) printDocument1.Print(); 

Создание заданий печати

Краеугольным камнем процессов печати в Windows Forms является компонент PrintDocument, а если точнее, то событие PrintPage. Написав код для обработки события PrintPage, можно указать, что необходимо напечатать и как. Ниже показано создание задания печати.

  1. Добавьте в форму компонент PrintDocument.
  2. Напишите код для обработки события PrintPage . Вам придется закодировать собственную логику печати. Кроме того, вам придется указать материал для печати. В следующем примере кода в качестве материала в виде красного прямоугольника создается пример рисунка в обработчике PrintPage событий.

Private Sub PrintDocument1_PrintPage(ByVal sender As Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage e.Graphics.FillRectangle(Brushes.Red, New Rectangle(100, 100, 100, 100)) End Sub 
private void PrintDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e) => e.Graphics.FillRectangle(Brushes.Red, new Rectangle(100, 100, 100, 100)); 

Вы также можете написать код для BeginPrint событий и EndPrint событий. Это поможет включить целое число, представляющее общее количество страниц для печати, которое уменьшается по мере печати каждой страницы.

Вы можете добавить компонент в форму PrintDialog, чтобы предоставить пользователям чистый и удобный пользовательский интерфейс. Document Задание свойства PrintDialog компонента позволяет задать свойства, связанные с документом печати, с которым вы работаете в форме.

Дополнительные сведения о специфике заданий печати Windows Forms, в том числе о том, как создать задание печати программным способом, см. в разделе PrintPageEventArgs.

Выполнение заданий печати

Чаще всего текстовые процессоры и другие приложения, связанные с печатью, предоставляют возможность отображения сообщения пользователям о завершении задания печати. Эту функцию можно предоставить в Windows Forms, обрабатывая событие EndPrint компонента PrintDocument.

Для выполнения следующей процедуры необходимо создать приложение на основе Windows с компонентом PrintDocument . Приведенная ниже процедура — это стандартный способ включения печати из приложения на основе Windows. Дополнительные сведения о печати из Windows Forms с помощью PrintDocument компонента см. в статье «Создание заданий печати».

    Установите свойство DocumentName элемента управления PrintDocument.

PrintDocument1.DocumentName = "SamplePrintApp" 
printDocument1.DocumentName = "SamplePrintApp"; 
Private Sub PrintDocument1_EndPrint(ByVal sender As Object, ByVal e As System.Drawing.Printing.PrintEventArgs) Handles PrintDocument1.EndPrint MessageBox.Show(PrintDocument1.DocumentName + " has finished printing.") End Sub 
private void PrintDocument1_EndPrint(object sender, System.Drawing.Printing.PrintEventArgs e) => MessageBox.Show(printDocument1.DocumentName + " has finished printing."); 

Совместная работа с нами на GitHub

Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.

PrintDialog и PrinterSetupDialog – диалоги печати и установки принтера

Запись была обновлена

Компонент PrintDialog вызывает диалоговое окно печати. В нем пользователь может выбрать принтер и установить его свойства, указать число копий и последовательность их печати, печатать в файл или непосредственно на принтер, выбрать печатаемые страницы или печатать только выделенный фрагмент.

Компонент PrintDialog не осуществляет печать. Он только позволяет пользователю задать атрибуты печати. А сама печать должна осуществляться программно с помощью объекта Printer или иным путем.

Компонент PrintDialog возвращает ряд свойств, характеризующих выбранные пользователем установки. Это следующие свойства:

Название Значение
PrintRange Показывает выбранную пользователем радиокнопку из группы Печатать:
prAllPages — выбрана кнопка Все страницы
prSelection — выбрана кнопка Страницы с . по .
prPageNums — выбрана кнопка Страницы
FromPage Показывает установленную пользователем начальную страницу в окне Страницы с . по .
ToPage Показывает установленную пользователем конечную страницу в окне Страницы с . по .
PrintToFile Показывает, выбран ли пользователем индикатор Печать в файл
Copies Показывает установленное пользователем число копий
Collate Показывает, выбран ли пользователем индикатор Разобрать

Перед вызовом диалога желательно определить, сколько страниц в печатаемом тексте, и задать параметры MaxPage и MinPage — максимальный и минимальный номера страниц. В противном случае пользователю в диалоговом окне не будет доступна кнопка «Страницы с . по . ». Кроме того, следует определить множество опций в свойстве Options:

Название Значение
poDisablePrintToFile Запретить доступ к индикатору Печать в файл. Эта опция работает только при включенной опции poPrintToFile
poHelp Отображать в диалоговом окне кнопку Справка. Опция может не работать для некоторых версий Windows 95/98
poPageNums Сделать доступной радиокнопку Страницы, позволяющую пользователю задавать диапазон печатаемых страниц
poPrintToFile Отображать в диалоговом окне кнопку Печать в файл
poSelection Сделать доступной кнопку Выделение, позволяющую пользователю печатать только выделенный текст
poWarning Выдавать замечания, если пользователь пытается послать задачу на неустановленный принтер

Компоненте PrinterSetupDialog вызывает диалоговое окно установки принтера. Это единственный диалоговый компонент, не имеющий никаких специфических свойств, которые надо было бы устанавливать или читать. Диалог выполняет операции по установке принтера, на котором будет производиться печать и задании его свойств. Этот диалог не возвращает никаких параметров.

Печать

Хотя WPF включает в себя десятки классов, имеющих отношение к печати (большинство из которых находятся в пространстве имен System.Printing), существует одна начальная точка, которая призвана облегчить жизнь: класс PrintDialog.

PrintDialog заключает в себе знакомое диалоговое окно Print, которое позволяет пользователю выбрать принтер и установить несколько стандартных настроек печати, такие как количество копий. Однако класс PrintDialog — это нечто больше, чем просто симпатичное окно; он также обладает встроенной способностью инициировать вывод на печать:

Print Dialog

Чтобы отправить задание на печать посредством класса PrintDialog, необходимо воспользоваться одним из двух описанных ниже методов:

PrintVisual()

Работает с любым классом, унаследованным от System.Windows.Media.Visual. Сюда относится любая графика, нарисованная вручную, и любой элемент, помещенный в окно.

PrintDocument()

Работает с любым объектом DocumentPaginator. Сюда относятся те, что используются для разбиения на страницы FlowDocument (или XpsDocument), и все специальные объекты DocumentPaginator, которые создаются для работы с данными.

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

Печать элемента

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

Чтобы увидеть пример в действии, взгляните на окно, показанное на рисунке ниже. Оно содержит контейнер Grid, в котором располагаются все элементы. В верхней строке находится контейнер Canvas, содержащий в себе элементы TextBlock и Path (которые визуализируются как прямоугольник с эллиптическим отверстием в центре):

Простой рисунок

Отправить Canvas вместе со всем содержимым на принтер можно с помощью следующего фрагмента кода, выполняемого по щелчку на кнопке «Печать»:

PrintDialog printDialog = new PrintDialog(); if (printDialog.ShowDialog() == true)

Первый шаг предусматривает создание объекта PrintDialog. Следующий шаг — вызов ShowDialog() для отображения диалогового окна Print. Метод ShowDialog() возвращает булевское значение. Возврат true указывает на то, что пользователь щелкнул на кнопке OK, false — на кнопке Cancel (Отмена), а значение null означает, что диалоговое окно было просто закрыто без щелчка на какой-либо кнопке.

При вызове методу PrintVisual() передаются два аргумента. Первый — это элемент, который необходимо напечатать, а второй — строка, используемая для идентификации задания печати. Она будет отображаться в очереди печати Windows (в столбце Document Name (Имя документа)). При печати подобным образом вывод особо не управляется. Элемент всегда располагается в левом верхнем углу страницы. Если элемент не включает ненулевых значений Margin, то граница содержимого может оказаться в непечатаемой области страницы и, следовательно, просто не будет распечатана.

Недостаток контроля над полями — лишь первое из ограничений этого подхода. Кроме того, невозможно разбить длинное содержимое на страницы, поэтому если окажется, что оно не уместилось на одну страницу, оно будет просто потеряно за нижней ее гранью. Наконец, нельзя управлять масштабированием, используемым при визуализации задания для печати. Вместо этого WPF использует некоторую независимую от устройства систему визуализации, основанную на единицах величиной в 1/96 дюйма. Например, прямоугольник шириной в 96 единиц на мониторе будет иметь 1 дюйм в ширину (при условии, что применяется стандартная установка Windows в 96 dpi) и тот же 1 дюйм на печатной странице. Часто это меньше, чем требуется.

Очевидно, что WPF выведет намного больше деталей на печатную страницу, потому что вряд ли найдется принтер со столь малым разрешением, как 96 dpi (намного более распространены разрешения принтеров в 600 dpi и 1200 dpi). Однако WPF будет поддерживать на печатной копии тот же размер содержимого, что и на мониторе.

На следующем рисунке показан полностраничный печатный вывод Canvas из окна:

Напечатанный элемент

Класс PrintDialog скрывает в себе низкоуровневый внутренний класс .NET по имени Win32PrintDialog, который, в свою очередь, заключает в себе диалоговое окно Print, представленное API-интерфейсом Win32. К сожалению, эти дополнительные уровни лишают гибкости.

Потенциальная проблема заключена в способе, которым класс PrintDialog работает с модальными окнами. В недоступном коде Win32PrintDialog имеется логика, которая всегда делает диалоговое окно Print модальным по отношению к главному окну приложения. Это приводит к странной проблеме, когда отображается модальное окно из главного окна и затем из него вызывается метод PrintDialog.ShowDialog(). Хотя ожидается, что диалоговое окно Print будет модальным по отношению ко второму окну, на самом деле оно модально по отношению к главному окну, а это значит, что пользователь может вернуться ко второму окну и взаимодействовать с ним (даже щелкать на кнопке Print, открывая несколько экземпляров диалогового окна Print)!

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

Существует и другое ограничение, связанное с работой класса PrintDialog. Поскольку главный поток приложения владеет печатаемым содержимым, невозможно запустить печать в фоновом потоке. Это становится заметно, если выполнение логики печати занимает некоторое время.

Есть два возможных решения. Когда визуальные компоненты, которые должны быть напечатаны, конструируются в фоновом потоке (а не извлекаются из существующего окна), то в фоновом потоке можно запустить и печать. Однако более простое решение предусматривает использование диалогового окна PrintDialog для того, чтобы позволить пользователю указать настройки печати, и затем с помощью класса XpsDocumentWriter выполнение действительной печати содержимого вместо вызова методов печати класса PrintDialog. Класс XpsDocumentWriter поддерживает возможность отправки содержимого на принтер асинхронно.

Трансформация печатного вывода

Возможно, вы помните, что за счет присоединения объекта Transform к свойству RenderTransform или LayoutTransform любого элемента можно изменить способ его визуализации. Объекты Transform помогают решить проблему негибкого вывода на печать, потому что с их помощью можно изменять размеры элемента (ScaleTransform), перемещать его по странице (TranslateTransform) либо делать то и другое (TransformGroup).

К сожалению, визуальные компоненты могут размещать себя только одним способом в каждый конкретный момент времени. Это значит, что масштабировать элемент одним способом в окне, а другим — в выводе на печать не удастся. Вместо этого любой объект Transform, который будет применен, изменит и печатный вывод, и экранное представление элемента.

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

Может показаться, что для сокрытия элемента следует изменить его свойство Visibility, но это скроет его как в окне, так и в печатном выводе, что очевидно не подходит. Возможное решение состоит в изменении Visibility родительского элемента (в данном примере — контейнера компоновки Grid). Это работает, потому что метод PrintVisual() учитывает только указанный элемент и его дочерние элементы, а не родительский.

Ниже приведен код для вывода на печать элемента Canvas, показанного ранее, который имеет впятеро большие размеры:

PrintDialog printDialog = new PrintDialog(); if (printDialog.ShowDialog() == true) < // Скрыть Grid grid.Visibility = Visibility.Hidden; // Увеличить вывод в 5 раз canvas.LayoutTransform = new ScaleTransform(5, 5); // Напечатать элемент printDialog.PrintVisual(canvas, "Распечатываем элемент Canvas"); // Удалить трансформацию и снова сделать элемент видимым canvas.LayoutTransform = null; grid.Visibility = Visibility.Visible; >

В этом примере не хватает одной детали. Хотя Canvas (и его содержимое) растягивается, контейнер Canvas все равно использует информацию о компоновке из включающего его Grid. Другими словами, Canvas все равно полагает, что в его распоряжении лишь столько пространства, сколько отводит ему ячейка Grid, в которую он помещен.

В рассматриваемом примере это не представляет проблемы, потому что Canvas не ограничивает себя доступным пространством (в отличие от некоторых других контейнеров). Однако проблемы возникнут в ситуации, когда есть текст, который должен заворачиваться в рамках печатной страницы, или если в Canvas используется фон (который в данном примере будет иметь меньшие размеры ячейки Grid, а не размеры всей области под Canvas).

Решить эту проблему просто. После установки LayoutTransform (но перед печатью Canvas) понадобится инициировать процесс компоновки вручную, используя методы Measure() и Arrange(), наследуемые каждым элементом от класса UIElement. Трюк состоит в том, что при вызове этим методам передаются размеры страницы, и контейнер Canvas растягивается, чтобы заполнить страницу. (Кстати, поэтому устанавливается свойство LayoutTransform, а не RenderTransform, т.к. нужно, чтобы компоновка принимала во внимание новый растянутый размер.) Для получения размеров страницы служат свойства PrintableAreaWidth и PrintableAreaHeight.

На основе имен свойств имеет смысл предположить, что PrintableAreaWidth и PrintableAreaHeight отражают печатаемую область страницы, другими словами — часть страницы, которую принтер действительно печатает. (Большинство принтеров не могут печатать очень близко к границам листа, обычно из-за устройства подающих роликов.) Но в действительности PrintableAreaWidth и PrintableAreaHeight просто возвращают полную ширину и высоту страницы в независимых от устройства единицах. Для листа бумаги А4 это будет 816 и 1056. (Разделив эти числа на 96 dpi, можно получить полный размер листа в дюймах.)

В следующем примере демонстрируется использование свойств PrintableAreaWidth и PrintableAreaHeight. Для красоты у границ страницы остаются поля шириной 10 единиц (около 0,1 дюйма):

PrintDialog printDialog = new PrintDialog(); if (printDialog.ShowDialog() == true) < // Скрыть Grid grid.Visibility = Visibility.Hidden; // Увеличить размер в 5 раз canvas.LayoutTransform = new ScaleTransform(5, 5); // Определить поля int pageMargin = 5; // Получить размер страницы Size pageSize = new Size(printDialog.PrintableAreaWidth - pageMargin * 2, printDialog.PrintableAreaHeight - 20); // Инициировать установку размера элемента canvas.Measure(pageSize); canvas.Arrange(new Rect(pageMargin, pageMargin, pageSize.Width, pageSize.Height)); // Напечатать элемент printDialog.PrintVisual(canvas, "Распечатываем элемент Canvas"); // Удалить трансформацию и снова сделать элемент видимым canvas.LayoutTransform = null; grid.Visibility = Visibility.Visible; >

Конечным результатом будет печать любого элемента и масштабирование в соответствии с потребностями, как показано на рисунке ниже. Этот подход работает исключительно хорошо, однако в его основе лежит несколько запутанный механизм.

Масштабированный печатный элемент

Печать элементов без их отображения

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

PrintDialog printDialog = new PrintDialog(); if (printDialog.ShowDialog() == true) < // Создать текст Run run = new Run ("Это простой текст, тестируем функциональность " + "печати в Windows Presentation Foundation."); // Поместить его в TextBlock TextBlock visual = new TextBlock(); visual.Inlines.Add(run); // Использовать поля для получения рамки страницы visual.Margin = new Thickness(5); // Разрешить перенос для заполнения всей ширины страницы visual.TextWrapping = TextWrapping.Wrap; // Увеличить TextBlock по обоим измерениям в 5 раз. // (В этом случае увеличение шрифта дало бы тот же эффект, // потому что TextBlock — единственный элемент) visual.LayoutTransform = new ScaleTransform(5, 5); // Установить размер элемента Size pageSize = new Size (printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight); visual.Measure(pageSize); visual.Arrange(new Rect(0, 0, pageSize.Width, pageSize.Height)); // Напечатать элемент printDialog.PrintVisual(visual, "Распечатываем текст"); >

Многострочный текст в TextBlock

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

Печать документа

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

Можно было предположить, что метод PrintDialog.PrintDocument() требует объект FlowDocument, но на самом деле он принимает объект DocumentPaginator. Класс DocumentPaginator — это специализированный класс, единственное назначение которого — брать содержимое, разбивать его на множество страниц и поставлять каждую страницу по мере поступления запросов. Каждая страница представлена объектом DocumentPage, который на самом деле — просто оболочка для единственного объекта Visual с несколькими удобными средствами. В классе DocumentPage доступны только три дополнительных свойства: Size возвращает размер страницы, ContentBox — размер прямоугольника, в который помещается содержимое на странице после добавления полей, a BleedBox — область, куда помещаются торговые марки, водяные знаки и авторские логотипы, находящаяся вне границ страницы.

Это значит, что PrintDocument() работает почти так же, как PrintVisual(). Отличие в том, что он печатает несколько визуальных компонентов — по одному на каждую страницу.

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

Каким же образом получить объект DocumentPaginator для FlowDocument? Трюк заключается в приведении FlowDocument к IDocumentPaginatorSource с последующим использованием DocumentPaginator. Вот пример:

PrintDialog printDialog = new PrintDialog(); if (printDialog.ShowDialog() == true)

Этот код может привести к желаемому результату, а может и не привести, в зависимости от контейнера, в котором в данный момент находится документ. Если документ расположен в памяти (а не в окне) или если он хранится в RichTextBox либо FlowDocumentScrollViewer, этот код работает хорошо. Будет получен многостраничный печатный вывод с двумя столбцами (на стандартном листе формата А4 в портретной ориентации). Это тот же результат, который получается с помощью команды ApplicationCommands.Print.

Как известно, некоторые элементы управления включают встроенную привязку команд. Контейнер FlowDocument (подобно использованному ранее FlowDocumentScrollViewer) входит в число таких элементов. Он обрабатывает команду ApplicationCommands.Print для выполнения базовой печати. Этот жесткий код печати подобен коду, показанному ранее, хотя использует объект XpsDocumentWriter.

Однако если документ хранится в FlowDocumentPageViewer или FlowDocumentReader, результат не так хорош. В этом случае документ разбивается на страницы так же, как текущее представление в контейнере. Таким образом, если для того, чтобы уместить содержимое в текущее окно, понадобится 24 страницы, то получатся именно 24 страницы печатного вывода, каждая с крошечным окошком с данными. Опять-таки, решение несколько запутано, но оно работает. (По сути — это то же решение, которое использует команда ApplicationCommands.Print.) Трюк состоит в том, чтобы заставить FlowDocument разбивать себя на страницы. Это можно сделать, установив свойства FlowDocument.PageHeight и FlowDocument.PageWidth в границы страницы, а не границы контейнера.

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

PrintDialog printDialog = new PrintDialog(); if (printDialog.ShowDialog() == true)

Может также понадобиться установить такие свойства, как ColumnWidth и ColumnGap, чтобы получить нужное количество столбцов. В противном случае их будет столько же, как в текущем окне.

Единственная проблема этого подхода в том, что как только эти свойства изменены, они применятся к контейнеру, который отображает документ. В результате получается сжатая версия документа, которая, скорее всего, будет слишком мала, чтобы его можно было прочитать в текущем окне. Правильное решение учитывает это, сохраняя значения, изменяя их и затем восстанавливая в исходном виде.

Ниже приведен полный код, печатающий двухстолбцовый вывод с общими полями (добавленными через свойство FlowDocument.PagePadding):

PrintDialog printDialog = new PrintDialog(); if (printDialog.ShowDialog() == true) < FlowDocument doc = docViewer.Document; // Сохранить все имеющиеся настройки double pageHeight = doc.PageHeight; double pageWidth = doc.PageWidth; Thickness pagePadding = doc.PagePadding; double columnGap = doc.ColumnGap; double columnWidth = doc.ColumnWidth; // Привести страницу FlowDocument в соответствие с печатной страницей doc.PageHeight = printDialog.PrintableAreaHeight; doc.PageWidth = printDialog.PrintableAreaWidth; doc.PagePadding = new Thickness(0); // Использовать два столбца doc.ColumnGap = 25; doc.ColumnWidth = (doc.PageWidth - doc.ColumnGap - doc.PagePadding.Left - doc.PagePadding.Right) / 2; printDialog.PrintDocument( ((IDocumentPaginatorSource)doc).DocumentPaginator, "A Flow Document"); // Восстановить старые настройки doc.PageHeight = pageHeight; doc.PageWidth = pageWidth; doc.PagePadding = pagePadding; doc.ColumnGap = columnGap; doc.ColumnWidth = columnWidth; >>

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

Печать документа

Манипуляции страницами в печатном выводе документа

Чтобы обрести больший контроль над печатью FlowDocument, можно создать собственный DocumentPaginator. Класс DocumentPaginator разделяет содержимое документа на отдельные страницы для вывода на печать (или отображения в средстве постраничного просмотра FlowDocument). Класс DocumentPaginator отвечает за возврат общего количества страниц на основе заданного размера страницы и предоставляет скомпонованное содержимое для каждой страницы в виде объекта DocumentPage.

Объект DocumentPaginator не должен быть очень сложным: фактически он может просто упаковывать DocumentPaginator, который предоставлен FlowDocument, и позволяет ему выполнять всю необходимую работу по разбиению текста на страницы. Однако DocumentPaginator можно использовать для внесения небольших поправок, таких как добавление заголовка и нижнего колонтитула.

Основной фокус состоит в перехвате каждого запроса страницы, который осуществляет PrintDialog, с последующим изменением этой страницы перед передачей дальше.

Первой частью этого решения является построение класса HeaderFlowDocumentPaginator, унаследованного от DocumentPaginator. Поскольку DocumentPaginator — абстрактный класс, HeaderFlowDocumentPaginator должен реализовать несколько методов. Тем не менее, HeaderFlowDocumentPaginator может передать большую часть работы стандартному DocumentPaginator, который предоставляется экземпляром FlowDocument:

public class HeaderedFlowDocumentPaginator : DocumentPaginator < // Реальный класс разбиения на страницы (выполняющий всю работу по разбиению) private DocumentPaginator flowDocumentPaginator; // Сохранить класс разбиения на страницы FlowDocument из заданного документа public HeaderedFlowDocumentPaginator(FlowDocument document) < flowDocumentPaginator = ((IDocumentPaginatorSource)document).DocumentPaginator; >public override bool IsPageCountValid < get < return flowDocumentPaginator.IsPageCountValid; >> public override int PageCount < get < return flowDocumentPaginator.PageCount; >> public override Size PageSize < get < return flowDocumentPaginator.PageSize; >set < flowDocumentPaginator.PageSize = value; >> public override IDocumentPaginatorSource Source < get < return flowDocumentPaginator.Source; >> public override DocumentPage GetPage(int pageNumber) < // . >>

Поскольку HeaderedFlowDocumentPaginator передает свою работу приватному DocumentPaginator, код не показывает, как работают свойства PageSize, PageCount и IsPageCountValid. Свойство PageSize устанавливается потребителем DocumentPaginator (кодом, использующим DocumentPaginator). Это свойство сообщает DocumentPaginator, сколько еще места доступно на каждой печатаемой странице (или на экране). Свойства PageCount и IsPageCountValid предоставляются потребителю DocumentPaginator для отображения результата разбиения на страницы. При каждом изменении PageSize объект DocumentPaginator заново вычисляет размер каждой страницы.

Метод GetPage() — место, где происходит действие. Это код вызывает метод GetPage() реального объекта DocumentPaginator и затем выполняет работу над страницей. Базовая стратегия состоит в извлечении объекта Visual из страницы и помещении его в новый объект ContainerVisual. К этому ContainerVisual можно впоследствии добавить нужный текст. Наконец, допускается создать новый класс DocumentPage, который упаковывает ContainerVisual с вновь вставленным заголовком:

public override DocumentPage GetPage(int pageNumber) < // Получить запрошенную страницу DocumentPage page = flowDocumentPaginator.GetPage(pageNumber); // Поместить страницу в объект Visual. После этого можно // будет применять трансформации и добавлять другие элементы ContainerVisual newVisual = new ContainerVisual(); newVisual.Children.Add(page.Visual); // Создать заголовок DrawingVisual header = new DrawingVisual(); using (DrawingContext dc = header.RenderOpen()) < Typeface typeface = new Typeface("Times New Roman"); FormattedText text = new FormattedText("Страница " + (pageNumber + 1) .ToString(), System.Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 14, Brushes.Black); // Оставить четверть дюйма пространства между краем страницы и текстом dc.DrawText(text, new Point(96*0.25, 96*0.25)); >// Добавить заголовок к объекту Visual newVisual.Children.Add(header); // Поместить объект Visual в новую страницу DocumentPage newPage = new DocumentPage(newVisual); return newPage; >

В этой реализации предполагается, что добавление заголовка не приводит к изменению размеров страницы. Вместо этого предполагается, что на полях есть достаточно места, чтобы вместить заголовок. Если этот код применить с небольшими полями, заголовок будет напечатан поверх содержимого документа. Именно так работают заголовки в таких программах, как Microsoft Word. Они не считаются частью главного документа и позиционируются отдельно от главного содержимого документа.

Здесь присутствует небольшой нюанс. Добавить объект Visual для страницы к ContainerVisual не удастся до тех пор, пока он отображается в окне. Обходной путь предусматривает его временное удаление из контейнера, выполнение печати и последующий возврат объекта на место:

PrintDialog printDialog = new PrintDialog(); if (printDialog.ShowDialog() == true)

Объект HeaderedFlowDocumentPaginator используется для печати, но не присоединен к FlowDocument, так что он не сможет изменить способ отображения документа на экране:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *