如何在 UWP 设备应用中管理打印作业

在 Windows 8.1 中,打印机的 UWP 设备应用可以管理打印作业。 本主题使用 C# 版本的打印作业管理和打印机维护示例来演示如何创建打印作业视图、监视这些作业以及在必要时取消作业。 要了解有关 UWP 设备应用的一般详细信息,请参阅初识 UWP 设备应用

C# 版本的打印作业管理和打印机维护示例演示了使用 DeviceAppForPrinters2 项目中 DeviceMaintenance.xaml.cs 文件的打印机维护。 为了使用 Bidi,该示例使用 PrinterExtensionLibrary 项目中的打印机扩展库。 打印机扩展库提供了一种访问 v4 打印驱动程序的打印机扩展接口的便捷方法。 有关详细信息,请参阅打印机扩展库概述

本主题中显示的代码示例基于 C# 版本的打印作业管理和打印机维护示例。 此示例还有 JavaScript 和 C++ 版本。 请注意,由于 C++ 可以直接访问 COM,因此该示例的 C++ 版本不包括代码库项目。 下载示例以查看最新版本的代码。

管理打印作业

Windows 8.1 在 v4 打印机驱动程序中引入了新的打印机扩展接口,你可以使用这些接口来管理打印作业:IPrinterQueue2IPrinterQueueViewIPrinterQueueViewEventIPrintJobIPrintJobCollection。 通过这些接口,可以监视和取消打印作业。 有关详细信息,请参阅打印作业管理(v4 打印机驱动程序)

C# 和 JavaScript 应用不能直接使用 COM API。 如果要编写 C# 或 JavaScript UWP 设备应用,请使用打印机扩展库访问这些接口(如本主题所示)。

先决条件

准备工作:

  1. 请确保使用 v4 打印驱动程序安装打印机。 有关详细信息,请参阅开发 v4 打印驱动程序

  2. 设置你的开发电脑。 有关下载工具和创建开发人员帐户的信息,请参阅入门

  3. 将应用与商店相关联。 请参阅创建 UWP 设备应用以获取相关信息。

  4. 为你的打印机创建设备元数据,将其与你的应用关联起来。 请参阅创建设备元数据以了解详细信息。

  5. 为你的应用的主页构建 UI。 所有 UWP 设备应用都可以从“开始”启动,并以全屏方式显示。 使用“开始”体验,以符合设备特定品牌和功能的方式突出你的产品或服务。 它可以使用的 UI 控件类型没有特殊限制。 要开始设计全屏体验,请参阅 Microsoft Store 设计原则

  6. 如果要使用 C# 或 JavaScript 编写应用,请将 PrinterExtensionLibrary 项目添加到你的 UWP 设备应用解决方案。 你可以在打印作业管理和打印机维护示例中找到此项目。

由于 C++ 可以直接访问 COM,因此 C++ 应用不需要单独的库来处理基于 COM 的打印机设备上下文。

步骤 1:查找打印机

在管理打印任务之前,你的应用必须先找到有打印任务的打印机。 为此,打印作业管理和打印机维护示例包括一个名为 PrinterEnumeration 的方便的类(在 PrinterEnumeration.cs 文件中)。 此类通过设备元数据查找与应用关联的所有打印机,并返回一个 PrinterInfo 对象列表,其中包含每个打印机的名称和设备 ID。

此示例显示 PrintJobManagement.xaml.cs 文件中的 EnumeratePrinters_Click 方法。 它演示示例如何使用 PrinterEnumeration 类获取关联的打印机列表。

