在 UWP 设备应用中处理打印通知

Important

设备元数据已弃用,并将在 Windows 的将来版本中删除。 有关替代功能的信息,请参阅驱动程序包容器元数据

UWP 设备应用可以响应从 v4 打印驱动程序发送的双向通信(Bidi)事件。 本文介绍打印通知,并介绍了 打印设置和打印通知 示例的 C# 版本如何使用后台任务来响应打印通知。 后台任务演示如何在本地应用数据存储中保存通知详细信息、发送 toast 通知,以及更新磁贴和徽章。 若要了解有关 UWP 设备应用的一般详细信息,请参阅 UWP 设备应用简介

打印设置和打印通知示例的 C# 版本演示了 BackgroundTask 项目中应用(后台任务)的背景部分。 后台任务的代码位于 PrintBackgroundTask.cs 文件中。 前台应用(可从“开始”启动的全屏应用)位于 DeviceAppForPrinters 项目中。 InkLevel.xaml.cs文件显示了可从前台应用访问通知详细信息的一种方式。 为了处理打印通知,该示例使用 PrinterExtensionLibrary 项目中的打印机扩展库。 打印机扩展库提供了一种访问 v4 打印驱动程序的打印机扩展接口的便捷方法。 有关详细信息,请参阅 打印机扩展库概述

本文中所示的代码示例基于 打印设置和打印通知 示例的 C# 版本。 此示例在 JavaScript 和 C++中也可用。 由于C++可以直接访问 COM,因此示例C++版本不包括代码库项目。 下载示例以查看最新版本的代码。

打印通知允许 UWP 设备应用在打印时通知用户重要打印机事件,例如纸塞、打开打印机门、低墨量或打印机纸外错误。 当打印机触发通知时,系统事件代理将运行应用的后台任务。 从那里开始,后台任务可以保存通知详细信息、发送通知、更新磁贴、更新徽章或不执行任何操作。 通过保存通知详细信息,你的应用可以提供一种有助于用户了解和修复打印机问题的体验。

打印机制造商必须在 v4 打印驱动程序中实现 Bidi 和 DriverEvent XML 文件,才能将其 UWP 设备应用使用打印通知。 有关详细信息,请参阅 双向通信

当 DriverEvent 发生且启动 UWP 设备应用的后台任务时,应用程序有多种选项可以继续进行。 有关导致任务启动的流的详细信息,请参阅 驱动程序对自定义 UI 的支持

后台任务可以选择:

磁贴通知或 Toast 通知可让用户方便地启动前台应用。 启动前台应用时,可以使用OnLaunchedApp.xaml.cs中的方法检查它是否已由磁贴或 Toast 启动。 如果是,前台应用可以访问 本地应用数据存储中的任何打印通知详细信息。

Prerequisites

准备工作:

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

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

  3. 将应用与应用商店相关联。 有关详细信息,请参阅 “创建 UWP 设备应用”。

  4. 为打印机创建将其与应用程序关联的设备元数据。 有关详细信息,请参阅 “创建设备元数据”。

  5. 为应用的主页生成 UI。 可以从“开始”启动所有 UWP 设备应用,它们以全屏模式显示。 使用“开始”体验以与设备的特定品牌和功能匹配的方式突出显示产品或服务。 它可以使用的 UI 控件类型没有特殊限制。 若要开始设计全屏体验,请参阅 Microsoft应用商店设计原则

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

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

步骤 1:注册后台任务

为了使 Windows 能够识别应用可以处理打印通知,它必须注册打印通知的后台任务扩展。 此扩展在 Extension 元素中声明,其 Category 属性设置为 windows.backgroundTasks,以及 EntryPoint 属性设置为 BackgroundTask.PrintBackgroundTask。 该扩展还包括一个 Task 元素,用于指示它支持 systemEvent 任务类型。

