Печать рисунка в WinRT

92

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

Чтобы свести к минимуму изменения готового кода FingerPaint, я решил определить производный от PrintDocument класс с именем BitmapPrintDocument. Ранее я уже упоминал о такой возможности, и хотя полученный класс не имеет переопределяемых методов, он упрощает некоторые ссылки на объекты:

public MainPage()
{
    // ...

    // Создание экземпляра класса, производного от PrintDocument,
    // для процесса печати
    new BitmapPrintDocument(() => { return bitmap; });
}

Обратите внимание на странный аргумент конструктора BitmapPrintDocument! Проблема в том, что классу BitmapPrintDocument определенно необходима ссылка на поле WriteableBitmap с именем bitmap, если он собирается выводить на печать это изображение, но его нельзя просто взять и передать конструктору BitmapPrintDocument. Поле bitmap изменяется каждый раз, когда программа загружает изображение из файла или буфера обмена или создает новый «холст». По этой причине я определил конструктор BitmapPrintDocument с параметром типа Func<BitmapSource>, чтобы каждый раз, когда экземпляру BitmapPrintDocument потребуется текущее изображение, он мог просто обратиться с обратным вызовом к MainPage.

BitmapPrintDocument сохраняет этот аргумент в поле и выполняет стандартную инициализацию:

using System;
using Windows.Graphics.Printing;
using Windows.Graphics.Printing.OptionDetails;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Printing;

namespace FingerPaint
{
    public class BitmapPrintDocument : PrintDocument
    {
        Func<BitmapSource> getBitmap;
        IPrintDocumentSource printDocumentSource;

        // Печатаемый элемент
        Border border = new Border
        {
            Child = new Image()
        };

        public BitmapPrintDocument(Func<BitmapSource> getBitmap)
        {
            this.getBitmap = getBitmap;

            // Получение объекта IPrintDocumentSource и регистрация
            // обработчиков событий
            printDocumentSource = this.DocumentSource;
            this.Paginate += OnPaginate;
            this.GetPreviewPage += OnGetPreviewPage;
            this.AddPages += OnAddPages;

            // Прикрепить обработчик PrintManager
            PrintManager.GetForCurrentView().PrintTaskRequested += 
                                                OnPrintDocumentPrintTaskRequested;
        }

        // ...
    }
}

Обработчик PrintTaskRequested - первое место, в котором возникает необходимость в обращении к изображению, потому что исходная ориентация страницы печати выбирается в соответствии с ориентацией изображения:

// ...

namespace FingerPaint
{
    public class BitmapPrintDocument : PrintDocument
    {
        // ...

        private async void OnPrintDocumentPrintTaskRequested(PrintManager sender, 
            PrintTaskRequestedEventArgs e)
        {
            PrintTaskRequestedDeferral deferral = e.Request.GetDeferral();

            // Получение PrintTask
            PrintTask printTask = e.Request.CreatePrintTask("Finger Paint", 
                                                               OnPrintTaskSourceRequested);

            // Возможный выбор альбомной ориентации
            PrintTaskOptionDetails optionDetails = 
                PrintTaskOptionDetails.GetFromPrintTaskOptions(printTask.Options);

            PrintOrientationOptionDetails orientation = 
                optionDetails.Options[StandardPrintTaskOptions.Orientation] as 
                                                            PrintOrientationOptionDetails;

            bool bitmapIsLandscape = false;

            await border.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                {
                    BitmapSource bitmapSource = getBitmap();
                    bitmapIsLandscape = bitmapSource.PixelWidth > bitmapSource.PixelHeight;
                });

            orientation.TrySetValue(bitmapIsLandscape ? PrintOrientation.Landscape : 
                                                        PrintOrientation.Portrait);

            deferral.Complete();
        }
        
        // ...
    }
}

Обратите внимание на необходимость использования объекта CoreDispatcher для обращения к растровому изображению в потоке пользовательского интерфейса. Вероятно, другие обработчики событий выглядят достаточно знакомо, не считая того, что ссылки на методы PrintDocument стали обычными ссылками на методы this:

// ...

namespace FingerPaint
{
    public class BitmapPrintDocument : PrintDocument
    {
        // ...

        private void OnPrintTaskSourceRequested(PrintTaskSourceRequestedArgs e)
        {
            e.SetSource(printDocumentSource);
        }

        private void OnPaginate(object sender, PaginateEventArgs e)
        {
            PrintPageDescription pageDesc = e.PrintTaskOptions.GetPageDescription(0);

            // Получение изображения
            (border.Child as Image).Source = getBitmap();

            // Задать отступы для Border
            double left = pageDesc.ImageableRect.Left;
            double top = pageDesc.ImageableRect.Top;
            double right = pageDesc.PageSize.Width - left - pageDesc.ImageableRect.Width;
            double bottom = pageDesc.PageSize.Height - top - pageDesc.ImageableRect.Height;
            border.Padding = new Thickness(left, top, right, bottom);
            
            this.SetPreviewPageCount(1, PreviewPageCountType.Final);
        }

        private void OnGetPreviewPage(object sender, GetPreviewPageEventArgs e)
        {
            this.SetPreviewPage(e.PageNumber, border);            
        }

        private void OnAddPages(object sender, AddPagesEventArgs e)
        {
            this.AddPage(border);
            this.AddPagesComplete();
        }
    }
}    
Пройди тесты
Лучший чат для C# программистов