private async void EnumeratePrinters_Click(object sender, RoutedEventArgs e)
{
    try
    {
        rootPage.NotifyUser("Enumerating printers. Please wait", NotifyType.StatusMessage);

        // Retrieve the running app's package family name, and enumerate associated printers.
        string currentPackageFamilyName = Windows.ApplicationModel.Package.Current.Id.FamilyName;

        // Enumerate associated printers.
        PrinterEnumeration pe = new PrinterEnumeration(currentPackageFamilyName);
        List<PrinterInfo> associatedPrinters = await pe.EnumeratePrintersAsync();

        // Update the data binding source on the combo box that displays the list of printers.
        PrinterComboBox.ItemsSource = associatedPrinters;
        if (associatedPrinters.Count > 0)
        {
            PrinterComboBox.SelectedIndex = 0;
            rootPage.NotifyUser(associatedPrinters.Count + " printers enumerated", NotifyType.StatusMessage);
        }
        else
        {
            rootPage.NotifyUser(DisplayStrings.NoPrintersEnumerated, NotifyType.ErrorMessage);
        }
    }
    catch (Exception exception)
    {
        rootPage.NotifyUser("Caught an exception: " + exception.Message, NotifyType.ErrorMessage);
    }
}

有关 PrinterEnumerationPrinterInfo 类的详细信息,请参阅 PrinterEnumeration.cs 文件。

步骤 2:获取打印机队列

确定打印机具有要管理的打印作业后,请使用基于 IPrinterQueueView 接口的对象(在 PrinterExtensionLibrary 项目的 PrinterExtensionTypes.cs 文件中定义)创建打印作业的“视图”。打印作业管理和打印机维护示例中,此对象被命名为 currentPrinterQueueView 并在每次打印机选择更改时重新创建。

Printer_SelectionChanged 方法中,示例首先使用对象 PrinterInfo 创建名为 context 的打印机扩展上下文对象。 然后,它使用 context 上的 GetPrinterQueueView 方法创建 currentPrinterQueueView 对象。 最后,添加事件处理程序来处理 currentPrinterQueueViewOnChanged 事件。

此示例显示 PrintJobManagement.xaml.cs 文件中的 Printer_SelectionChanged 方法。 它演示如何创建基于 IPrinterQueueView 的打印机队列视图对象。

private void Printer_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    try
    {
        // Remove the current printer queue view (if any) before displaying the new view.
        if (currentPrinterQueueView != null)
        {
            currentPrinterQueueView.OnChanged -= OnPrinterQueueViewChanged;
            currentPrinterQueueView = null;
        }

        // Retrieve a COM IPrinterExtensionContext object, using the static WinRT factory.
        // Then instantiate one "PrinterExtensionContext" object that allows operations on the COM object.
        PrinterInfo queue = (PrinterInfo)PrinterComboBox.SelectedItem;
        Object comContext = Windows.Devices.Printers.Extensions.PrintExtensionContext.FromDeviceId(queue.DeviceId);
        PrinterExtensionContext context = new PrinterExtensionContext(comContext);

        // Display the printer queue view.
        const int FirstPrintJobEnumerated = 0;
        const int LastPrintJobEnumerated = 10;

        currentPrinterQueueView = context.Queue.GetPrinterQueueView(FirstPrintJobEnumerated, LastPrintJobEnumerated);
        currentPrinterQueueView.OnChanged += OnPrinterQueueViewChanged;
    }
    catch (Exception exception)
    {
        rootPage.NotifyUser("Caught an exception: " + exception.Message, NotifyType.ErrorMessage);
    }
}

此外,每当打印作业视图发生更改时,事件处理程序都将调用 OnPrinterQueueViewChanged 方法。 此方法负责将 PrintJobListBoxIPrintJob 对象的 IEnumerable 集合重新绑定。 集合通过 PrinterQueueViewEventArgs 对象传递给该方法,该对象在 PrinterExtensionTypes.cs 文件中定义。

此示例显示 PrintJobManagement.xaml.cs 文件中的 OnPrinterQueueViewChanged 方法。

private async void OnPrinterQueueViewChanged(object sender, PrinterQueueViewEventArgs e)
{
    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
    {
        // Update the data binding on the ListBox that displays print jobs.
        PrintJobListBox.ItemsSource = e.Collection;
        if (PrintJobListBox.Items.Count > 0)
        {
            // If there are print jobs in the current view, mark the first job as selected.
            PrintJobListBox.SelectedIndex = 0;
        }
    });
}

步骤 3:显示打印作业状态

由于 PrintJobListBox 绑定到 IPrintJob 对象的集合,因此显示作业的状态相当简单。 所选打印作业被强制转换为 IPrintJob 对象,然后使用该对象的属性填充 PrintJobDetails TextBox。