可以在Microsoft Visual Studio清单设计器的“声明”选项卡上添加打印后台任务扩展。 还可以使用 XML(文本)编辑器手动编辑应用包清单 XML。 右键单击解决方案资源管理器中的 Package.appxmanifest 文件进行编辑选项。

此示例显示元素中的 Extension 后台任务扩展,因为它显示在应用包清单文件 Package.appxmanifest 中。

<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/2010/manifest">
  <Identity Name="Microsoft.SDKSamples.DeviceAppForPrinters.CS" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" Version="1.0.0.0" />
  <Properties>
    <DisplayName>Device App For Printers C# sample</DisplayName>
    <PublisherDisplayName>Microsoft Corporation</PublisherDisplayName>
    <Logo>Assets\storeLogo-sdk.png</Logo>
  </Properties>
  <Prerequisites>
    <OSMinVersion>6.3.0</OSMinVersion>
    <OSMaxVersionTested>6.3.0</OSMaxVersionTested>
  </Prerequisites>
  <Resources>
    <Resource Language="x-generate" />
  </Resources>
  <Applications>
    <Application Id="DeviceAppForPrinters" Executable="$targetnametoken$.exe" EntryPoint="DeviceAppForPrinters.App">
      <VisualElements DisplayName="Device App For Printers C# sample" Logo="Assets\squareTile-sdk.png"
                      SmallLogo="Assets\smallTile-sdk.png" Description="DeviceAppForPrinters C# sample"
                      ForegroundText="light" BackgroundColor="#00b2f0" ToastCapable="true">
        <DefaultTile ShowName="allLogos" ShortName="App4PrinterCS" WideLogo="Assets\tile-sdk.png" />
        <SplashScreen Image="Assets\splash-sdk.png" BackgroundColor="#00b2f0" />
      </VisualElements>
      <Extensions>
        <Extension Category="windows.backgroundTasks" EntryPoint="BackgroundTask.PrintBackgroundTask">
          <BackgroundTasks>
            <Task Type="systemEvent" />
          </BackgroundTasks>
        </Extension>
        <Extension Category="windows.printTaskSettings" Executable="$targetnametoken$.exe" EntryPoint="DeviceAppForPrinters.App" />
      </Extensions>
    </Application>
  </Applications>
</Package>

步骤 2:配置设备元数据

使用设备元数据创作向导将应用与设备关联时,请确保在“指定 UWP 设备应用信息”页上完成“通知处理程序”框。 这有助于确保在打印通知期间调用您应用的后台任务。

有关如何编辑设备元数据的分步说明,请参阅 “测试 ”部分。

步骤 3:生成 UI

在构建应用之前,应与设计人员和营销团队合作,设计用户体验。 用户体验应投影公司的品牌方面,并帮助你与用户建立连接。

设计指南

在设计磁贴和锁屏提醒体验之前,请务必查看 Microsoft 应用商店应用指南。 这些指南有助于确保应用提供与其他 UWP 应用一致的直观体验。

对于应用的主页,请记住,Windows 8.1 可以在单个监视器上以各种大小显示多个应用。 请参阅以下指南,详细了解应用如何在屏幕大小、窗口大小和方向之间优雅地自适应布局。

最佳做法

  • 不要在通知中包含动作词。 在通知消息中,不要使用通知用户推送、按或选择通知的文本。 用户已经了解他们可以按 Toast 来查找详细信息。 例如,只需编写“打印机墨迹不足”,而不是“打印机墨迹不足”。 按“进行故障排除”。

  • 保持交互简单。 通知体验上显示的所有内容都应与通知相关。 例如,有关纸张卡住的通知页面应仅包含解决此问题的链接和信息。 它不应包含指向不相关的体验的链接,例如购买墨迹或其他支持信息。

  • 使用多媒体。 使用设备的实际照片、视频或插图来帮助用户快速解决其设备的问题。

  • 将用户保留在应用的上下文中。 提供有关问题的信息时,请勿链接到在线或其他支持材料。 将用户保留在应用的上下文中。

