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

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

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

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

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

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

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

后台任务可以选择:

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

先决条件

准备工作:

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

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

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

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

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

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

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

步骤 1:注册后台任务

为了使 Windows 能够识别应用可以处理打印通知,它必须注册打印通知的后台任务扩展。 此扩展在 Extension 元素中声明,Category 属性设置为 windows.backgroundTasksEntryPoint 属性设置为 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 Store 应用指南。 这些指南有助于确保你的应用提供与其他 UWP 应用一致的直观体验。

对于应用的主页,请记住,Windows 8.1 可以在一个监视器上显示不同大小的多个应用。 请参阅以下指南,详细了解应用如何在不同屏幕大小、窗口大小和方向之间正常重排。

最佳实践

  • 不要在通知中包含操作词语。 在通知消息中,不要使用让用户推、按或单击通知的文本。 用户已了解他们可以按 Toast 了解详细信息。 例如,只需编写“打印机墨水量低”,而不是“打印机墨水量低。 按下以排除故障”。

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

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

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

步骤 4:创建后台任务

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

如果打印机状态不需要即时用户干预,请更新磁贴而不是显示 Toast。 例如,在墨水量低的情况下,只需更新磁贴即可。 但是,如果打印机完全没有墨水,应用可显示 Toast 通知。

保存通知详细信息

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

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

此示例显示 PrintBackgroundTask.cs 文件中后台任务的 Run 方法,打印通知详细信息会将保存到应用设置中,然后再调用 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 方法时,示例的后台任务演示如何在磁贴上显示它们。 有关磁贴的详细信息,请参阅磁贴和磁贴通知概述

本示例显示 PrintBackgroundTask.cs 文件中的 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);
}

更新锁屏提醒

UpdateBadge 方法演示如何使用 BadgeNotification 类更新锁屏提醒。 有关磁贴的详细信息,请参阅锁屏提醒概述

本示例显示 PrintBackgroundTask.cs 文件中的 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 通知,应用需要在应用包清单中注册 Toast 功能。 在 VisualElements 元素中,将 ToastCapable 属性设置为 true。

重要

我们不建议始终显示 Toast,尤其是对于不可操作的事件。 这可能会令用户恼火,并导致他们关闭应用中的所有 Toast。 对于不需要用户立即注意的事件,建议仅更新磁贴和锁屏提醒,而不显示 Toast。

此示例显示 VisualElements 元素中的 ToastCapable 属性,它显示在应用包清单文件 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>

此示例来自 PrintBackgroundTask.cs 文件的 ShowToast 方法。 它演示如何根据名为 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,则应用已由用户从“开始”激活。 如果事件参数不为 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 消息。 代码来自 InkLevel.xaml.cs 文件的 DisplayBackgroundTaskTriggerDetails 方法。 请注意,键索引值 keyPrinterNamekeyAsyncUIXML 与后台任务 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;
}

测试

在测试 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 在设备连接时读取更新的设备元数据。

疑难解答

问题:未显示默认 Toast 通知

如果没有显示预期的默认打印通知...

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

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

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

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

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

  • 可能的原因: 应用中作为通知入口点的类在清单或设备元数据中被错误地给出,导致应用在 backgroundhost 中崩溃,并且不显示任何 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) 方法。

锁屏提醒概述(UWP 应用)

磁贴和磁贴通知概述(UWP 应用)

磁贴和锁屏提醒的指南和检查清单(UWP 应用)

Toast 通知概述(UWP 应用)

Toast 通知指南和检查清单(UWP 应用)

自定义 UI 的驱动程序支持

开发 v4 打印驱动程序

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

双向通信

UWP 应用入门

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

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