打印作业管理和打印机维护示例中,每次选择不同的打印作业时,都会显示打印作业状态。 这种更新由 PrintJob_SelectionChanged 方法负责。

此示例显示 PrintJobManagement.xaml.cs 文件中的 PrintJob_SelectionChanged 方法。 它显示如何基于 IPrintJob 对象显示打印作业的状态。

private void PrintJob_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    try
    {
        // Display details of the selected print job.
        IPrintJob job = (IPrintJob)PrintJobListBox.SelectedItem;
        if (job != null)
        {
            PrintJobDetails.Text =
                "Details of print job: " + job.Name + "\r\n" +
                "Pages printed: " + job.PrintedPages + "/" + job.TotalPages + "\r\n" +
                "Submission time: " + job.SubmissionTime + "\r\n" +
                "Job status: " + DisplayablePrintJobStatus.ToString(job.Status);
        }
        else
        {
            PrintJobDetails.Text = "Please select a print job";
        }
    }
    catch (Exception exception)
    {
        rootPage.NotifyUser("Caught an exception: " + exception.Message, NotifyType.ErrorMessage);
    }
}

为了帮助显示打印作业状态说明,PrintJob_SelectionChanged 方法使用名为 printJobStatusDisplayNames 的静态字典以帮助显示用户友好的文本格式的作业状态说明。

此示例显示 PrintJobManagement.xaml.cs 文件中的 DisplayablePrintJobStatus 类。 此类包含由 PrintJob_SelectionChanged 类使用的静态成员。

internal class DisplayablePrintJobStatus
{
    /// <summary>
    /// Converts the PrintJobStatus bit fields to a display string.
    /// </summary>
    internal static string ToString(PrintJobStatus printJobStatus)
    {
        StringBuilder statusString = new StringBuilder();

        // Iterate through each of the PrintJobStatus bits that are set and convert it to a display string.
        foreach (var printJobStatusDisplayName in printJobStatusDisplayNames)
        {
            if ((printJobStatusDisplayName.Key & printJobStatus) != 0)
            {
                statusString.Append(printJobStatusDisplayName.Value);
            }
        }

        int stringlen = statusString.Length;
        if (stringlen > 0)
        {
            // Trim the trailing comma from the string.
            return statusString.ToString(0, stringlen - 1);
        }
        else
        {
            // If no print job status field was set, display "Not available".
            return "Not available";
        }
    }

    /// <summary>
    /// Static constructor that initializes the display name for the PrintJobStatus field.
    /// </summary>
    static DisplayablePrintJobStatus()
    {
        printJobStatusDisplayNames = new Dictionary<PrintJobStatus, string>();

        printJobStatusDisplayNames.Add(PrintJobStatus.Paused, "Paused,");
        printJobStatusDisplayNames.Add(PrintJobStatus.Error, "Error,");
        printJobStatusDisplayNames.Add(PrintJobStatus.Deleting, "Deleting,");
        printJobStatusDisplayNames.Add(PrintJobStatus.Spooling, "Spooling,");
        printJobStatusDisplayNames.Add(PrintJobStatus.Printing, "Printing,");
        printJobStatusDisplayNames.Add(PrintJobStatus.Offline, "Offline,");
        printJobStatusDisplayNames.Add(PrintJobStatus.PaperOut, "Out of paper,");
        printJobStatusDisplayNames.Add(PrintJobStatus.Printed, "Printed,");
        printJobStatusDisplayNames.Add(PrintJobStatus.Deleted, "Deleted,");
        printJobStatusDisplayNames.Add(PrintJobStatus.BlockedDeviceQueue, "Blocked device queue,");
        printJobStatusDisplayNames.Add(PrintJobStatus.UserIntervention, "User intervention required,");
        printJobStatusDisplayNames.Add(PrintJobStatus.Restarted, "Restarted,");
        printJobStatusDisplayNames.Add(PrintJobStatus.Complete, "Complete,");
        printJobStatusDisplayNames.Add(PrintJobStatus.Retained, "Retained,");
    }
    
