wtorek, 1 maja 2012

Notatki o Windows 8 Consumer Preview - odc. 21

Tym razem ostatnia część hardcorowego tryptyku o aplikacjach Metro dla urządzeń, który będzie na temat współpracy z drukarkami. Ogólne informacje z BUILD wydają się nadal aktualne, przy czym w wersji Consumer Preview dokonano pewnych uaktualnień.

Tak poza tematem spodobała mi się definicja symulatora jako zdalnej maszyny o wszystkich właściwościach maszyny lokalnej (od zawsze miałem odczucie, że wygląda to jakbyśmy łączyli się ze swoją maszyną, ale teraz zostało to tak oficjalnie zdefiniowane) oraz sposób na problem z debugowaniem lokalnym np. gdy przerwie go utrata focusa - debugować zdalnie lub przez symulator!

Printers - Metro style Device Apps

The new driver is called the v4 printer driver model. Support for Metro style apps and their UI and environment has been integrated into the v4 printer driver. A v4 printer driver can also include an app known as a printer extension that provides the ability to customize the print preferences UI. A printer extension can also support printer notifications.

Authoring a Metro style Device App for Printing

In Windows 8, Metro style device apps can extend and enhance built-in user experiences for printers - providing automatic installation of printer apps, access to differentiating print properties, and a rich notification experience to help users resolve issues with their device. With a Metro style app for your printer, you can build customer loyalty by providing print features and services targeted to specific devices, reduce costs with integrated support, and increase revenues by providing streamlined purchase experiences for ink and supplies.


Metro style Device App Experiences

A Metro style device app for printers can deliver three types of experiences in a single app package:

  1. Full-screen Start experience that is launched from the app tile in Start.
  2. The Advanced Print Settings experience that is launched from the Print window when the user clicks on More Settings.
  3. The Print Notifications experience that is launched when the user clicks on a toast that is raised when a device enters certain status.

Start experience

