Hello,

welcome to my blog of various technical solutions, experiences and tutorials.

Please feel free to use the Archive menu to go through my articles.

Thursday, January 26, 2012

4. Propagating UI states to PrintTicket

In last article we learned how to design an user interface (UI) for our XPS filter - simple setting page with checkbox, that was supposed to control the Reverse filter usage. Today we look at how to ensure that the checkbox state will be propagated to the PrintTicket (an XML file carrying all the print settings).

This is going to be a little bit messy and demanding, so please work carefully, as even a tiny mistake can result in complete malfunction of the driver.

Ready?

  1. Go to [sample root]\src\ui and open xdsmpldlg.rc for editing (I recommend you to open it in MS Visual Studio). Open its String Table and add following records to it. The IDs and string values are not so much important (they will probably be displayed in the list of printer capabilities), but be careful to ensure each record has its unique numerical Value to prevent conflicts, that can result in driver malfunction. The best practice is to use numbers a bit further behind the last one used. Here I started with value of 2200.
      IDS_GPD_REVERSETITLE   2200   "Revert page order"
      IDS_GPD_NOREVERSAL     2201   "No reversal"
      IDS_GPD_REVERSAL       2202   "Reversal"

  2. Now go to [sample root]\install and open xdnames.gpd for editing. Go to the end of file and add those new strings to the list of StdFeatureNames. You should have something like this:
      IDS_GPD_REVERSETITLE:     RESDLL.xdsmplui.2200
      IDS_GPD_NOREVERSAL:       RESDLL.xdsmplui.2201
      IDS_GPD_REVERSAL:         RESDLL.xdsmplui.2202
    (The RESDLL macro says the driver to look up the xdsmplui.dll for values of those strings)
  3. Create a new file in [sample root]\install and name it xdreverse.gpd. Copy and paste the following code in it. This is a so called "Generic Printer Description" script which defines one of printer's capabilities.

    *% File Name:
    *%
    *%    xdreverse.gpd
    *%
    *% Abstract:
    *%
    *%    Reverse filter feature specific GPD settings.
    *%

    *%***********************************************************
    *%                            Reverse
    *%***********************************************************
    *%  rcNameID      feature name listed in Printer Capabilities
    *%***********************************************************
    *Feature: PageReversal
    {
        *rcNameID: =IDS_GPD_REVERSETITLE
        *DefaultOption: Reversal
        *PrintSchemaKeywordMap: "PageReversal"

        *Option: NoReversal
        {
            *rcNameID: =IDS_GPD_NOREVERSAL
            *PrintSchemaKeywordMap: "NoReversal"
        }

        *Option: Reversal
        {
            *rcNameID: =IDS_GPD_REVERSAL
            *PrintSchemaKeywordMap: "Reversal"
        }
    }

    It says that we define a new printer feature - PageReversal (this name is very important as we will use it in source code), which has two options (we use checkbox, aren't we? So two options are pretty enough). The Reversal option is taken as default (that means our checkbox is checked by default and the Reverse filter is ON). Keep in mind that the feature (so as both options) needs to have a unique keyword provided in PrintSchemaKeywordMap identifier. This keyword will be used in PrintTicket.
    You can save and close this file now. Please make sure that the PageReversal name is used in revctrls.cpp file.
  4. Open [sample root]\install\xdsmpl.inf for edit and perform following tasks:
    • add xdreverse.gpd in [XPSDrvSample] section
    • add xdreverse.gpd = 1 in [SourceDisksFiles] section
  5. Now open [sample root]\src\ui\uiproperties.cpp and add the following on line 198:

      m_OptItemList.push_back("PageReversal");

    This will add our new feature to the driver's internal feature list, which ensures that our checkbox will get it's default value (defined in xdreverse.gpd file) when the user clicks on Reverse setting page in advanced print dialog and will also "communicate" with UniDrv to update PrintTicket when it's state changes.
  6. Save all files and rebuild and reinstall the driver (as we have changed its sources).
  7. Now when you open the print dialog, go to Advanced and open the Reverse page, you should see your checkbox checked (or unchecked in case you changed the default value in GPD file to NoReversal).
  8. Try to print some document. When it's done, change the output xps file's extension to zip and go inside and lookup \Metadata\Job_PT.xml file. This is the PrintTicket associated to your printed document which controlled its printing. Open it for reading (in Notepad, for example). You should be able to find the following near the end of the file:

      <psf:Feature name="psk:PageReversal">
            <psf:Option name="psk:NoReversal"/>
      </psf:Feature>

  9. If so, congratulations! Your PrintTicket knows about your checkbox and its state now. But one thing still remains - we have to edit our Reverse filter sources for it to follow the PrintTicket settings and control its function. See you in next part of this tutorial, which is hopefully coming very soon :)
