Статья описывает исходный код приложения C# Windows Forms печати текстовых файлов. Функциональность приложения включает выбор файла, принтера, настройки параметров печатаемой страницы, предварительный просмотр печати, изменение вида и цвета шрифта. Если в вашей системе Windows нет установленных принтеров печать можно осуществлять в файл pdf.
Для настроек и выполнения печати применены стандартные диалоговые окна Windows: PrintPreviewDialog, PrintDialog, PageSetupDialog. Для выбора шрифта и его цвета используются FontDialog и ColorDialog соответственно. Полный комплект данных для печати между диалогами сохраняется в экземпляре класса PrintDocument и поэтому он определен как глобальный на уровне класса.
Глобальные поля на уровне класса:
public partial class Form1 : Form
{
// переменная доступа к выбранному для печати файла.
private StreamReader? _printFile = null;
// Хранение настроек печатаемого документа.
private readonly PrintDocument _printDocument = new();
// Временное хранение пути к файлу выбранному для печати.
private string? _fileToPrint = null;
// Шрифт по умолчанию для печати
private Font _printFont = new("Arial", 10);
// Цветовая кисть по умолчанию для шрифта
private readonly SolidBrush _printColor = new(Color.Black);
...
}
Печать в C# Windows Forms представляет собой знакомую процедуру рисования в экземпляре класса Graphics, но с выводом нарисованного на устройство принтер. В отличие от вывода на экран, при формировании печати дополнительно необходимо писать программный код деления графики на страницы.
Для каждого вида файлов пишется отдельный программный код. Для текстовых файлов, для документов Word, Excel, PDF, для изображений и т. п. программные коды вывода на принтер заметно отличаются.
Кроме того, координаты расположения контента на экране и на бумаге принтера, как правило, не совпадают. Чтобы правильно расположить элементы графики на странице необходимо согласованное использование свойства Graphics.PageUnit и вычисления координат, размеров печатаемых элементов.
GraphicsUnit.Display - определяет единицу измерения 1/100 дюйма для принтеров. GraphicsUnit.Millimeter - определяет миллиметр в качестве единицы измерения. GraphicsUnit.Inch - задает дюйм в качестве единицы измерения. GraphicsUnit.Point - определяет точку для принтера, размером 1/72 дюйма, в качестве единицы измерения. GraphicsUnit.Document - указывает единицу измерения документа (1/300 дюйма) в качестве единицы измерения.
По умолчанию для печати Graphics.PageUnit содержит значение Display, определяющее единицу измерения 1/100 дюйма равную 254 мкм (называется калибр, применяется в США и Великобритании, калибр численно равен точке в старой русской системе мер).
Примечание. Работая с дюймами обязательно указывайте для чисел тип float 100.0f (но не 100), поскольку в ином случае возможна потеря точности расположения элементов на странице.
Краткий программный код согласования координат и размеров элементов для бумаги (страницы) А4 в зависимости от выбранной PageUnit, полный код смотрите ниже в событии PrintDocument.PrintPage:
graphics.PageUnit = GraphicsUnit.Display;
//graphics.PageUnit = GraphicsUnit.Point;
//graphics.PageUnit = GraphicsUnit.Millimeter;
//graphics.PageUnit = GraphicsUnit.Inch;
// Счетчик строк
int counterLines = 0;
// Строка текста
string? lineText = null;
float leftMargin = e.MarginBounds.Left;
float topMargin = e.MarginBounds.Top;
// Высота шрифта в выбранных единицах объекта graphics.
float fontHeight = _printFont.GetHeight(graphics);
// Количество возможных строк для одной страницы.
float linesPerPage = e.MarginBounds.Height / fontHeight;
Pen rectPen = new(Brushes.Red)
{
Alignment = System.Drawing.Drawing2D.PenAlignment.Inset
};
switch (graphics.PageUnit)
{
case GraphicsUnit.Display:
// Ширина пера 1мм
rectPen.Width = (1 * 100.0f) / 25.4f;
graphics.DrawRectangle(
rectPen,
0, 0,
8.267716535433071f * 100.0f, 11.69291338582677f * 100.0f);
break;
case GraphicsUnit.Point:
// Ширина пера 1мм
rectPen.Width = (1 * 72.0f) / 25.4f;
linesPerPage =
(e.MarginBounds.Height * (72.0f / 100.0f)) / fontHeight;
leftMargin = e.MarginBounds.Left * (72.0f / 100.0f);
topMargin = e.MarginBounds.Top * (72.0f / 100.0f);
graphics.DrawRectangle(
rectPen,
0, 0,
8.267716535433071f * 72.0f, 11.69291338582677f * 72.0f);
break;
case GraphicsUnit.Millimeter:
// Ширина пера 1мм
rectPen.Width = 1;
linesPerPage =
(e.MarginBounds.Height * (25.4f / 100.0f)) / fontHeight;
leftMargin = e.MarginBounds.Left * (25.4f / 100.0f);
topMargin = e.MarginBounds.Top * (25.4f / 100.0f);
graphics.DrawRectangle(rectPen, 0, 0, 210, 297);
break;
case GraphicsUnit.Inch:
// Ширина пера 1мм
rectPen.Width = 1 / 25.4f;
linesPerPage =
(e.MarginBounds.Height / 100.0f) / fontHeight;
leftMargin = e.MarginBounds.Left / 100.0f;
topMargin = e.MarginBounds.Top / 100.0f;
graphics.DrawRectangle(
rectPen,
0, 0,
8.267716535433071f, 11.69291338582677f);
break;
}
// Печать последовательно всех строк файла на одной странице.
while (counterLines < linesPerPage &&
((lineText = _printFile.ReadLine()) != null))
{
// Координата позиции строки по высоте от верхнего края страницы.
float yPos = topMargin + (counterLines * fontHeight);
//
graphics.DrawString(
lineText,
_printFont,
_printColor,
leftMargin,
yPos
);
//
counterLines++;
}
Для облегчения программирования печати C# Windows Forms предоставляет высокоуровневый класс PrintDocument, хранящий в себе все параметры печати и способный распечатать документ на принтере по умолчанию. PrintDocument имеет в своем составе события, генерирующиеся перед печатью, при отправке страницы на печать и после окончания печати последней страницы.
События PrintDocument.BeginPrint и PrintDocument.EndPrint возникают в начале и в конце печати. BeginPrint и EndPrint предназначены для подготовки ресурсов (например, файла) для печати и освобождение ресурсов после печати соответственно.
Событие PrintDocument.PrintPage возникает при отправке каждой страницы на печать. В обработчике события PrintPage(object sender, PrintPageEventArgs e) происходит подготовка контента для печати на одну страницу, в случае если контент не поместился на одну страницу, в обработчике обеспечивается оповещение PrintPageEventArgs.HasMorePages=true о необходимости печати следующей страницы.
Ниже программный код обработчиков событий BeginPrint, PrintPage, EndPrint приложения печати текстовых файлов. Если выбирать различные единицы измерения для Graphics.PageUnit, логика соответствующего кода скорректирует координаты и размеры. Для большей наглядности по периметру страницы, для всех единиц измерения, печатается красная рамка толщиной в 1 мм.
// Вызывается перед печатью страниц.
private void PrintDocument_BeginPrint(object sender, PrintEventArgs e)
{
// Перед печатью открываем выбранный файл.
if (_fileToPrint != null)
{
_printFile = new StreamReader(
_fileToPrint,
Encoding.GetEncoding(comboBox1.Text));
}
else e.Cancel = true;
}
// Вызывается после окончания печати всех страниц.
private void PrintDocument_EndPrint(object sender, PrintEventArgs e)
{
// Закрываем файл после окончания печати.
_printFile?.Close();
}
// Событие объекта класса PrintDocument,
// вызывается для рисования-печати каждой страницы.
private void PrintDocument_PrintPage(object sender, PrintPageEventArgs e)
{
if (_printFile != null && e.Graphics is Graphics graphics)
{
graphics.PageUnit = GraphicsUnit.Display;
//graphics.PageUnit = GraphicsUnit.Point;
//graphics.PageUnit = GraphicsUnit.Millimeter;
//graphics.PageUnit = GraphicsUnit.Inch;
// Счетчик строк
int counterLines = 0;
// Строка текста
string? lineText = null;
float leftMargin = e.MarginBounds.Left;
float topMargin = e.MarginBounds.Top;
// Высота шрифта в выбранных единицах объекта graphics.
float fontHeight = _printFont.GetHeight(graphics);
// Количество возможных строк для одной страницы.
float linesPerPage = e.MarginBounds.Height / fontHeight;
Pen rectPen = new(Brushes.Red)
{
Alignment = System.Drawing.Drawing2D.PenAlignment.Inset
};
switch (graphics.PageUnit)
{
case GraphicsUnit.Display:
// Ширина пера 1мм
rectPen.Width = (1 * 100.0f) / 25.4f;
graphics.DrawRectangle(
rectPen,
0, 0,
8.267716535433071f * 100.0f, 11.69291338582677f * 100.0f);
break;
case GraphicsUnit.Point:
// Ширина пера 1мм
rectPen.Width = (1 * 72.0f) / 25.4f;
linesPerPage =
(e.MarginBounds.Height * (72.0f / 100.0f)) / fontHeight;
leftMargin = e.MarginBounds.Left * (72.0f / 100.0f);
topMargin = e.MarginBounds.Top * (72.0f / 100.0f);
graphics.DrawRectangle(
rectPen,
0, 0,
8.267716535433071f * 72.0f, 11.69291338582677f * 72.0f);
break;
case GraphicsUnit.Millimeter:
// Ширина пера 1мм
rectPen.Width = 1;
linesPerPage =
(e.MarginBounds.Height * (25.4f / 100.0f)) / fontHeight;
leftMargin = e.MarginBounds.Left * (25.4f / 100.0f);
topMargin = e.MarginBounds.Top * (25.4f / 100.0f);
graphics.DrawRectangle(rectPen, 0, 0, 210, 297);
break;
case GraphicsUnit.Inch:
// Ширина пера 1мм
rectPen.Width = 1 / 25.4f;
linesPerPage =
(e.MarginBounds.Height / 100.0f) / fontHeight;
leftMargin = e.MarginBounds.Left / 100.0f;
topMargin = e.MarginBounds.Top / 100.0f;
graphics.DrawRectangle(
rectPen,
0, 0,
8.267716535433071f, 11.69291338582677f);
break;
}
// Печать последовательно всех строк файла на одной странице.
while (counterLines < linesPerPage &&
((lineText = _printFile.ReadLine()) != null))
{
// Координата позиции строки по высоте от верхнего края страницы.
float yPos = topMargin + (counterLines * fontHeight);
//
graphics.DrawString(
lineText,
_printFont,
_printColor,
leftMargin,
yPos
);
//
counterLines++;
}
// Если текст еще остался, печатаем следующую страницу.
if (lineText != null)
{
e.HasMorePages = true;
}
else
{
// Печать закончена
e.HasMorePages = false;
}
}
}
PrintPreviewDialog – диалоговое окно печати с предпросмотром будущих страниц. Имеет свойства стандартного диалогового окна, с отличительным программным свойством PrintDocument, по данным которого создается предпросмотр страниц в актуальных координатах и возможность отправки печати на принтер.
Примечание. Работая с предварительным просмотром печати в PrintPreviewDialog необходимо учитывать, что при открытии диалога происходит полноценная печать всех страниц в окно PrintPreviewDialog. Нажатие на кнопку печать – это уже повторная печать, и если не предусмотреть повторную инициализацию ресурсов печати, то в результате на принтере будет распечатываться пустая страница.
PrintDialog – диалог настроек принтера и печати: выбор принтера, установка диапазона печати, количество копий, сброс настроек и другое в зависимости от моделей принтеров.
PrintDialog наследует от CommonDialog возможность показа кнопки Справка и события нажатия данной кнопки. В обработчике события программистом пишется код формирования и вызова окна со справочной информацией.
Программный код исполнения печати из разных диалогов. Объект PrintDialog показан с обработчиком события вызова справки:
private void BtnPreviewPrint_Click(object sender, EventArgs e)
{
// Предварительный просмотр печати
PrintPreviewDialog dialog = new()
{
Document = _printDocument
};
dialog.ShowDialog();
}
private void BtnPrintDialog_Click(object sender, EventArgs e)
{
PrintDialog dialog = new()
{
Document = _printDocument,
// Показывать кнопку Справка
ShowHelp = true,
// Выбор печатаемых страниц
AllowSomePages = true,
// Вид окна печати в стиле Windows XP.
//UseEXDialog = true
};
// Определение обработчика вызова справки.
dialog.HelpRequest += PrintDialog_HelpRequest;
if (dialog.ShowDialog() == DialogResult.OK)
{
_printDocument.Print();
}
}
// Вызов окна справки для PrintDialog
private void PrintDialog_HelpRequest(object? sender, EventArgs e)
{
MessageBox.Show("Справка диалога печати!");
}
private void BtnPrintDocument_Click(object sender, EventArgs e)
{
_printDocument.Print();
}
Настройки параметров страницы, выбор шрифта печати и его цвета выделены в отдельный условный модуль. При изменении пользователем параметров страницы, данные сохраняются в объекте класса PrintDocument.
Программный код модуля настроек печати:
#region Настройка печати
private void BtnPageSetup_Click(object sender, EventArgs e)
{
PageSetupDialog dialog = new()
{
// В .NET Framework версии 2.0 и выше вы должны
// установить для этого свойства значение true
// если ваше приложение может использоваться в культурах,
// которые измеряют поля документа в миллиметрах.
EnableMetric = true,
Document = _printDocument
};
dialog.ShowDialog();
}
private void BtnSelectFont_Click(object sender, EventArgs e)
{
FontDialog dialog = new()
{
Font = _printFont
};
if (dialog.ShowDialog() == DialogResult.OK)
{
_printFont = dialog.Font;
}
}
private void BtnFontColor_Click(object sender, EventArgs e)
{
ColorDialog dialog = new();
if (dialog.ShowDialog() == DialogResult.OK)
{
_printColor.Color = dialog.Color;
}
}
#endregion
По умолчанию, на платформе .NET Core применяется кодировка UTF-8 для текстовых файлов. Кроме того, .NET Core, в отличие от .NET Framework, поддерживает небольшое количество кодировок. Класс CodePagesEncodingProvider расширяет количество доступных кодировок для .NET Core.
Для случаев распечатывания текстовых файлов со специфическими кодировками в описываемом приложении предусмотрено изменение кодировки чтения для выбранного файла.
Программный код определения доступа к дополнительным кодировкам:
public Form1()
{
InitializeComponent();
// Расширяет доступ к кодировкам, которые недоступны в .NET
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
// Заполнение комбинированного бокса доступными кодировками.
foreach (EncodingInfo en in Encoding.GetEncodings())
{
comboBox1.Items.Add(en.Name);
}
comboBox1.Sorted = true;
...
}
Статья может описывать не весь исходный код, но для случаев заинтересованного изучения приложения печати к статье прикрепляется архивный рабочий файл исходника. Исходный код приложения написан на языке C# в MS Visual Studio 2022, платформа .NET6, Windows Forms.