function getInkStatus(interfaceId) {

    var responseString;

    try {

        var context = Windows.Devices.Printers.Extensions.


        var helper = new Microsoft.Samples.Printers.Extension.


        var responseString = helper.getInkLevel(1);

    } catch (e) {

        responseString = "InterfaceId: " + interfaceId +

            " Message: " + e.message;


    return responseString


namespace Microsoft.Samples.Printers.Extensions {


    public sealed class PrintHelper {

        PrinterExtensionContext context;

        private IPrinterQueue printerQueue;

        private AutoResetEvent waitHandle = new AutoResetEvent(false);


        Public PrintHelper(Object context) {

           this.context = new PrinterExtensionContext(context);

           this.printerQueue= context.Queue;



        /// <summary>

        /// Returns the ink level of the printer queue

        /// Note that this is done synchronously to show how to use

        /// the response, ideally this should be done asynchronously.

        /// </summary>

        public string GetInkLevel(int timeoutInSeconds) {

            string queryString = "\\Printer.Consumables";


            // register an event handler for when the response returns

            printerQueue.OnBidiResponseReceived +=  




            // wait while the response comes back

            // note this makes the function synchronous

            waitHandle.WaitOne(timeoutInSeconds * 1000);


            if (bidiResponse != null)

                return bidiResponse;


                return "Timed out.";


        // saves the response and sets the handle to move on

        private void OnWaitedBidiResponse(object sender,

            PrinterQueueEventArgs responseArguments) {

            bidiResponse = ParseResponse(responseArguments);




        // parses the response from bidi

        private string ParseResponse(PrinterQueueEventArgs

            responseArguments) {

            if (responseArguments.StatusHResult == (int)HRESULT.S_OK)

                return responseArguments.Response;


                return InvalidHResult(responseArguments.



C#: asynchronous

The Start app is a full-screen app. These apps should be designed for 1366 pixels wide and 768 pixels high.

Advanced Print Settings experience

This experience can be used to highlight differentiated features for your printer such as the ability to apply watermarks to a document page, offer secure printing options, or image enhancement options.

If Windows detects that a Metro style device app is installed for your printer, and that the app has opted-into the PrintTaskSettings contract, your app replaces the default experience provided by Windows.



When the app is activated, the activation event provides an event argument of type Windows.UI.WebUI.WebUIPrintTaskSettingsActivatedEventArgs for the JavaScript sample and Windows.ApplicationModel.Application.PrintTaskSettingsActivatedEventArgs for the C# sample. This event argument exposes a property for getting and setting (currently selected) printer properties. This is a COM object and its fields and methods cannot be accessed from JavaScript. So, the app needs a custom WinRT component that is able to interact with this object.


WinJS.Application.addEventListener("activated", activated, false);


function activated(eventArgs) {


   if (eventArgs.detail.kind ===

      Windows.ApplicationModel.Activation.ActivationKind.launch) {

         if (!eventArgs.detail.arguments) {

            // Initialize logic for Start experience here

         } else {

            // Initialize logic for Print Notification experience here



   } else if (eventArgs.detail.kind === Windows.ApplicationModel.

      Activation.ActivationKind.printTaskSettings) {

         // Initialize logic for PrintTaskSettings experience here



The app needs to listen to the Back Button event when a user clicks the Back button to go back to the Print window.

When the back button is clicked from the Advanced Print Settings window, the app has to save the changes the user made and therefore needs to listen to the Back Button event.

var configuration;

var printerExtesionContext;

var printHelper = null;


WinJS.Application.addEventListener("activated", activated, false);


function activated(eventArgs) {

    // logic to determine app is launched from ‘More settings’ here


    configuration = eventArgs.detail.configuration;


    // subscribing to the event


        onSaveRequested, false);


    printerExtesionContext = configuration.printerExtensionContext;

    printHelper = new Microsoft.Samples.Printers.Extensions.





The flyout that displays the Advanced Printer Settings experiences is 768 pixels high and 646 pixels wide. If your app is more than 688 pixels in height, the user may slide or scroll to view parts of the app that are above or below the viewable area.

Best practises:

  • Avoid time-consuming or complex interactions in your Advanced Print Settings experience. In most cases, actions like setting up a printer, viewing status, ordering ink, and troubleshooting are best done inside the Start experience.
  • Avoid making your users navigate back and forth between multiple pages in your Advanced Print Settings experience.
  • Don't use light dismiss flyouts.
  • When a user is printing content, you should take steps to ensure they remain in the print context. For example, if your app has links that lead to other areas of your app (such as to a home page or to a page for purchasing ink), you should disable them.
  • If your app includes a preview of what the user is printing, that preview should match what will actually be printed as much as possible.

Build the Device Logic Layer

WebUIPrintTaskSettingsActivatedEventArgs (JS) or PrintTaskSettingsActivatedEventArgs (C#) exposes a property for controlling the printer. The detail.configuration property provides an object of type Windows.Devices.Printers.Extensions.PrintTaskConfiguration. This object provides access to the print task extension context, and also allows you to add an event handler to update the print ticket. The print task extension context can be accessed via detail.configuration. printerExtensionContext. It is a Windows.Devices.Printers.Extensions.PrinterExtensionContext object, which is a pointer to the PrinterExtensionLibrary interfaces for Print Schema, PrintTicket, and print queue information. It will be null if no interfaces are exposed.

var configuration;

var printerExtesionContext;


function displayPrintSettings() {


    if (!configuration) {

        sdkSample.displayError("Configuration argument is null");




    printerExtesionContext = configuration.printerExtensionContext;

    printHelper = new Microsoft.Samples.Printers.Extensions.



    var feature = "PageOrientation";


    // if printer's capabilities include this feature.

    if (!printHelper.featureExists(feature)) {




    // Get the selected option for this feature in the current

    // context's print ticket.

    var selectedOption = printHelper.getSelectedOptionIndex(feature);


    // Get the array of options in the current context’s print ticket

    var optionIndex = printHelper.getOptionInfo(feature, "Index"),

    var optionNames = printHelper.getOptionInfo(feature,



    var selectedName;

    for (var i = 0; i < optionIndex.length; i++) {

        if (optionIndex[i] === selectedOption)

           selectedName = optionNames[i];


    // logic to display the orientation string here


Build the WinRT component

// make sure to include printer extension on top of standard libraries

using Microsoft.Samples.Printing.PrinterExtension;

using Microsoft.Samples.Printing.PrinterExtension.Types;


namespace Microsoft.Samples.Printers.Extensions {


    public sealed class PrintHelper {

        private PrinterExtensionContext context;

        private IPrintSchemaCapabilities capabilities;

        private PrinterExtensionContext context;

        private Dictionary<string, List<IPrintSchemaOption>>

            featureOptions = new Dictionary<string,



        Public PrintHelper(Object context) {

           this.context = new PrinterExtensionContext(context);



        /// <summary>

        /// Returns the capabilities for the context's print ticket.

        /// This method only looks up the capabilities the first time

        /// it's called, and it returns the cached capabilities on

        /// subsequent calls to avoid making a repeated expensive call

        /// to retrieve the capabilities from the print ticket.

        /// </summary>

        private IPrintSchemaCapabilities Capabilities




                if (capabilities != null)


                    return capabilities;




                    if (context == null || context.Ticket == null)

                        return null;


                    capabilities = context.Ticket.GetCapabilities();

                    return capabilities;




        /// <summary>

        /// Get a list of options for a specified feature.

        /// </summary>

        /// <param name="feature">The feature whose options will be

        /// retrieved</param>

        /// <returns></returns>

        private List<IPrintSchemaOption> GetCachedFeatureOptions

            (string feature)


            if (false == featureOptions.ContainsKey(feature))


                // The first time this feature's options are

                // retrieved, cache a copy of the list

                featureOptions[feature] = Capabilities.GetOptions



            return featureOptions[feature];




        /// <summary>

        /// Determines whether the print capabilities contain the

        /// specified feature.

        /// </summary>

        /// <param name="feature">The feature to search the

        /// capabilities for</param>

        /// <returns>True if the capabilities contain the specified

        /// feature, False if the feature was not found</returns>

        public bool FeatureExists(string feature)


            if (string.IsNullOrWhiteSpace(feature))

                return false;


            if (Capabilities != null)


                IPrintSchemaFeature capsFeature = Capabilities.



                if (capsFeature != null)


                    return true;




            return false;



        /// <summary>

        /// Gets the index of the currently selected option in the

        /// list of options for a specified feature in the current

        /// print ticket

        /// </summary>

        /// <param name="feature">The feature whose currently selected

        /// option will be looked up</param>

        /// <returns>String-based representation of the index in the

        /// list of options of the specified feature's currently

        /// selected option</returns>

        public string GetSelectedOptionIndex(string feature)


            var currentFeature = context.Ticket.GetFeature(feature);

            var selectedOption = currentFeature.SelectedOption;

            var options = GetCachedFeatureOptions(feature);


            // Declare a Func that will be used to test whether the

            // currently selected option matches an option from the

            // list of options supported by the capabilities.  This

            // function will be different for features that require a

            // unique way to compare their options.

            Func<IPrintSchemaOption, IPrintSchemaOption, bool>

                optionComparer = null;


            if (String.Compare(feature,

                "JobNUpAllDocumentsContiguously") == 0)


                // If feature is JobNUpAllDocumentsContiguously,

                // verify whether both options have the same pages per

                // sheet count. This is the only valid way to compare

                // 2 NUp options since they don't have a valid Name

                // property.               

                optionComparer = (option1, option2) =>

                    ((IPrintSchemaNUpOption)option1).PagesPerSheet ==



            else if (String.Compare(feature, "PageMediaSize") == 0)


                // If the feature is PageMediaSize, verify whether

                // both options have the same actual media size

                // as well as the same Name and NamespaceUri, since

                // their Name alone may not be unique

                optionComparer = delegate(IPrintSchemaOption opt1,

                    IPrintSchemaOption opt2)


                    var option1 = (IPrintSchemaPageMediaSizeOption)


                    var option2 = (IPrintSchemaPageMediaSizeOption)


                    return (option1.HeightInMicrons ==

                        option2.HeightInMicrons &&

                        option1.WidthInMicrons ==

                        option2.WidthInMicrons &&

                        option1.Name == option2.Name &&

                        option1.XmlNamespace == option2.XmlNamespace);





                // For all other options, it's sufficient to compare

                // their Name properties

                optionComparer = (option1, option2) => option1.Name ==




            // Now that the appropriate comparer function has been

            // selected, iterate through all the options, find whether

            // the selected option matches one of them, and return its

            // index if it does

            for (int i = 0; i < options.Count; i++)


                if (optionComparer(options[i], selectedOption)== true)

                    return i.ToString();



            return null;



        /// <summary>

        /// Get an array of a specified type of option information

        /// items for a specified feature in print capabilities.

        /// This function is called by JavaScript to retrieve option

        /// display names and indeces

        /// </summary>

        /// <param name="feature">The feature whose options'

        /// information will be returned</param>

        /// <param name="infoTypeString">The type of information about

        /// the option to be looked up.  Valid strings include

        /// "DisplayName", and "Index"</param>

        /// <returns>An array of strings corresponding to each

        /// option's value for information of the specified

        /// type</returns>

        public string[] GetOptionInfo(string feature, string



            var options = new List<string>();


            if (string.IsNullOrWhiteSpace(infoTypeString))

                return null;


            // Parse infoTypeString to match it to an OptionInfoType

            OptionInfoType infoType;

            if (!Enum.TryParse(infoTypeString, out infoType) ||

                !Enum.IsDefined(typeof(OptionInfoType), infoType))

                return null;


            var schemaOptions = GetCachedFeatureOptions(feature);




                if (infoType == OptionInfoType.DisplayName)

                    // Select just the DisplayName from each option

                    options.AddRange(schemaOptions.Select(option =>


                else if (infoType == OptionInfoType.Index)

                    // Generate a range starting from 0 and equal in

                    // length to the number of options, and select

                    // each number in the range as a string


                        schemaOptions.Count).Select(index =>



            catch (System.Runtime.InteropServices.COMException e)


                // If an error was encountered, surface it to the

                // caller by putting the error information

                // into the returned option information

                options.Add("COMException: " + e.Message);


            return options.ToArray();




Save on Back button click

function onSaveRequested(eventArguments, sender) {

    request = eventArguments.request;


    // this code assumes that the orientation presented to the user

    // in a "select" HTML element



    // Go through all the feature select elements, look up the

    // selected option name, and update the context for each feature

    var orientationSelect = document.getElementByTagName("select");

    for (var i = 0; i < selects.length; i++) {

        if (selects[i].id === "scenarios" ||

            selects[i].id === "printerSelect") {

            // Skip the scenario selector that's part of the standard

            // sample template.  Also skip the hidden printer selector

            // from launch from tile scenario




            var index = selects[i].selectedIndex;

            var optionIndex = selects[i].options[index].value;

            var feature = selects[i].id;


            // Set the feature's selected option in the context's

            // print ticket. The printerExtensionContext object is

            // updated with each iteration of this loop

            printHelper.setFeatureOption(feature, optionIndex);


    try {

        if (request) {

            // Save the updated context.



         if (configuration) {

             // unsubcribe to the back button event




    } catch (exception) {

      sdkSample.displayError("onSaveRequested: " + exception.message);



    public sealed class PrintHelper {

        // Set a specified feature's selected option to the specified

        // option in the print ticket

        public void SetFeatureOption(string feature, string



            try {

                // convert the index from string to int

                var index = int.Parse(optionIndex);

                // Get the feature in the context's print ticket

                var ticketFeature = context.Ticket.GetFeature



                if (ticketFeature != null) {

                    // Look up the specified option in the print

                    // capabilities set it as the feature's selected

                    // option

                    var option = GetCachedFeatureOptions(feature)


                    ticketFeature.SelectedOption = option;


            } catch (System.Runtime.InteropServices.COMException) {




Debug issues in the app

Note that it is difficult to capture the PrintTicket as part of debugging process because bringing the debugger into focus will dismiss the flyout, and the app will lose all unsaved context. For advanced debugging, you will have to debug either on the VS Simulator or a remote machine. The simulator behaves like a remote machine that has the exact same setup as your local machine.

Print Notifications experience

The Print Notifications experience is the functionality a Metro style device app for printers provides when a user needs to be informed of important printer statuses such as a paper jam or low-ink statuses while printing. It is a full-screen experience that helps users understand and fix their printer problems. This experience can be used to communicate and help users through issues with their printers such as a paper jam, open printer door, low ink levels, or printer out-of-paper errors.



Windows listens for Bidi events from a printer when users print to the printer. When a Metro style device app for printers is installed and the app has opted-into the Background Tasks contract, the System Event Broker activates the app’s Background Task Handler. The app activation event contains Trigger Details, consisting of the printer name that caused the event and the XML data from the Async message. We recommend that the Background Task Handler saves the XML data into local storage for access by the app’s Presentation Layer. The Background Task Handler may respond by toasting to the user. Device manufacturers must implement Bidi to make the Print Notifications app work.


The Background Task Handler can choose to show Toast or save the PrintNotificationEventDetails to the local storage.

function showToast(title, body) {

        var notifications = Windows.UI.Notifications;

        var toastNotificationManager = Windows.UI.Notifications.ToastNotificationManager;

        var toastXml = toastNotificationManager.getTemplateContent(notifications.ToastTemplateType.toastText02);


        toastXml.selectSingleNode("/toast").setAttribute("launch", title);       


        var textNodes = toastXml.getElementsByTagName("text");




        var toast = new notifications.ToastNotification(toastXml);



Best practises:

  • Everything shown on the notifications experience should be related to the notification.
  • Use actual photos, videos, or illustrations of the device to help users quickly resolve an issue with their device.
  • When providing information about an issue, do not link to online or other support materials.

Develop device metadata


In order to get the Background Task Handler to work with the printer, the device metadata should specify the Background Task Handler in SoftwareInformation\SoftwareInfo.xml.



       <DeviceNotificationHandler EventID="PrintNotificationHandler" EventAsset="backgroundtask.js"/>


Your printer must use a v4 printer driver to take advantage of the Advanced Print Settings and Printer Notifications experiences. V3 printer drivers can have the Metro style device app for printers only for the Start tile activation. Advanced Print Settings and Printer Notifications experiences will not work for v3 printer drivers.

Printer Extension Library

The Visual Studio project PrinterExtensionLibrary wraps the COM implementation of the COM interface PrinterExtensionLib.


Brak komentarzy: