Создание Windows-приложений на основе Visual C#

         

Печать содержимого RichTextBox


В качестве исходного приложения, в котором будем реализовывать возможность печати, возьмем проект TextEditor, созданный нами во второй лекции. Как мы знаем, максимальный объем элемента TextBox ограничивается 64 Кб, для работы с многостраничными документами этого явно мало. Поэтому удаляем с формы элемент txtBox и на его место помещаем элемент RichTextBox, которому задаем имя rtbText. Свойству Dock устанавливаем значение Fill, кроме того, удаляем значение свойства Text. Изменяем и добавляем следующие пункты главного меню (рис. 6.1):

NameTextShortcut
mnuFile&Файл
mnuOpen&ОткрытьCtrlO
mnuSave&СохранитьCtrlS
menuItem1-
mnuPageSetupПара&метры страницы
mnuPrintPreviewПред&варительный просмотр
mnuPrint&ПечатьCtrlP


Рис. 6.1.  Пункты главного меню

Перетаскиваем на форму из окна ToolBox элементы управления: PrintDocument, PageSetupDialog, PrintPreviewDialog и PrintDialog. Подобно другим элементам диалогов, они отображаются на панели компонент в среде Visual Studio .NET. При печати формируется одна или несколько страниц, за которые отвечает объект PrintDocument. Элементы управления PageSetupDialog, PrintPreviewDialog и PrintDialog представляют собой диалоговые окна параметров страниц, предварительного просмотра и печати соответственно. Настройка свойств этих объектов представляет собой столь гибкую и широкую задачу, решаемую в коде приложения, что, в отличие, например, от элементов OpenFileDialog или SaveFileDialog, имеет смысл создавать их программно, в классе формы:

// PrintDocument printDocument1 = new PrintDocument(); // PageSetupDialog pageSetupDialog1 = new PageSetupDialog(); // PrintPreviewDialog printPreviewDialog1 = new PrintPreviewDialog(); // PrintDialog printDialog1 = new PrintDialog();

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

Свойству Document элементов управления PageSetupDialog и PrintPreviewDialog устанавливаем значение свойства Name элемента PrintDocument — printDocument1. Тем самым мы связываем объект printDocument1, отвечающий за формирование страниц печати, с диалоговыми окнами. Устнавливаем следующие свойства элемента PrintDialog:

printDialog1, свойствоЗначениеОписание
AllowSelectionTrueРазрешение на печать выделенного фрагмента документа
AllowSomePagesTrueРазрешение на печать нескольких страниц
PrintDocumentprintDocument1Связывание с экземпляром объекта PrintDocument


На информационной панели окна Properties при выборе свойств AllowSelection или AllowSomePages можно заметить сообщение – Enables and disables the Selection/Pages radio button (Включает или отключает доступность радиопереключателей "Выделенный фрагмент/Страницы"). Само по себе определение этих свойств еще не включает соответствующую функциональность — еще одна причина, по которой конфигурировать объекты печати лучше программно:

InitializeComponent(); // pageSetupDialog1.Document = printDocument1; // printPreviewDialog1.Document = printDocument1; // printDialog1.Document = printDocument1; // printDialog1.AllowSomePages = true; // printDialog1.AllowSelection = true;

Переходим в код формы. Для работы с печатью в библиотеке .NET Framework применяется класс System.Drawing.Printing, поэтому его нужно подключить в самом начале работы:

using System.Drawing.Printing;

В классе формы объявляем следующие переменные:

Листинг 6.1.

(html, txt)

Следует обратить внимание на переменную stringPrintText — именно она будет отвечать за передачу текста в объект печати. Поскольку мы определили для нее строковый тип, это означает, что мы не сможем вывести на печать рисунки в тексте — как мы знаем, элемент RichTextBox поддерживает их наличие (в этом можно убедиться, скопировав содержимое документа Microsoft Word в наше приложение). Определение полной печати будет более сложным, что, впрочем, не является существенным для понимания самой концепции печати11).

В конструкторе форме определяем диапазон страниц для печати:

Листинг 6.2.

(html, txt)

Максимальное количество страниц в диапазоне — 9999;, очевидно, что его вполне хватит для распечатки любого документа. Основным событием, в обработчике которого практически и будет осуществляться вся печать, будет событие PrintPage элемента printDocument1. Переключаемся в режим дизайна, выделяем элемент и, переключившись в окне Properties на вкладку событий, дважды щелкаем в поле этого события:





Листинг 6.3.

(html, txt)

Далее все описываемые фрагменты кода будут относиться к этому обработчику.

Класс Graphics, принадлежащий пространству имен System.Drawing, — один из важнейших классов, отвечающих за рисование графики и вывода текста на форме. При создании новой формы пространство имен System.Drawing по умолчанию включается в шаблон. Для формирования страницы печати создаем экземпляр этого класса:



Graphics graph = e.Graphics; // Создаем объект font, которому устанавливаем // шрифт элемента rtbText Font font = rtbText.Font; //Получаем значение межстрочного интервала — высоту шрифта float HeightFont = font.GetHeight(graph); //Создаем экземпляр strfmt класса StringFormat для определения //дополнительных параметров форматирования текста. StringFormat stringformat = new StringFormat();

Страница представляет собой прямоугольный объект, параметры которого следует определить. Для этого используется класс RectangleF (буква F указывает на использование типа данных с плавающей точкой float):

//Создаем экземляры rectanglefFull и rectfText класса RectangleF для //определения областей печати и текста. RectangleF rectanglefFull, rectfText; //Создаем переменные для подсчета числа символов и строк. int NumberSymbols, NumberLines;

Страница, формируемая для печати, состоит из трех областей (рис. 6.2).


увеличить изображение
Рис. 6.2.  Области страницы, выводимой на печать


Rectangle ( int x, int y, int width, int height);

RectangleF(float x, float y, float width, float height);

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


увеличить изображение
Рис. 6.3.  Расположение координатных осей и формирование объекта Rectangle

Вернемся к нашему коду. В качестве области печати устанавливаем объект rectanglefFull:

if (graph.VisibleClipBounds.X<0) rectanglefFull = e.MarginBounds; else //Определяем объект rectanglefFull rectanglefFull = new RectangleF( //Устанавливаем координату X e.MarginBounds.Left — (e.PageBounds.Width — graph.VisibleClipBounds.Width)/2, //Устанавливаем координату Y .MarginBounds.Top — (e.PageBounds.Height — graph.VisibleClipBounds.Height)/2, //Устанавливаем ширину области e.MarginBounds.Width, //Устанавливаем высоту области e.MarginBounds.Height);

Область текста будет представлять собой копию области rectanglefFull c учетом высоты шрифта:

rectanglefText = RectangleF.Inflate(rectanglefFull, 0, -2*HeightFont); //Определяем число строк int NumDisplayLines = (int)Math.Floor(rectanglefText.Height/HeightFont); //Устанавливаем высоту области rectanglefText.Height = NumDisplayLines*HeightFont;

Элемент RichTextBox обладает свойством WordWrap, при установке значения True которого текст автоматически переносится на новую строку. Однако разбитый на строки текст может выйти за границы предназначенной для него области rectanglefText. Чтобы этого не произошло, мы создали экземпляр stringformat класса StringFormat, для которого теперь устанавливаем значение Trimming:

if (rtbText.WordWrap) { stringformat.Trimming = StringTrimming.Word; } else { stringformat.Trimming = StringTrimming.EllipsisCharacter; stringformat.FormatFlags |=StringFormatFlags.NoWrap; }

Далее код достаточно простой, привожу его с комментариями:

//При печати выбранных страниц переходим к первой стартовой странице while ((PageNumber<StartPage)&&(stringPrintText.Length>0)) { if(rtbText.WordWrap) //Измеряем текстовые переменные, //формирующие печать, и возвращаем число символов NumberSymbols //и число строк NumberLines graph.MeasureString(stringPrintText, font, rectanglefText.Size, stringformat, out NumberSymbols, out NumberLines); else NumberSymbols = SymbolsInLines(stringPrintText, NumDisplayLines); stringPrintText = stringPrintText.Substring(NumberSymbols); //Увеличиваем число страниц PageNumber++; } //Если длина строки stringPrintText равняется нулю (нет текста для печати), // останавливаем печать if (stringPrintText.Length==0) { e.Cancel = true; return; } //Выводим (рисуем) текст для печати — stringPrintText, используем для этого шрифт font, //кисть черного цвета — Brushes.Black, область печати — rectanglefText, //передаем строку дополнительного форматирования stringformat graph.DrawString(stringPrintText, font, Brushes.Black, rectanglefText, stringformat); //Получаем текст для следующей страницы if (rtbText.WordWrap) graph.MeasureString(stringPrintText, font, rectanglefText.Size, stringformat, out NumberSymbols, out NumberLines); else NumberSymbols = SymbolsInLines(stringPrintText, NumDisplayLines); stringPrintText = stringPrintText.Substring(NumberSymbols); //Очищаем объект stringformat, использованный для формирования полей. stringformat = new StringFormat(); //Добавляем вывод на каждую страницу ее номера stringformat.Alignment = StringAlignment.Far; graph.DrawString("Страница" + PageNumber, font, Brushes.Black, rectanglefFull, stringformat); PageNumber++; //Cнова проверяем, имеется ли текст для печати и номер страницы, заданной для печати e.HasMorePages=(stringPrintText.Length>0)&&(PageNumber<StartPage+NumPages); //Для печати из окна предварительного просмотра снова инициализируем переменные if(!e.HasMorePages) { stringPrintText = rtbText.Text; StartPage = 1; NumPages = printDialog1.PrinterSettings.MaximumPage; PageNumber = 1; }



}