Should you not find the piece of XML text listed in step 8, be sure to revise all the steps carefully with stress on PageReversal attribute as it is one of the main identifiers, which all the action is controlled by.

HINT:  If you would like to edit your GPD file after installation (e.g. to change your default option of PageReversal feature), you can do it without subsequent driver reinstallation. You just need to know where to find it - the xdreverse.gpd file (together with all the others) resides in c:\windows\system32\spool\drivers\w32x86\3 (in case you use 64 bit Windows go to ...\drivers\x64\3 instead).

Wednesday, January 25, 2012

3. Creating XPS filter settings page

Welcome.
Today's tutorial will guide you through the process of creating filter settings page for the Reverse filter we created in last article. The page will be displayed as a part of the regular system dialog after clicking the "Advanced" button for our sample XPS driver and will be very simple as it will contain just one checkbox (for enabling/disabling the reverse function).
The settings, however, will have no effect yet - the filter will be always active. Connecting the settings to the filter will be discussed in some future article.

Unlike the previous article, today's all work will be done only in [sample root]\src\ui directory.

  1. Open [sample root]\src\ui\xdsmpldlg.rc resource file for edit (in MSVC). 
    1. Right-click the Dialog tree item, choose "Add resource..." and create new dialog. In Properties set this dialog's ID to IDD_REVERSE. Then also make following changes:
      • set 3D look to true
      • set Border to none 
      • set Style to Child
      • set System menu to false
      • set Disabled to true
    2. Now add a checkbox control to the dialog. Call it IDC_CHECK_REVERSE and let its caption be "Revert page order".
    3. Go back to resources tree and double click the String table [English (U.S.)] to open it. Choose "New string" on right click to add new string into table. Call it IDS_REVERSE and let its caption be "Reverse". This string will represent the title of our new settings page.
  2. That's all for visual design. Now let's move to the second part - coding. To follow the style of coding of the other filters, we now have to create 4 new files in current ui directory:
    • revctrls.h
    • revctrls.cpp
    • revppg.h
    • revppg.cpp
    Every settings page must have its own class (inherited from CDocPropPage); what's more - even each control on the setting page must do so (and inherit from default control class (CUICtrlDefaultCheck in our case)). Thus, the first two files (their abbreviated names stand for ReverseControls) will contain the class declarations and definitions for every control used on our page (fortunately we have just one - checkbox), while the other two files (the name stands for ReversePropertyPage) will contain the declarations and definitions for the page class.
    • Every control class must contain: constructor, virtual destructor, private static string. If the control need some initialisation (e.g. setting default value), the class must also contain an overriden OnInit method.
    • Every page class must contain constructor, virtual destructor and overriden method InitDlgBox (which will set the dialog UI template and title).
  3. Open xdsmplui.cpp for edit and add the #include "revppg.h" line to the beginning (right after the line 31 where the ftrppg.h file is included). Go to line 920 inside the CreatePropertyPages function and add the following CReversePropPage class instance creating:

    if (SUCCEEDED(hr))

    {

       pPropPage = new CReversePropPage();

       if (SUCCEEDED(hr = CHECK_POINTER(pPropPage, E_OUTOFMEMORY)))
       {
           m_vectPropPages.push_back(pPropPage);
       }
    }


    This code snippet creates a new instance of our setting page and adds it in the pages vector if successful. When user opens the "Advanced" dialog of our XPS printer (in Windows Control Panel), the system calls DocumentPropertySheets function where the content of this vector is processed (PropPageInit function is then called on every page which invokes the provided InitDlgBox function of each page).
  4. We are almost done here. Now open the sources file and add revctrls.cpp and revppg.cpp to SOURCES variable for these files to get compiled.
  5. Run x86 build environment (for Vista or later), type build -cZ, hit enter.
  6. Reinstall the driver of our XPS sample printer.
  7. Open any document and call print dialog. After selecting our sample XPS printer, click "Advanced" button and see the new tab with your brand new settings page :)
Sourcesrevctrls.hrevctrls.cpp, revppg.h, revppg.cpp

Tuesday, January 24, 2012

2. Implementing custom simple XPS filter

Hi,


in this article I will show you what to do if you want to create your own XPS print filter and use it in the existing XPSDrvSmpl pipeline. Thanks to the modular structure of pipeline it's quite easy to do so :)

