如何在 UWP 设备应用中显示打印机状态

在 Windows 8.1 中,用户可以从 UWP 设备应用的现代 UI 中检查打印机状态。 本主题使用 C# 版本的打印设置和打印通知示例来演示如何查询打印机状态并显示它。 要了解有关 UWP 设备应用的一般详细信息,请参阅初识 UWP 设备应用

C# 版本的打印设置和打印通知示例使用 InkLevel.xaml 页面演示如何获取打印机状态(在本例中为墨水级别)并显示它。 打印帮助程序类用于创建设备上下文 (IPrinterExtensionContext) 并执行设备查询。 PrinterHelperClass.cs 文件位于 DeviceAppForPrintersLibrary 项目中,并使用 PrinterExtensionLibrary 项目中定义的 API。 打印机扩展库提供了一种访问 v4 打印驱动程序的打印机扩展接口的便捷方法。 有关详细信息,请参阅打印机扩展库概述

注意

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

先决条件

准备工作:

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

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

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

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

  5. 如果要使用 C# 或 JavaScript 编写应用,请将 PrinterExtensionLibraryDeviceAppForPrintersLibrary 项目添加到你的 UWP 设备应用。 可以在打印设置和打印通知示例中找到每个项目。

    注意

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

步骤 1:查找打印机

在创建设备上下文之前,应用需要确定打印机的设备 ID。 为此,此示例使用 EnumerateAssociatedPrinters 方法搜索附加到电脑的所有打印机。 然后,它检查每个打印机的容器,并通过比较每个容器的 PackageFamilyName 属性来查找关联。

注意

可以在 Microsoft Visual Studio 清单设计器的“打包”选项卡下找到与应用关联的设备的 System.Devices.AppPackageFamilyName。

此示例显示 InkLevel.xaml.cs 文件中的 EnumerateAssociatedPrinters 方法:

async void EnumerateAssociatedPrinters(object sender, RoutedEventArgs e)
{
    // Reset output text and associated printer array.
    AssociatedPrinters.Items.Clear();
    BidiOutput.Text = "";

    // GUID string for printers.
    string printerInterfaceClass = "{0ecef634-6ef0-472a-8085-5ad023ecbccd}";
    string selector = "System.Devices.InterfaceClassGuid:=\"" + printerInterfaceClass + "\"";

    // By default, FindAllAsync does not return the containerId for the device it queries.
    // We have to add it as an additional property to retrieve. 
    string containerIdField = "System.Devices.ContainerId";
    string[] propertiesToRetrieve = new string[] { containerIdField };

    // Asynchronously find all printer devices.
    DeviceInformationCollection deviceInfoCollection = await DeviceInformation.FindAllAsync(selector, propertiesToRetrieve);

    // For each printer device returned, check if it is associated with the current app.
    for (int i = 0; i < deviceInfoCollection.Count; i++)
    {
        DeviceInformation deviceInfo = deviceInfoCollection[i];
        FindAssociation(deviceInfo, deviceInfo.Properties[containerIdField].ToString());
    }
}

FindAssociation 方法由 EnumerateAssociatedPrinters 调用,用于检查是否有打印机与当前应用程序相关联。 换句话说,此方法检查应用是否为 UWP 设备应用。 当本地电脑上的设备元数据中定义了应用和打印机时,就会存在此关联。

此示例显示 InkLevel.xaml.cs 文件中的 FindAssociation 方法:

async void FindAssociation(DeviceInformation deviceInfo, string containerId)
{

    // Specifically telling CreateFromIdAsync to retrieve the AppPackageFamilyName. 
    string packageFamilyName = "System.Devices.AppPackageFamilyName";
    string[] containerPropertiesToGet = new string[] { packageFamilyName };

    // CreateFromIdAsync needs braces on the containerId string.
    string containerIdwithBraces = "{" + containerId + "}";

    // Asynchronously getting the container information of the printer.
    PnpObject containerInfo = await PnpObject.CreateFromIdAsync(PnpObjectType.DeviceContainer, containerIdwithBraces, containerPropertiesToGet);

    // Printers could be associated with other device apps, only the ones with package family name
    // matching this app's is associated with this app. The packageFamilyName for this app will be found in this app's packagemanifest
    string appPackageFamilyName = "Microsoft.SDKSamples.DeviceAppForPrinters.CS_8wekyb3d8bbwe";
    var prop = containerInfo.Properties;

    // If the packageFamilyName of the printer container matches the one for this app, the printer is associated with this app.
    string[] packageFamilyNameList = (string[])prop[packageFamilyName];
    if (packageFamilyNameList != null)
    {
        for (int j = 0; j < packageFamilyNameList.Length; j++)
        {
            if (packageFamilyNameList[j].Equals(appPackageFamilyName))
            {
                AddToList(deviceInfo);
            }
        }
    }
}

找到关联后,方法 FindAssociation 使用 AddToList 方法将设备 ID 添加到关联的设备 ID 列表中。 这些 ID 存储在名为 AssociatedPrinters 的 ComboBox 中。

此示例显示 InkLevel.xaml.cs 文件中的 AddToList 方法:

void AddToList(DeviceInformation deviceInfo)
{
    // Creating a new display item so the user sees the friendly name instead of the interfaceId.
    ComboBoxItem item = new ComboBoxItem();
    item.Content = deviceInfo.Properties["System.ItemNameDisplay"] as string;
    item.DataContext = deviceInfo.Id;
    AssociatedPrinters.Items.Add(item);

    // If this is the first printer to be added to the combo box, select it.
    if (AssociatedPrinters.Items.Count == 1)
    {
        AssociatedPrinters.SelectedIndex = 0;
    }
}

步骤 2:显示状态

GetInkStatus 方法使用基于事件的异步模式从打印机请求信息。 此方法使用关联的设备 ID 来获取可用于获取设备状态的设备上下文。 对 printHelper.SendInkLevelQuery() 方法的调用将启动设备查询。 响应返回时,将调用 OnInkLevelReceived 方法并更新 UI。

注意

此 C# 示例采用的模式与 JavaScript 示例不同,因为 C# 允许将调度程序发送到 PrintHelperClass,以便它可以将事件消息发布回 UI 线程。

此示例显示 InkLevel.xaml.cs 文件中的 GetInkStatusOnInkLevelReceived 方法:

void GetInkStatus(object sender, RoutedEventArgs e)
{
    if (AssociatedPrinters.Items.Count > 0)
    {
        // Get the printer that the user has selected to query.
        ComboBoxItem selectedItem = AssociatedPrinters.SelectedItem as ComboBoxItem;

        // The interfaceId is retrieved from the detail field.
        string interfaceId = selectedItem.DataContext as string;

        try
        {
            // Unsubscribe existing ink level event handler, if any.
            if (printHelper != null)
            {
                printHelper.OnInkLevelReceived -= OnInkLevelReceived;
                printHelper = null;
            }

            object context = Windows.Devices.Printers.Extensions.PrintExtensionContext.FromDeviceId(interfaceId);printHelper.SendInkLevelQuery()

            // Use the PrinterHelperClass to retrieve the bidi data and display it.
            printHelper = new PrintHelperClass(context);
            try
            {
                printHelper.OnInkLevelReceived += OnInkLevelReceived;
                printHelper.SendInkLevelQuery();

                rootPage.NotifyUser("Ink level query successful", NotifyType.StatusMessage);
            }
            catch (Exception)
            {
                rootPage.NotifyUser("Ink level query unsuccessful", NotifyType.ErrorMessage);
            }
        }
        catch (Exception)
        {
            rootPage.NotifyUser("Error retrieving PrinterExtensionContext from InterfaceId", NotifyType.ErrorMessage);
        }
    }
}

