Печать

84

На первый взгляд графика и печать кажутся совершенно разными темами, однако на самом деле это не так. Между этими процедурами больше сходств, чем отличий. И действительно, в обоих случаях надстройка Silverlight должна передать изображение текста, фигур и элементов другому устройству, в первом случае — драйверу монитора, а во втором — драйверу принтера.

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

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

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

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

Печать одного элемента

Любая печать начинается с класса PrintDocument, расположенного в пространстве имен System.Windows.Printing. Чтобы начать печать, нужно создать экземпляр этого класса, подключить обработчик к его событию PrintPage и вызвать метод Print():

PrintDocument document = new PrintDocument(); 
document.PrintPage += documentImage_PrintPage;
document.Print ("Image Document");

Методу Print() передается имя документа. Пользователь не видит его в приложении, но может увидеть в окне очереди печати, в котором он может приостановить или отменить печать.

При вызове метода Print() приложение отображает стандартное диалоговое окно "Печать", в котором пользователь может выбрать принтер и отредактировать параметры печати. Если пользователь щелкнет в этом окне на кнопке Печать, выполнение приложения будет продолжено, и оно сгенерирует событие PrintDocument.PrintPage в фоновом потоке.

В обработчике события PrintPage обычно используются свойства объекта PrintPageEventArgs, перечисленные в таблице ниже. Понимание этих свойств — ключ к правильному использованию модели печати Silverlight:

Свойства объекта PrintPageEventArgs
Свойство Описание
PrintableArea Возвращает объект Size, содержащий значения Height и Width доступной области печати, в которую можно выводить содержимое. Если содержимое выходит за пределы области, оно отсекается
PageMargin Возвращает объект Thickness, содержащий параметры Тор, Bottom, Left и Right внешних полей текущей страницы
PageVisual Устанавливает визуальный элемент, который нужно распечатать на этой страницы. Им может быть простой элемент (например, TextBlock или Image) или более сложный контейнер (например, Grid или Canvas). Для получения лучших результатов визуальный элемент должен существовать только в памяти, но не в окне на экране
HasMorePages Определяет, будет ли печать остановлена после вывода текущей страницы (значение false) или будет продолжена (значение true)

В процессе печати объект PrintDocument генерирует ряд событий. Сначала генерируется событие BeginPrint (в нем удобно выполнять инициализацию и предварительные расчеты), затем — событие PrintPage (в это время выполняется фактическая печать) и наконец — событие EndPrint (в нем можно выполнить очистку буфера печати). В большинстве приложений обрабатывается только событие PrintPage.

Событие PrintPage генерируется по одному разу для каждой страницы. В рассматриваемом примере печатается одна страница, поэтому событие PrintPage генерируется один раз. Его обработчик выводит на печать определенный элемент страницы (используется показанный ранее пример использования PlaneProjection, где transformGrid - элемент Grid, содержащий трансформированные элементы):

private void documentImage_PrintPage(object sender, PrintPageEventArgs e)
{
        // Задание печатаемого элемента
        e.PageVisual = transformGrid;

        // Событие не должно повториться
        e.HasMorePages = false;
}

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

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

Печать на многих страницах

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

В следующем примере код печатает содержимое списка. Для этого код должен пройти в цикле по коллекции, отслеживая позицию печати и запрашивая новую страницу, когда текущая заполняется.

Код начала печати практически тот же, что и в предыдущем примере. Единственное отличие состоит в том, что теперь нужно отслеживать позицию в списке по мере перехода от одной страницы к другой:

<Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <ListBox x:Name="printListBox">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" Margin="10,4">
                        <TextBlock Text="Это строка с номером" FontSize="14"/>
                        <TextBlock Text="{Binding}" FontSize="14" Margin="5,0"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Content="Print" Click="Print_Click_1" Grid.Row="2"/>
</Grid>
public partial class MainPage : UserControl
{
        private int listPrintIndex;

        public MainPage()
        {
            InitializeComponent();
            // Заполняем ListBox
            IEnumerable<int> nums = Enumerable.Range(1, 40);
            printListBox.ItemsSource = nums;
        }

        private void Print_Click_1(object sender, RoutedEventArgs e)
        {
            // Переустановка позиции
            listPrintIndex = 0;
            PrintDocument document = new PrintDocument();
            document.PrintPage += documentImage_PrintPage;
            document.Print("Image Document");
        }

        // Добавление внешнего поля
        private int extraMargin = 50;

        private void documentImage_PrintPage(object sender, PrintPageEventArgs e)
        {
            // Использование объекта Canvas в качестве поверхности для печати
            Canvas printCanvas = new Canvas();
            e.PageVisual = printCanvas;

            // Поиск начальных координат
            double topPosition = e.PageMargins.Top + extraMargin;
            // Проход по списку в цикле
            while (listPrintIndex < printListBox.Items.Count)
            {
                // Создание объекта TextBlock для каждой строки
                TextBlock txt = new TextBlock();
                txt.FontSize = 30;
                txt.Text = "Это строка с номером " + 
                    printListBox.Items[listPrintIndex].ToString();

                // Если очередная строка не помещается на странице запрашиваем новую
                double measuredHeight = txt.ActualHeight;
                if (measuredHeight > (e.PrintableArea.Height - topPosition - extraMargin))
                {
                    e.HasMorePages = true;
                    return;
                }

                // Помещаем TextBlock в Canvas
                txt.SetValue(Canvas.TopProperty, topPosition);
                txt.SetValue(Canvas.LeftProperty, e.PageMargins.Left + extraMargin);
                printCanvas.Children.Add(txt);

                listPrintIndex++;
                topPosition = topPosition + measuredHeight;
            }

            // Код печати дошел до конца списка, больше страниц не нужно
            e.HasMorePages = false;
        }
}
Распечатка списка

Данный пример довольно простой, но из него видно, что при усложнении содержимого или когда необходимо распечатать текст с переносами либо смешанное содержимое (например, рисунки и текст), логика печати может стать очень сложной.

Пройди тесты
Лучший чат для C# программистов