Prerequisities
This article assumes that:
  • you are at least a bit familiar with XPS, its pipeline and the code used in XPSDrvSmpl from WDK
  • you have WDK installed and XPSDrvSmpl built (see my previous post "Building and using XPSDrvSmpl Under Windows XP"))
  • you have Windows SDK Tools installed (or at least have the GUID Generator utility)

So, we are going to create a quite simple filter - its only purpose will be reverting the order of pages in document being printed. That means if you print a document (for example from MS Word) with 10 pages, this filter will make the order of the pages go descending (from 10 to 1). It will be used always (during every printing) as we will not provide any setting possibilities yet.

We will use this filter (let's call it Reverse) as the first one in the pipeline, before any other changes are made to the document.
Here are all the steps we need to take before we proceed to coding (of course we can code the filter first and then take all the other steps, but I believe the order presented here will be better for the article structure ;) )

(Note: throughout the steps I often use the term "sample root" - it denotes the directory in which you have the XPSDrvSmpl sample copied, e.g. c:\projects\xpssample or its original location in WDK directory or whatever else it is)
  1. Ok, let's move on the first step. Go to [sample root]\src\filters and create a new subfolder here, called reverse.
  2. Go to Start menu and choose Programs - Windows SDK Tools and run GUID Generator utility. Generate new unique ID (it should read {8-4-4-4-12 hex chars}, for example {AA356934-2EC6-43af-BF20-05F05D2448D6}), copy it to clipboard and keep somewhere for later use.
  3. You need to edit the XPS print pipeline itself and add the new (yet to come) filter at the first position (to revert pages before any other action is performed).
    Open [sample root]\install\xdsmpl-PipelineConfig.xml for edit. Move behind the Filters opening tag and paste the following code (explanation below):

     <Filter dll   = "XDReverse.dll"
            clsid  = "<your generated GUID from step 2>"
            name   = "Reverse Filter">
            <Input guid  = "{b8cf8530-5562-47c4-ab67-b1f69ecf961e}" comment="IID_IXpsDocumentProvider"/>
            <Output  guid  = "{4368d8a2-4181-4a9f-b295-3d9a38bb9ba0}" comment="IID_IXpsDocumentConsumer"/></Filter>

    The value of dll attribute denotes the name of our (yet to come) filter dynamic library. The clsid attribute should contain your generated GUID exactly in the form which it has in your clipboard. The name can be whatever you want, but should be related to the function of the filter. Please note that there is a closing bracket after the name value.
    Since our filter is of XPS type (not the Stream one), it uses already existing XPS input and output providers with given GUIDs. If you look at the other filters in the pipeline, almost all of them (except the Scale filter, which uses Stream) use XPS interface and thus have the same input and output interfaces. Just copy and paste this items and don't worry about it ;)
  4. Go to [sample root]\install\ and open xdsmpl.inf for edit.
    1. Find the [XPSDrvSample] section and add the xdreverse.dll line to it.
    2. Find the [SourceDisksFiles] section and add the xdreverse.dll = 2 line to it.
    The former line tells the install script that we want to include our dll in the installation. The latter says where the installator should look for it.
  5. Add two new empty files to [sample root]\src\filters\reverse directory (we will fill them later):
    1. reverse.h
    2. reverse.cpp
  6. You need to add the dllentry.cpp file. But it will be almost the same as in the other filters, so go to some other filter directory (e.g. booklet) and copy the dllentry.cpp file from here to the reverse directory. Open it and make following edits:
    1. on the line 28 replace the bkflt.h include with reverse.h (for the new dll to know about our filter class)
      #include "bkflt.h" ==> #include "reverse.h"
    2. Move down to the end of the file and look for DllGetClasObject function. At the top of the function there are commented lines with the GUID of the Booklet filter. I recommend you to replace this GUID with your own (generated in step 2) to easily make following changes.
    3. Rename the bookletCLSID variable (e.g. to reverseCLSID).
    4. Now follow the way of transforming the GUID in this variable exactly and replace the hexadecimal values with your own. Provided your generated GUID is  {AA356934-2EC6-43af-BF20-05F05D2448D6}, you should get something like this:

      CLSID reverseCLSID = {0xAA356934, 0x2EC6, 0x43af, {0xBF, 0x20, 0x05, 0xF0, 0x5D, 0x24, 0x48, 0xD6}};
    5. On the return line change the parameter of GetFilterClassFactory from CBookletFilter to CReverseFilter. Then change the third parameter from bookletCLSID to reverseCLSID. Save the file and close it. Now the dll interface knows (by the changed GUID) which filter to handle when it gets called.

  7. Add sources file (no extension). It is similar to the way of obtaining dllentry.cpp in previous step. Just copy it from some other filter. Then open it and:
    1.  edit the SOURCES variable to have only two files: reverse.cpp and dllentry.cpp (these files will be compiled when build is run). So it should read

      SOURCES=$(SOURCES) \
       dllentry.cpp      \
       reverse.cpp       \

      Bear the backslashes in mind! Keep an empty line after reverse.cpp.
    2. edit the TARGETNAME variable value to xdreverse (or whatever name you wish the created dll should have. But keep in mind that the same name must be listed in the filter pipeline file!). Save the file and close it.
  8. Copy the makefile and makefile.inc files from other filter without any other changes made.
  9. Copy xdbook.inc from the booklet filter directory, rename it to xdreverse.inc, open it and change the value of LIBRARY parameter from xdbook.inc to xdreverse.inc
  10. You are now ready to write the reverse-filter source code and build the driver. The source code is available through my Google Documents storage. It is short and clear as it contains only the class definition and two overriden functions and will probably be discussed later in the future.
    Source code: reverse.h, reverse.cpp
  11. Run x86 checked build utility from Start - Programs - Windows Driver Kits - WDK [version] - Windows Vista and Windows Server 2008 (or higher). After the console comes up, use cd command to move to your [sample root] directory, write build -cZ and hit Enter.
  12. Hurray! Your driver is built and ready to use. Now install (or reinstall) the driver following the steps in Part 1 of this tutorial:)