    /// <summary>
    /// Private constructor to prevent default instantiation.
    /// </summary>
    private DisplayablePrintJobStatus() { }

    /// <summary>
    /// Contains the mapping between PrintJobStatus fields and display strings.
    /// </summary>
    private static Dictionary<PrintJobStatus, string> printJobStatusDisplayNames;
}

步骤 4:取消打印作业

与显示打印作业状态类似,当你有 IPrintJob 对象时,取消打印作业非常简单。 IPrintJob 类提供一个 RequestCancel 方法,用于启动相应打印作业的取消。 示例的 CancelPrintJob_Click 方法中对此进行了演示。

此示例显示 PrintJobManagement.xaml.cs 文件中的 CancelPrintJob_Click 方法。

private void CancelPrintJob_Click(object sender, RoutedEventArgs e)
{
    try
    {
        IPrintJob job = (IPrintJob)PrintJobListBox.SelectedItem;
        job.RequestCancel();
    }
    catch (Exception exception)
    {
        rootPage.NotifyUser("Caught an exception: " + exception.Message, NotifyType.ErrorMessage);
    }
}

测试

在测试 UWP 设备应用之前,必须使用设备元数据将其链接到你的打印机。

你需要一份打印机的设备元数据包副本,以便在其中添加设备应用信息。 如果没有设备元数据,可以使用设备元数据创作向导构建,如主题为 UWP 设备应用创建设备元数据所述。

要使用设备元数据创作向导,必须先安装 Microsoft Visual Studio Professional、Microsoft Visual Studio Ultimate 或适用于 Windows 8.1 的独立 SDK,然后才能完成本主题中的步骤。 安装 Microsoft Visual Studio Express for Windows 会安装不包含向导的 SDK 版本。

以下步骤构建你的应用并安装设备元数据。

  1. 启用测试签名。

    1. 通过双击 DeviceMetadataWizard.exe,从 %ProgramFiles(x86)%\Windows Kits\8.1\bin\x86 启动设备元数据创作向导

    2. 在“工具”菜单中,选择“启用测试签名”。

  2. 重新启动计算机

  3. 通过打开解决方案 (.sln) 文件生成解决方案。 按 F7 或在示例加载后从顶部菜单中转到“生成-生成解决方案”。>

  4. 断开连接并卸载打印机。 此步骤是必需的,以便 Windows 下次检测设备时读取更新的设备元数据。

  5. 编辑和保存设备元数据。 要将设备应用链接到你的设备,必须将设备应用与你的设备相关联。

    注意:如果尚未创建设备元数据,请参阅为 UWP 设备应用创建设备元数据

    1. 如果设备元数据创作向导尚未打开,请双击 DeviceMetadataWizard.exe%ProgramFiles(x86)%\Windows Kits\8.1\bin\x86 将其打开。

    2. 单击“编辑设备元数据”。 这样,就可以编辑现有的设备元数据包。

    3. 在“打开”对话框中,找到与 UWP 设备应用关联的设备元数据包。 (它具有 devicemetadata-ms 文件扩展名。)

    4. 在“指定 UWP 设备应用信息”页上,在“UWP 设备应用”框中输入 Microsoft Store 应用信息。 单击“导入 UWP 应用清单文件”,自入“包名称”、“发布者名称”和“UWP 应用 ID”。

    5. 如果你的应用正在注册打印机通知,请填写“通知处理程序”框。 在”事件 ID“中,输入打印事件处理程序的名称。 在”事件资产“中,输入代码所在的文件的名称。

    6. 完成后,单击“下一步”,直到进入“完成”页。

    7. 在“查看设备元数据包”页上,确保所有设置都正确,并选择“将设备元数据包复制到本地计算机上的元数据存储”复选框中。 然后单击保存

  6. 重新连接打印机,以便 Windows 在设备连接时读取更新的设备元数据。

作业管理(v4 打印机驱动程序)

开发 v4 打印驱动程序

双向通信

UWP 应用入门

创建 UWP 设备应用(分步指南)

为 UWP 设备应用创建设备元数据(分步指南)