步骤 4:创建后台任务

如果应用为打印通知注册后台任务,则它必须提供后台任务激活的处理程序。 在 “打印设置”和“打印通知 ”示例中, PrintBackgroundTask 类处理打印通知。

如果打印机状态不需要即时用户干预,请更新磁贴而不是显示 Toast。 例如,对于低墨迹条件,磁贴更新已足够。 但是,如果打印机墨迹不足,应用可能会显示 Toast 通知。

保存通知详细信息

后台任务无法直接启动前台应用程序,只有用户可以通过磁贴、通知或开始菜单来启动。 因此,为了确保前台应用可以访问打印通知详细信息,后台任务会将它们保存到本地存储。 有关使用本地存储的详细信息,请参阅 快速入门:本地应用数据

触发打印通知时,Windows 通过调用其 Run 方法运行后台任务。 通知数据通过必须强制转换为类型 Windows.Devices.Printers.Extensions.PrintNotificationEventDetails 的方法参数传递到后台任务。 该对象的PrinterNameEventData属性分别包含打印机名称和 Bidi 消息。

此示例展示了后台任务的 Run 方法,该方法位于 PrintBackgroundTask.cs 文件中;在调用 Toast、磁贴和徽章方法之前,会先将打印通知的详细信息保存到应用设置中。

public void Run(Windows.ApplicationModel.Background.IBackgroundTaskInstance taskInstance)
{
    // Save notification details to local storage
    PrintNotificationEventDetails details = (PrintNotificationEventDetails)taskInstance.TriggerDetails;
    settings.Values[keyPrinterName] = details.PrinterName;
    settings.Values[keyAsyncUIXML] = details.EventData;

    // Demonstrate possible actions
    ShowToast(details.PrinterName, details.EventData);
    UpdateTile(details.PrinterName, details.EventData);
    UpdateBadge();
}

更新磁贴

当打印通知详细信息发送到 UpdateTile 方法时,示例的后台任务演示如何在磁贴上显示它们。 有关磁贴的详细信息,请参阅 磁贴和磁贴通知概述

本示例显示UpdateTile文件中后台任务的方法。

void UpdateTile(string printerName, string bidiMessage)
{
    TileUpdater tileUpdater = TileUpdateManager.CreateTileUpdaterForApplication();
    tileUpdater.Clear();

    XmlDocument tileXml = TileUpdateManager.GetTemplateContent(TileTemplateType.TileWide310x150Text09);
    XmlNodeList tileTextAttributes = tileXml.GetElementsByTagName("text");
    tileTextAttributes[0].InnerText = printerName;
    tileTextAttributes[1].InnerText = bidiMessage;

    TileNotification tileNotification = new TileNotification(tileXml);
    tileNotification.Tag = "tag01";
    tileUpdater.Update(tileNotification);
}

更新徽章

该方法演示如何使用 BadgeNotification 类来更新徽章。 有关磁贴的详细信息,请参阅 徽章概述

本示例显示UpdateBadge文件中后台任务的方法。

void UpdateBadge()
{
    XmlDocument badgeXml = BadgeUpdateManager.GetTemplateContent(BadgeTemplateType.BadgeGlyph);
    XmlElement badgeElement = (XmlElement)badgeXml.SelectSingleNode("/badge");
    badgeElement.SetAttribute("value", "error");

    var badgeNotification = new BadgeNotification(badgeXml);
    BadgeUpdateManager.CreateBadgeUpdaterForApplication().Update(badgeNotification);
}

举杯致敬

Toast 通知是向用户发送的暂时性消息,其中包含相关的时间敏感信息,并提供对应用中相关内容的快速访问。 Toast 通知应被视为一种邀请,促使用户返回应用程序以跟进感兴趣的内容。 有关详细信息,请参阅 Toast 通知概述

若要启用 Toast 通知,应用需要在应用包清单中注册支持 Toast 的功能。 在元素中 VisualElements ,将 ToastCapable 属性设置为 true。

Important

我们不建议总是显示 Toast,特别是针对不可操作的事件。 这可能会令用户恼火,并导致他们关闭应用中的所有通知。 对于不需要用户立即注意的事件,建议仅更新磁贴和徽章,而不显示通知。

此示例显示 ToastCapable 元素中的 VisualElements 属性,因为它显示在应用包清单文件 Package.appxmanifest 中。

<VisualElements DisplayName="Device App For Printers C# sample" Logo="Assets\squareTile-sdk.png"
                SmallLogo="Assets\smallTile-sdk.png" Description="DeviceAppForPrinters C# sample"
                ForegroundText="light" BackgroundColor="#00b2f0" ToastCapable="true">
  <DefaultTile ShowName="allLogos" ShortName="App4PrinterCS" WideLogo="Assets\tile-sdk.png" />
  <SplashScreen Image="Assets\splash-sdk.png" BackgroundColor="#00b2f0" />
</VisualElements>

此示例来自ShowToastPrintBackgroundTask.cs文件的方法。 它演示如何基于两个字符串(命名 titlebody)引发 Toast。

void ShowToast(string title, string body)
{
    //
    // Get Toast template
    //
    XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02);

    //
    // Pass to app as eventArgs.detail.arguments
    //
    ((XmlElement)toastXml.SelectSingleNode("/toast")).SetAttribute("launch", title);

    //
    // The ToastText02 template has 2 text nodes (a header and a body)
    // Assign title to the first one, and body to the second one
    //
    XmlNodeList textList = toastXml.GetElementsByTagName("text");
    textList[0].AppendChild(toastXml.CreateTextNode(title));
    textList[1].AppendChild(toastXml.CreateTextNode(body));

    //
    // Show the Toast
    //
    ToastNotification toast = new ToastNotification(toastXml);
    ToastNotificationManager.CreateToastNotifier().Show(toast);
}

步骤 5:处理激活

打印通知触发后台任务后,可以通过点击 Toast 通知或磁贴来启动应用。 如果应用从任一项激活,则参数将通过 LaunchActivatedEventArgs.arguments 属性传递给应用。 有关激活和Microsoft Store应用生命周期的详细信息,请参阅应用程序生命周期

若要确定应用是否在这些情况下激活,请处理该 OnLaunched 事件,并检查传递给事件处理程序的事件参数。 如果事件参数为 null,则应用已由用户从 Start 激活。 如果事件参数不为 null,则应用是从 Toast 或磁贴启动的。

此示例来自OnLaunchedApp.xaml.cs文件的方法。 它演示如何处理来自 Toast 或磁贴的激活。

protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
    Frame rootFrame = Window.Current.Content as Frame;

    // Do not repeat app initialization when the Window already has content,
    // just ensure that the window is active

    if (rootFrame == null)
    {
        // Create a Frame to act as the navigation context and navigate to the first page
        rootFrame = new Frame();
        // Associate the frame with a SuspensionManager key
        SuspensionManager.RegisterFrame(rootFrame, "AppFrame");

        if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
            // Restore the saved session state only when appropriate
            try
            {
                await SuspensionManager.RestoreAsync();
            }
            catch (SuspensionManagerException)
            {
                //Something went wrong restoring state.
                //Assume there is no state and continue
            }
        }

        // Place the frame in the current Window
        Window.Current.Content = rootFrame;
    }
    if (rootFrame.Content == null || !String.IsNullOrEmpty(args.Arguments))
    {
        // When the navigation stack isn't restored or there are launch arguments
        // indicating an alternate launch (e.g.: via toast or secondary tile),
        // navigate to the appropriate page, configuring the new page by passing required
        // information as a navigation parameter
        if (!rootFrame.Navigate(typeof(MainPage), args.Arguments))
        {
            throw new Exception("Failed to create initial page");
        }
    }
    // Ensure the current window is active
    Window.Current.Activate();
}