1. Building and using XPSDrvSmpl under Windows XP

Microsoft Windows Driver Developer Kit (DDK/WDK) comes with a sample of XPS print driver. This post will tell you what to do to build it and use it under WinXP.

(XPS print path isn't supported in standard Windows XP, you need to have either SP3 with .Net3.0, MS XPS Essentials Pack or this MS hotfix installed in order to use it. We will assume that you have this done)

Having WDK downloaded (for free) and installed, you should find this sample at [WDK root]\[WDK version]\src\print\XPSDrvSmpl folder (e.g. c:\WinDDK\7600.16385.1\src\print\XPSDrvSmpl). Further you should see some records in your Start menu under Programs/Windows Driver Kits. This folder and its content is important, as it provides building environments for various MS operating systems (XPSDrvSmpl must be built using at least Vista build environment, as it is not supported in XP and trying to build the sample using XP build environment will result in lot of errors and no output libraries).

So let's start:
  1. Go to Start - Programs - Windows Driver Kits - WDK [version number] - Build environments and choose Windows Vistaand Windows Server 2008 - x86 checked environment (the checked environment should provide more information at output when errors occur; it's something like Debug/Release configuration in MSVC). A console should start.
  2. If you have copied the XPS sample to a custom directory, use cd command to go there (e.g. cd c:\projects\xpsdrvsmpl). From now on I will call this directory a sample root.
  3. Type build -cZ and hit Enter. After few moments the build should end successfully (without any error and warning) telling you the count of files compiled and libraries and executables built. Now your new dll output files are located in [sample root]\install\x86 directory.
  4. We can now proceed to new XPS printer installation.
    Go to Start - Control Panel - Add new printer. Then choose Local printer and Create new port. Select Local Port in combo box. The dialog asking for a port name will appear. For our XPS driver to print to specified file without asking a user for a location, write in full path for this output file. This file need not to exist at the moment (something like c:\mydriveroutput.xps for example).
  5. Now when you see the list of vendors and devices, click the "From disk" button. Then select [sample root]\install\xdsmpl.inf and click Next. This will load the printer installing instructions.
  6. As you go on, the wizard can ask you for some files it cannot find. But this should be no big trouble if you have SP3 or XPS Extension Pack installed.
    If the wizard asks you for xdsmpl.gpd, click Browse and find this file in [sample root]\install\x86.
    If it asks for xdwmark.dll, navigate to [sample root]\install\x86 as well.
    For msxpsinc.gpd go to c:\windows\Driver cache\i386.
    The xpssvcs.dll file should be found in c:\windows\system32.
  7. And that's it. The printer driver should now be installed and ready to use. Now go to any of your favourite application and under File - Print menu choose XPSDrv Sample Driver. The output XPS file is the one you provided aas the port name at the beginning of printer installation (e.g. c:\mydriveroutput.xps)