private void OnInkLevelReceived(object sender, string response)
{
    BidiOutput.Text = response;
}

打印帮助程序类负责将 Bidi 查询发送到设备并接收响应。

此示例显示 PrintHelperClass.cs 文件中的 SendInkLevelQuery 方法和其他方法。 请注意,此处仅显示一些打印帮助程序类方法。 下载打印设置和打印通知示例以查看完整代码。

public void SendInkLevelQuery()
{
    printerQueue.OnBidiResponseReceived += OnBidiResponseReceived;

    // Send the query.
    string queryString = "\\Printer.Consumables";
    printerQueue.SendBidiQuery(queryString);
}

private void OnBidiResponseReceived(object sender, PrinterQueueEventArgs responseArguments)
{
    // Invoke the ink level event with appropriate data.
    dispatcher.RunAsync(
        Windows.UI.Core.CoreDispatcherPriority.Normal,
        () =>
        {
            OnInkLevelReceived(sender, ParseResponse(responseArguments));
        });
}

private string ParseResponse(PrinterQueueEventArgs responseArguments)
{
    if (responseArguments.StatusHResult == (int)HRESULT.S_OK)
        return responseArguments.Response;
    else
        return InvalidHResult(responseArguments.StatusHResult);
}

private string InvalidHResult(int result)
{
    switch (result)
    {
        case unchecked((int)HRESULT.E_INVALIDARG):
            return "Invalid Arguments";
        case unchecked((int)HRESULT.E_OUTOFMEMORY):
            return "Out of Memory";
        case unchecked((int)HRESULT.ERROR_NOT_FOUND):
            return "Not found";
        case (int)HRESULT.S_FALSE:
            return "False";
        case (int)HRESULT.S_PT_NO_CONFLICT:
            return "PT No Conflict";
        default:
            return "Undefined status: 0x" + result.ToString("X");
    }
}

测试

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

你需要一份打印机的设备元数据包副本,以便在其中添加设备应用信息。 如果没有设备元数据,可以使用设备元数据创作向导构建,如主题步骤 2:为 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. 编辑和保存设备元数据。 要将设备应用链接到你的设备,必须将设备应用与你的设备相关联。

    注意

    如果尚未创建设备元数据,请参阅步骤 2:为 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 在设备连接时读取更新的设备元数据。

疑难解答

问题:枚举与应用关联的设备时找不到打印机

如果在枚举关联的打印机时找不到打印机:

  • 可能的原因:测试签名未打开。 有关打开调试的信息,请参阅本主题中的“调试”部分。

  • 可能的原因:应用未查询正确的包系列名称。 在代码中检查包系列名称。 在 Microsoft Visual Studio 中打开 package.appxmanifest,并确保要查询的包系列名称与“打包”选项卡中“包系列名称”字段中的名称匹配。

  • 可能的原因:设备元数据没有与包系列名称关联。 使用设备元数据创作向导打开设备元数据,并检查包系列名称。 双击 DeviceMetadataWizard.exe,从 %ProgramFiles(x86)%\Windows Kits\8.1\bin\x86 启动向导。

问题:找到了与应用关联的打印机,但无法查询 Bidi 信息

如果在枚举关联的打印机时找到了打印机,但 Bidi 查询返回错误...

  • 可能的原因:包系列名称错误。 在代码中检查包系列名称。 在 Visual Studio 中打开 package.appxmanifest,并确保要查询的包系列名称与“打包”选项卡中“包系列名称”字段中的名称匹配。

  • 可能的原因:打印机是使用 v3 打印机而不是 v4 打印机安装的。 要查看所安装的版本,请打开 PowerShell 并键入以下命令:

    get-printer | Select Name, {(get-printerdriver -Name $_.DriverName).MajorVersion}
    

开发 v4 打印驱动程序

打印机扩展接口(v4 打印驱动程序)

双向通信

UWP 应用入门

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

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