Пользовательские свойства печати в WinRT

53

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

Это можно сделать без лишних хлопот с настройкой панели параметров принтера. Windows Runtime выполнит основную работу по созданию необходимых элементов управления и управлению вводом. Местом для выполнения этой настройки является обработчик события PrintTaskRequested объекта PrintManager. До настоящего момента этот обработчик выглядел так:

private void OnPrintManagerPrintTaskRequested(PrintManager sender, 
            PrintTaskRequestedEventArgs e)
{
    e.Request.CreatePrintTask("Иерархия свойств зависимости", OnPrintTaskSourceRequested);
}

Или с использованием анонимной лямбда-функции для обратного вызова:

private void OnPrintManagerPrintTaskRequested(PrintManager sender, 
    PrintTaskRequestedEventArgs e)
{
    e.Request.CreatePrintTask("Иерархия свойств зависимости", (requestArgs) =>
    {
        requestArgs.SetSource(printDocumentSource);
    });
}

Какой бы способ вы ни выбрали, вызов CreatePrintTask возвращает объект типа PrintTask, который можно сохранить в локальной переменной:

private void OnPrintManagerPrintTaskRequested(PrintManager sender, 
    PrintTaskRequestedEventArgs e)
{
    PrintTask printTask = e.Request.CreatePrintTask("Иерархия свойств зависимости", (requestArgs) =>
    {
        requestArgs.SetSource(printDocumentSource);
    });
}

По этому объекту PrintTask можно получить объект типа PrintTaskOptionDetails; для этой цели используется обходной статический вызов:

PrintTaskOptionDetails optionDetails = 
                PrintTaskOptionDetails.GetFromPrintTaskOptions(printTask.Options);

PrintTaskOptionDetails и сопутствующие классы определяются в пространстве имен Windows.Graphics.Printing.OptionDetails.

При желании вы можете убрать все настройки с первой страницы панели конфигурации принтера:

optionDetails.DisplayedOptions.Clear();

Теперь вы не увидите поля для изменения количества копий или ориентации. При желании их можно поместить обратно - скажем, в обратном порядке:

optionDetails.DisplayedOptions.Add(StandardPrintTaskOptions.Orientation);
optionDetails.DisplayedOptions.Add(StandardPrintTaskOptions.Copies);

StandardPrintTaskOptions - статический класс, а его свойства представляют стандартные параметры конфигурации принтера, ассоциируемые со строковыми идентификаторами. Так, свойство StandardPrintTaskOptions.Orientation содержит строку «PageOrientation», a StandardPrintTaskOptions.Copies - строку «JobCopiesAll- Documents». Эти свойства можно инициализировать значениями, подходящими для вашей программы:

optionDetails.Options[StandardPrintTaskOptions.Orientation].TrySetValue
                (PrintOrientation.Landscape);

PrintOrientation - одно из одиннадцати аналогичных перечислений в Windows.Graphics.Printing. Вы можете добавить менее распространенный параметр, если полагаете, что он подходит для вашего приложения:

optionDetails.DisplayedOptions.Add(StandardPrintTaskOptions.Collation);

Также возможно добавление пользовательских параметров. Поддерживаются два типа таких параметров: текстовое поле или раскрывающийся список (как Orientation).

Давайте создадим новый проект с именем CustomizableClassHierarchy. Эта программа в целом похожа на PrintableClassHierarchy, но в ней определяются некоторые дополнительные параметры печати. Поля этих параметров инициализируются значениями, которые программа считает наиболее подходящими:

public sealed partial class MainPage : Page
{
    // ...

    // Исходные значения пользовательских параметров принтера
    double fontSize = new TextBlock().FontSize;
    double leftMargin = 96;     // 1 дюйм
    double topMargin = 72;      // 3/4 дюйма
    double rightMargin = 96;
    double bottomMargin = 72;
    
    // ...
}

Эти поля используются обработчиком события PrintTaskRequested объекта PrintManager. Вспомните, что это событие инициируется при нажатии пользователем чудо-кнопки "Устройства" - вероятно, в процессе выбора принтера:

private void OnPrintManagerPrintTaskRequested(PrintManager sender, 
            PrintTaskRequestedEventArgs e)
{
    PrintTask printTask = e.Request.CreatePrintTask("Иерархия свойств зависимости", (requestArgs) =>
    {
        requestArgs.SetSource(printDocumentSource);
    });

    PrintTaskOptionDetails optionDetails =
            PrintTaskOptionDetails.GetFromPrintTaskOptions(printTask.Options);

    // Добавление параметра для размера шрифта
    optionDetails.CreateTextOption("idFontSize", "Размер шрифта (pt)");
    optionDetails.DisplayedOptions.Add("idFontSize");
    optionDetails.Options["idFontSize"].TrySetValue((72 * fontSize / 96).ToString());

    // Добавление параметров для полей страницы
    optionDetails.CreateTextOption("idLeftMargin", "Левый отступ (в дюймах)");
    optionDetails.DisplayedOptions.Add("idLeftMargin");
    optionDetails.Options["idLeftMargin"].TrySetValue((leftMargin / 96).ToString());

    optionDetails.CreateTextOption("idTopMargin", "Верхний отступ (в дюймах)");
    optionDetails.DisplayedOptions.Add("idTopMargin");
    optionDetails.Options["idTopMargin"].TrySetValue((topMargin / 96).ToString());

    optionDetails.CreateTextOption("idRightMargin", "Правый отступ (в дюймах)");
    optionDetails.DisplayedOptions.Add("idRightMargin");
    optionDetails.Options["idRightMargin"].TrySetValue((rightMargin / 96).ToString());

    optionDetails.CreateTextOption("idBottomMargin", "Нижний отступ (в дюймах)");
    optionDetails.DisplayedOptions.Add("idBottomMargin");
    optionDetails.Options["idBottomMargin"].TrySetValue((bottomMargin / 96).ToString());

    // Назначение обработчика для изменения параметров
    optionDetails.OptionChanged += OnOptionDetailsOptionChanged;
}