步骤 6:访问通知详细信息

由于后台任务无法直接启动前台应用,因此需要将打印通知详细信息保存到应用的设置中,以便前台应用可以访问它们。 有关使用本地存储的详细信息,请参阅 快速入门:本地应用数据

此示例演示如何从 打印设置和打印通知 示例中的应用设置检索打印机名称和 Bidi 消息。 代码来自DisplayBackgroundTaskTriggerDetailsInkLevel.xaml.cs文件的方法。 键索引值 keyPrinterName以及 keyAsyncUIXML,是后台任务中使用的字符串常量, PrintBackgroundTask.cs

void DisplayBackgroundTaskTriggerDetails()
{
    String outputText = "\r\n";

    try
    {
        string printerName = settings.Values[keyPrinterName].ToString();
        outputText += ("Printer name from background task triggerDetails: " + printerName);
    }
    catch (Exception)
    {
        outputText += ("No printer name retrieved from background task triggerDetails ");
    }

    outputText += "\r\n";
    try
    {
        string asyncUIXML = settings.Values[keyAsyncUIXML].ToString();
        outputText += ("AsyncUI xml from background task triggerDetails: " + asyncUIXML);
    }
    catch (Exception)
    {
        outputText += ("No asyncUI xml retrieved from background task triggerDetails ");
    }

    ToastOutput.Text += outputText;
}

Testing

在测试 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. 如果设备元数据创作向导尚未打开,请从%ProgramFiles(x86)%\Windows Kits\8.1\bin\x86 双击DeviceMetadataWizard.exe以启动它。

    2. 选择 “编辑设备元数据”。 使用此选项可以编辑现有的设备元数据包。

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

    4. “指定 UWP 设备应用信息 ”页上,在 “UWP 设备应用 ”框中输入Microsoft应用商店应用信息。 选择 “导入 UWP 应用清单文件 ”以自动输入 包名称发布服务器名称和UWP 应用 ID

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

    6. 完成后,选择“ 下一步 ”,直到到达 “完成 ”页。

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

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

Troubleshooting

问题:未显示默认 Toast 通知

如果未在预期时显示默认打印通知...

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

  • 可能的原因: Toast 通知被域策略禁用。 退出域,然后重试。

  • 可能的原因: 打印机尚未实现 DriverEvents。 检查 v4 驱动程序是否支持 Bidi 和 DriverEvents。 有关详细信息,请参阅 驱动程序对自定义 UI 的支持

  • 可能的原因: 计算机在打印机队列中没有最近的作业。 确保打印机图标显示在屏幕右下角。 如果没有,请发送另一个打印作业。

  • 可能的原因: 后台任务的入口点(IBackgroundTask)与前台应用位于同一项目中。 不允许使用此方案。 为后台任务处理程序分离出全新的类。

  • 可能的原因: 作为应用中通知入口点的类在清单文件或设备元数据中配置错误,导致应用在后台宿主进程中崩溃,并且不会显示任何 Toast 通知。 检查是否存在以下问题:

    • 请确保在清单设计器的 “声明” 选项卡中正确填写入口点。 它应采用适用于 C# 和 C++ 的 Namespace.ClassName 格式。 对于 JavaScript,它应该是 .js 文件的相对目录路径。

    • JavaScript 应用在完成后应调用 close()。

    • C# 类必须实现 Windows.ApplicationModel.Background.IBackgroundTask,并且必须具有公共 void Run(Windows.ApplicationModel.Background.IBackgroundTaskInstance taskInstance) 方法。

    • C++类必须实现 Windows::ApplicationModel::Background::IBackgroundTask,并且必须具有方法 virtual void Run(Windows::ApplicationModel::Background::IBackgroundTaskInstance^ taskInstance)