Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

A Small Class for Saving and Restoring User's Printer Settings

4.67/5 (2 votes)
22 Jan 2014CPOL4 min read 12.4K   342  
A generic template class that wraps the GlobalAlloc API

Introduction

This is simple drop in C++ template class that makes it easy to save and restore a user's printer selection and settings. It does so by wrapping the HDEVMODE and HDEVNAMES structures. This class is a template class so it can be used to save and restore any memory block that has been allocated by the GlobalAlloc API.

Background

I like to write simple easy to use tools that are not overly complicated or confusing to my users. One of the things I find most often is that users usually always print to the same printer using the same settings. Myself, I like to print most documents in black and white, and I find it annoying having to go through the print dialog options to reselect my printer and its options every time I print. So there must be a way to persist my settings so that everything stays the same unless I want to change it.

For printing, the way to accomplish this is to save and restore the HDEVNAMES and HDEVMODE structures that are used by windows and the printer drivers. Not always an easy task due to the variable amount of extra data that is often allocated onto the end of these structures.

The class presented here is my solution to this problem. My naming skills are not very imaginative so the name I came up with is MyGHND. "My" because I wrote it, and "GHND" because that is the flag I used with the GlobalAlloc API function when allocating memory within this code. One of the nice things about this class is that it is a generic template class so it can actually be used by any block of data that has been or needs to be allocated by the GlobalAlloc API.

Using the Code

First, I am going to show you some code that uses this class. This first code block uses the MFC CPrintDialog to show a Print Setup dialog. It gets initialized to either the previously saved settings or to the system default settings if there are no saved settings or the saved settings are somehow corrupted.