Листинг 6.4.

Мы закончили рассмотрение метода printDocument1_PrintPage. Достаточно существенным моментом здесь является то, что в коде реализована проверка наличия расстановки переносов в текстовом поле RichTextBox (свойство WordWrap). Метод MeasureString возвращает число выводимых символов. Для учета символов, не попадающих в область rectanglefText, добавляем метод SymbolsInLines:

int SymbolsInLines(string stringPrintText, int NumLines) { int index = 0; for (int i = 0; i< NumLines; i++) { index = 1+ stringPrintText.IndexOf('\n', index); if(index==0) return stringPrintText.Length; } return index; }

Создаем обработчики событий пунктов mnuPageSetup и mnuPrintPreview главного меню:

private void mnuPageSetup_Click(object sender, System.EventArgs e) { //Показываем диалог pageSetupDialog1.ShowDialog(); } private void mnuPrintPreview_Click(object sender, System.EventArgs e) { //Инициализируем переменные printDocument1.DocumentName = Text; stringPrintText = rtbText.Text; StartPage = 1; NumPages = printDialog1.PrinterSettings.MaximumPage; PageNumber = 1; //Показываем диалог printPreviewDialog1.ShowDialog(); }

В обработчике пункта меню mnuPrint определяем диапазон страниц – все страницы, ряд или выделенная область:

private void mnuPrint_Click(object sender, System.EventArgs e) { printDialog1.AllowSelection = rtbText.SelectionLength >0; if(printDialog1.ShowDialog()==DialogResult.OK) { printDocument1.DocumentName =Text; //Определяем диапазон страниц для печати switch(printDialog1.PrinterSettings.PrintRange) { //Выбраны все страницы case PrintRange.AllPages: stringPrintText = rtbText.Text; StartPage = 1; NumPages = printDialog1.PrinterSettings.MaximumPage; break; //Выбрана выделенная область case PrintRange.Selection: stringPrintText = rtbText.SelectedText; StartPage = 1; NumPages = printDialog1.PrinterSettings.MaximumPage; break; //Выбран ряд страниц case PrintRange.SomePages: stringPrintText = rtbText.Text; StartPage = printDialog1.PrinterSettings.FromPage; NumPages = printDialog1.PrinterSettings.ToPage - StartPage+1; break; } PageNumber = 1; //Вызываем встроенный метод для начала печати printDocument1.Print(); }



}

Листинг 6.5.

Запускаем приложение. Изменения, сделанные в диалоговом окне "Параметры страницы" (рис. 6.4), сохраняются, и в окне предварительного просмотра можно видеть подготовленную для печати страницу (рис. 6.5).


Рис. 6.4.  Настройка параметров страницы


увеличить изображение
Рис. 6.5.  Окно предварительного просмотра. На каждой странице выводится ее номер

Из окна предварительного просмотра можно сразу перейти к печати. При выборе пункта печати доступен выбор ряда страниц или печать выделенного фрагмента (рис. 6.6).


Рис. 6.6.  Настройка параметров печати

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


увеличить изображение
Рис. 6.7.  Вывод документа в окно виртуального принтера программы pdfFactoryPro

В конце лекции приводится полный листинг приложения TextEditor, а исходный проект имеется на диске, прилагаемом к книге (Code\Glava6\TextEditor).


Содержание раздела