Каждый пользовательский параметр печати требует минимум двух, а возможно, и трех действий. Сначала его необходимо создать с назначением строкового идентификатора и метки, выводимой на панели конфигурации принтера. Затем созданный объект добавляется в коллекцию DisplayedOptions. Третий (необязательный) шаг задает начальное значение. В моем коде поля, в которых хранятся эти значения, преобразуются из пикселов в пункты (для размера шрифта) и дюймы (для размеров полей).

Метод завершается назначением обработчика события OptionChanged. Это событие инициируется для изменений во всех параметрах печати, не только в пользовательских. Для текстовых параметров, как в нашем случае, событие срабатывает не с каждым нажатием клавиши, а только при нажатии клавиши Enter, потере фокуса ввода или нажатии кнопки Print. Вот как выглядит измененная панель параметров:

Добавление параметров на панель печати

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

Ниже приведена реализация обработчика события OptionChanged. Именно здесь происходит проверка данных, и здесь вы сообщаете о необходимости обновления области предварительного просмотра с новыми настройками, что приводит к повторному вызову обработчика Paginate. PrintTaskOptionChangedEventArgs определяет всего одно свойство с именем OptionId типа object (хотя в действительности оно содержит строку), но вам также придется использовать аргумент sender. Это объект PrintTaskOptionDetails, который использовался при настройке поведения обработчика PrintTaskRequested:

private async void OnOptionDetailsOptionChanged(PrintTaskOptionDetails sender,
                                                PrintTaskOptionChangedEventArgs e)
{
    if (e.OptionId == null)
        return;

    string optionId = e.OptionId.ToString();
    string strValue = sender.Options[optionId].Value.ToString();
    string errorText = String.Empty;
    double value = 0;

    switch (optionId)
    {
        case "idFontSize":
            if (!Double.TryParse(strValue, out value))
                    errorText = "Значение должно быть числом";

            else if (value < 4 || value > 36)
                    errorText = "Значение должно быть в диапазоне от 4 до 36";
            break;

        case "idLeftMargin":
        case "idTopMargin":
        case "idRightMargin":
        case "idBottomMargin":
            if (!Double.TryParse(strValue, out value))
                    errorText = "Значение должно быть числом";

            else if (value < 0 || value > 2)
                    errorText = "Значение должно быть в диапазоне от 0 до 2";
            break;
    }

    sender.Options[optionId].ErrorText = errorText;

    // Если не было ошибки, запросить перерисовку области
    // предварительного просмотра
    if (String.IsNullOrEmpty(errorText))
    {
        await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            printDocument.InvalidatePreview();
        });
    }
}

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

Отображение ошибки в панели параметров печати

Обратите внимание на сдвиг всех элементов под сообщением об ошибке. Если заданное сообщение не помещается в одной строке, оно автоматически переносится.

Если ошибки во введенных данных не обнаружены, должен быть вызван метод InvalidatePreview объекта PrintDocument. Обратите внимание на необходимость использования CoreDispatcher для выполнения этого вызова в потоке пользовательского интерфейса. Этот обработчик OptionChanged выполняется во вторичном потоке.

Вызов InvalidatePreview приводит к инициированию события Paginate для PrintDocument. Эта новая версия обработчика Paginate начинается с получения всех введенных значений и преобразования их в числа. Размер шрифта применяется ко всем элементам TextBlock, сохраненным для печати, а размеры полей используются как в предыдущей версии этого метода:

private void OnPrintDocumentPaginate(object sender, PaginateEventArgs e)
{
    // Получение значений пользовательских параметров
    PrintTaskOptionDetails optionDetails = 
        PrintTaskOptionDetails.GetFromPrintTaskOptions(e.PrintTaskOptions);

    leftMargin = 96 * Double.Parse(optionDetails.Options["idLeftMargin"].Value.ToString());
    topMargin = 96 * Double.Parse(optionDetails.Options["idTopMargin"].Value.ToString());
    rightMargin = 96 * Double.Parse(optionDetails.Options["idRightMargin"].Value.ToString());
    bottomMargin = 96 * Double.Parse(optionDetails.Options["idBottomMargin"].Value.ToString());

    fontSize = 96 * Double.Parse(optionDetails.Options["idFontSize"].Value.ToString()) / 72;

    // Задание свойства FontSize хранимых элементов TextBlock
    foreach (TextBlock txtblk in printerTextBlocks)
        txtblk.FontSize = fontSize;
                
    // ...
}
Лучший чат для C# программистов