C++
void CSchedDlg::OnBnClickedButtonPrintSetup()
{
    // Print Setup Dialog
    CPrintDialog PD(TRUE);
 
    // SchedData.GetString() reads a text string from an XML file
    // Here we show the MyGHND c'tor that takes a text string
    MyGHND<DEVMODE> GDevMode(SchedData.GetString(L"DevMode"));
 
    // Check if the DEVMODE structure is valid
    if (GDevMode)
    {
        // make the hDevMode of the PRINTDLG structure point to our DEVMODE
        PD.m_pd.hDevMode = GDevMode;
 
    // Here we use the default c'tor
    MyGHND<DEVNAMES> GDevNames;
 
    // And then the assignment operator that takes a text string
    GDevNames = SchedData.GetString(L"DevNames");
    if (GDevNames)
    {
        PD.m_pd.hDevNames = GDevNames;
    }
 
    if (IDOK == PD.DoModal())
    {
        // User pressed OK so we save our settings

        // Reinitialize our DEVMODE to the one in the PRINTDLG
        GDevMode = PD.m_pd.hDevMode;
 
        // SchedData.SetString saves a text string to a XML file
        SchedData.SetString(L"DevMode", GDevMode);
 
        GDevNames = PD.m_pd.hDevNames;
        SchedData.SetString(L"DevNames", GDevNames);
    }
}

This next code block shows how to create a printer device context using the saved data.

C++
void CSchedDlg::OnBnClickedButtonPrint()
{
    // Print a test page

    // Load the DEVNAMES and DEVMODE structures from the XML file
    MyGHND<DEVMODE> GDevMode(SchedData.GetString(L"DevMode"));
    MyGHND<DEVNAMES> GDevNames(SchedData.GetString(L"DevNames"));
    
    // Check if they are valid
    if (GDevNames && GDevMode)
    {
        // Get the pointer to our DEVNAMES
        LPDEVNAMES pDevNames = GDevNames;
    
        // And extract the device names from it
        std::wstring Driver = (LPWSTR)pDevNames + GDevNames->wDriverOffset;
        std::wstring Device = (LPWSTR)pDevNames + GDevNames->wDeviceOffset;
        std::wstring Output = (LPWSTR)pDevNames + GDevNames->wOutputOffset;
 
        // Create the printer DC and print
        CDC PrinterDC;
        if (PrinterDC.CreateDCW(Driver.c_str(),
                                Device.c_str(),
                                Output.c_str(),
                                (LPDEVMODE)GDevMode))
        {
            PrinterDC.StartDocW(L"Test Document");
            PrinterDC.StartPage();
            PrinterDC.TextOutW(0, 0, L"Hello World");
            PrinterDC.EndPage();
            PrinterDC.EndDoc();
            return;
        }
    }
 
    MessageBox(L"First Select a Printer using \"Print Setup...\"",
               L"Unable to Print",
               MB_OK | MB_ICONINFORMATION);
    return;
}

While this code used a SetString() and a GetString() function, any method used to set or get text strings from any type of storage can be used. It could be simple text files, the registry, or even a database of some sort. The text string used to save the data will only ever contain printable ASCII characters. It will not have any carriage return, new line, nor NULL characters in it. The method you choose to use to save and read these text strings is beyond the scope of this article.

The MyGHND Class Members

Constructors

The MyGHND class has five constructors:

C++
template <typename TYPE>
class MyGHND
{
public:
    // Default c'tor. Allocate sizeof(TYPE) bytes
    MyGHND (bool AutoFree = true);

    // Allocate specified number of bytes
    MyGHND (size_t ByteCount, bool AutoFree = true);

    // AutoFree is false because we did not allocate the memory, so do not free it.
    MyGHND (HGLOBAL Object, bool AutoFree = false);

    // copy c'tor
    MyGHND (const MyGHND<TYPE>> &Other, bool AutoFree = true);

    // Decode an encoded text string
    MyGHND (std::tstring EncodedString, bool AutoFree = true);

Parameters

AutoFree specifies if the GlobalFree API function should be called on the HGLOBAL handle when it is no longer needed. It defaults to true for most of the constructors since this class allocates the memory it should be responsible for freeing it. The only time it defaults to false is when an existing HGLOBAL handle is passed in. Since this class did not allocate the memory, it should not delete it.

ByteCount specifies the amount of memory, in bytes, that is to be allocated.

Object is a previously allocated HGLOBAL handle that is to be wrapped by this MyGHND object.

Other is a MyGHND object that will be copied into this new MyGHND object.

EncodedString is a std::tstring that was previously encoded by the ToString member function. This string will be decoded and the data entered into this MyGHND object.

Four of the five constructors can throw a std::runtime_error exception if the GlobalAlloc function fails. The only constructor that does not throw an exception is the one that takes an HGLOBAL handle as it does not call GlobalAlloc.

Destructor

C++
public: ~MyGHND ()

The destructor will call GlobalUnlock() on the locked handle, and if AutoFree (specified in the constructor) is set to true, the destructor will call GlobalFree to free the memory allocated.

Memory Management Functions

MyGHND has three memory management functions.

C++
public:
    // calls GlobalAlloc(HGND, ByteCount) to allocate the memory
    void Init(size_t ByteCount);
    
    // calls GlobalFree() to free the memory
    void Free();
    
    // get the amount of memory allocated, in bytes
    size_t Size();

Init() will first free any memory that had been previously allocated before allocating the new memory. It can throw a std::runtime_error if the GlobalAlloc function fails.

Be careful when using Free() as it will call GlobalFree regardless of the state of the AutoFree flag (specified in the constructor). A safer way to clear the memory is to call Init(0) with the ByteCount set to zero.

Size() simple returns the size of the memory block allocated, in bytes. The return value can be larger then the value called for in Init().

Type Casting Operators

There are five type casting operators in MyGHND:

C++
public:
    // return a pointer to TYPE
    operator TYPE * ();
    
    // Allow access to TYPE's members
    TYPE * operator -> ();
    
    // get the HGLOBAL handle
    operator HGLOBAL ()
    
    // Get an encoded string in order to serialize this object
    operator std::tstring ();
    
    // check the validity of this object
    operator bool ();

The bool() operator returns false if the underlying handle is NULL or the size of the memory block is zero.

Assignment Operators

MyGHND has five assignment operators. They all make a copy of the data, leaving the original data untouched.

C++
public:
    // copy the data 
    MyGHND<TYPE> & operator =(const TYPE *Pointer);

    // copy the data
    MyGHND<TYPE> & operator =(const TYPE &Reference);

    // copy the data
    MyGHND<TYPE> & operator =(const MyGHND<TYPE> &Other);

    // copy the data
    MyGHND<TYPE> & operator =(const HGLOBAL OtherGlobalHandle);

    // Decode and assign the EncodedString
    MyGHND<TYPE> & operator =(const std::tstring &EncodedString);

Serialization Functions

C++
public:
    // Encode the data to a text string and return the encoded string.
    std::tstring ToString();
    
    // Take the previously encoded string and initialize this object with it.
    bool FromString(const std::tstring &EncodedString);

ToString returns the encoded string. The encoded string will be empty if an error was encountered.

FromString returns true if successful or false if the supplied string was invalid or another error occurred.

History

  • January 22, 2014 - Article published 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)