Introduction
Based on an idea by Nicholas Piasecki
This article shows how you can save printer settings of any printer to a file or an arraylist to be recalled later.
Background
I have been working on a C#.NET application for the design of radio receivers for a while.
The application has a print facility to print various pages of text, graphs and a scale so you can see what you’re tuned into.
The print system allows you to select the standard print features such as copies, collate, colour, paper source, etc. If you need to select a particular function specific to your printer, then there is a “Printer properties” button which brings up the setup dialogue for the selected printer.
There is a section of the form where the printer settings can be stored in “Memories” to be squirreled away for later. Wouldn’t it be nice if the “Memories” could store all the print settings including those not accessible from the printer settings class.
What do you mean “not accessible from the printer settings class”?
Settings such as Orientation, Resolution, Paper source, copies, etc. are held in what is called a “Devmode structure”. There are two parts to the structure:
- A public area holding data for Orientation, Resolution, Paper source, copies, etc. This can be accessed through the printer settings class in .NET. Any printer has the ability to recognise settings for Orientation, Resolution, Paper source, copies, etc.
- A Private area holding data for settings specific to a particular printer. Paper stapling, folding, best photo mode, feeding the dog, putting out the cat, reminding you of your wedding anniversary, etc. There are no settings in the printer settings class to handle these.
The data in the structure is held in contiguous memory, the public area first, immediately followed by the private area. If I wanted to store the data in the private area, I was stuffed without a definition of the data in the private area (which, being printer specific, will vary from printer to printer). I did not want to manipulate the data, just store and recall it. There is a definition for the public part of the structure and can be found here.
I want to be able to store all the printer settings, for any installed printer, The PROBLEM, is (sorry, was – let’s get the tense right!) how to do it.
I have found a solution to the problem based upon an idea by Nicholas Piasecki who had an “Off the wall idea” Look at this link to see how he did it.
The idea of storing the data (the devmode
structure) in a file and then when necessary, reload the file, overwrite the devmode
structure in memory with the one on file is the way I went.
I have added a few twists but the idea is the same and therefore credit must go to Nicholas Piasecki as it was his idea.
The best part I think is that the devmode
definition is not needed as I do not need to know what the variables names are or indeed the values, because I do not want to alter them. Just squirrel away copies of the data as a block, not as a collection of variables. The original version did use the devmode
definition so you could find the values of dm_Size
and dm_Extra
variables. dm_Size
is the size of the public part of the devmode
structure, (on my Win 7 laptop this is 220 bytes regardless of printer), dm_Extra
is the size of the private area of the devmode
structure which varies from printer to printer. Both of these values are added together to allocate the right amount of memory. This is the “Size needed”. If you look at the printersettings
class, there is a method called “GetHdevmode
”, this will return the FULL size of the devmode
structure! With my printers, there was no difference between GetHdevmode
or dm_Size
+ dm_Extra
, so I did away with the definition and used GetHdevmode
instead, much easier.
Using the Code
There are three main methods.
The first opens up the printer dialogue (the one supplied by the printer manufacturer) so you can change the settings of the selected printer, including the printer specific ones.
The methods require the following platform invokes in order to work:
[DllImport("winspool.Drv",
EntryPoint = "DocumentPropertiesW",
SetLastError = true,
ExactSpelling = true,
CallingConvention = CallingConvention.StdCall)]
static extern int DocumentProperties
(IntPtr hwnd, IntPtr hPrinter,
[MarshalAs(UnmanagedType.LPWStr)]
string pDeviceName,
IntPtr pDevModeOutput,
IntPtr pDevModeInput,
int fMode);
[DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GlobalFree(IntPtr handle);
[DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GlobalLock(IntPtr handle);
[DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GlobalUnlock(IntPtr handle);
The first method brings up the printer dialogue box:
private PrinterSettings OpenPrinterPropertiesDialog(PrinterSettings printerSettings)
{
IntPtr hDevMode = IntPtr.Zero;
IntPtr devModeData = IntPtr.Zero;
IntPtr hPrinter = IntPtr.Zero;
String pName = printerSettings.PrinterName;
try
{
hDevMode = printerSettings.GetHdevmode(printerSettings.DefaultPageSettings);
IntPtr pDevMode = GlobalLock(hDevMode);
int sizeNeeded = DocumentProperties(this.Handle, IntPtr.Zero, pName,
pDevMode, pDevMode, 0);
if (sizeNeeded < 0)
{
MessageBox.Show("Bummer, Cant get size of devmode structure");
Marshal.FreeHGlobal(devModeData);
Marshal.FreeHGlobal(hDevMode);
devModeData = IntPtr.Zero;
hDevMode = IntPtr.Zero;
return printerSettings;
}
devModeData = Marshal.AllocHGlobal(sizeNeeded);
int returncode = DocumentProperties
(this.Handle, IntPtr.Zero, pName, devModeData, pDevMode, 14);
if (returncode < 0)
{
MessageBox.Show("Dialogue Bummer, Got me a devmode, but the dialogue got stuck");
Marshal.FreeHGlobal(devModeData);
Marshal.FreeHGlobal(hDevMode);
devModeData = IntPtr.Zero;
hDevMode = IntPtr.Zero;
return printerSettings;
}
if (returncode ==2)
{
GlobalUnlock(hDevMode);
if (hDevMode != IntPtr.Zero)
{
Marshal.FreeHGlobal(hDevMode);
hDevMode = IntPtr.Zero;
}
if (devModeData != IntPtr.Zero)
{
GlobalFree(devModeData);
devModeData = IntPtr.Zero;
}
}
GlobalUnlock(hDevMode);
if (hDevMode != IntPtr.Zero)
{
Marshal.FreeHGlobal(hDevMode);
hDevMode = IntPtr.Zero;
}
if (devModeData != IntPtr.Zero)
{
printerSettings.SetHdevmode(devModeData);
printerSettings.DefaultPageSettings.SetHdevmode(devModeData);
GlobalFree(devModeData);
devModeData = IntPtr.Zero;
}
}
catch (Exception ex)
{
MessageBox.Show("An error has occurred, caught and chucked back\n" + ex.Message);
}
finally
{
if (hDevMode != IntPtr.Zero)
{
Marshal.FreeHGlobal(hDevMode);
}
if (devModeData != IntPtr.Zero)
{
Marshal.FreeHGlobal(devModeData);
}
}
return printerSettings;
}
The second method gets the devmode
data and either saves it to a file or to an arraylist
depending upon the value of a control variable. This method also uses the same platform invokes as before.
private void GetDevmode(PrinterSettings printerSettings, int mode, String Filename)
{
IntPtr hDevMode = IntPtr.Zero;
IntPtr pDevMode = IntPtr.Zero;
IntPtr hwnd = this.Handle;
try
{
hDevMode = printerSettings.GetHdevmode(printerSettings.DefaultPageSettings);
pDevMode = GlobalLock(hDevMode);
int sizeNeeded = DocumentProperties
(hwnd, IntPtr.Zero, printerSettings.PrinterName, IntPtr.Zero, pDevMode, 0);
if (sizeNeeded <= 0)
{
MessageBox.Show("Devmode Bummer, Cant get size of devmode structure");
GlobalUnlock(hDevMode);
GlobalFree(hDevMode);
return;
}
DevModeArray = new byte[sizeNeeded];
if (mode == 1)
{
FileStream fs = new FileStream(Filename, FileMode.Create);
for (int i = 0; i < sizeNeeded; ++i)
{
fs.WriteByte(Marshal.ReadByte(pDevMode, i));
}
fs.Close();
fs.Dispose();
}
if (mode == 2)
{
for (int i = 0; i < sizeNeeded; ++i)
{
DevModeArray[i] = (byte)(Marshal.ReadByte(pDevMode, i));
}
}
GlobalUnlock(hDevMode);
GlobalFree(hDevMode);
hDevMode = IntPtr.Zero;
}
catch (Exception ex)
{
if (hDevMode != IntPtr.Zero)
{
MessageBox.Show("BUGGER");
GlobalUnlock(hDevMode);
GlobalFree(hDevMode);
hDevMode = IntPtr.Zero;
}
}
}
The final method takes the devmode
data from either a file on your HDD or from the arraylist
, again depending upon a control variable and overwrites the structure in memory.
private void SetDevmode(PrinterSettings printerSettings, int mode, String Filename)
{
IntPtr hDevMode = IntPtr.Zero;
IntPtr pDevMode = IntPtr.Zero;
Byte[] Temparray;
try
{
DevModeArray = CurrentSetup.Devmodearray;
hDevMode = printerSettings.GetHdevmode(printerSettings.DefaultPageSettings);
pDevMode = GlobalLock(hDevMode);
if (mode == 1)
{
FileStream fs = new FileStream(Filename, FileMode.Open, FileAccess.Read);
Temparray = new byte[fs.Length];
fs.Read(Temparray, 0, Temparray.Length);
fs.Close();
fs.Dispose();
for (int i = 0; i < Temparray.Length; ++i)
{
Marshal.WriteByte(pDevMode, i, Temparray[i]);
}
}
if (mode == 2)
{
for (int i = 0; i < DevModeArray.Length; ++i)
{
Marshal.WriteByte(pDevMode, i, DevModeArray[i]);
}
}
GlobalUnlock(hDevMode);
printerSettings.SetHdevmode(hDevMode);
printerSettings.DefaultPageSettings.SetHdevmode(hDevMode);
GlobalFree(hDevMode);
}
catch (Exception ex)
{
if (hDevMode != IntPtr.Zero)
{
MessageBox.Show("BUGGER");
GlobalUnlock(hDevMode);
GlobalFree(hDevMode);
hDevMode = IntPtr.Zero;
}
}
}
The remaining methods handle the arraylist
, updating the form and a comparator.
The comparator allows to display two devmode
data sources (either from an arraylist
, datafile
or both) and look at the differences between them. The results are displayed in a text box.
This solution is not perfect, but with the printers I have installed on my system, does appear to work. The solution is not intended as a standalone application to work alongside an application with print facilities, but can be adapted to work in your own application (that is assuming your application needs to drive a printer)!
I have tried this with my “Canon MP810” and was able to store and recall the settings that controlled:
- Preview before print
- Paper type, (plain, glossy photopaper, compact disk, T shirt transfers, etc.
- Sepia toning
- Image optimiser
- Photo optimise Pro and so on
I have also tried this on PDF creator (which installs itself as a printer) with great success.
However, “Send to one note” was a bit risky. If you set the print area from the Send to one note dialogue, save, recall, then have a look at the dialogue again, the print area is sometimes zero. I have since found out that if the print area is altered from the one note driver, the papersize becomes “Custom”. When this is passed back to printersettings
class, the default width and height (Zero) are passed back, not the ones you have typed in. I have had to handle custom page sizes in a slightly different way.
If the arraylist
is used, other data is stored as well including print quality, paper size, printer name and so on. If you choose not to use the devmode
structure by setting “No (mouse)”, these settings are used instead. If you select “Yes (Man)”, the devmode
structure is used instead, but if loading from file, don’t load the wrong structure for the selected printer.
If the wrong devmode
structure is loaded, your computer could go round in ever decreasing circles and eventually achieve the impossible! The wrong devmode
structure can be loaded by:
- Loading in the wrong binary file, there is no check on this. If you take a movie file, rename the extension to “.BIN”, the file will can appear in the file browser. If such a file is selected, you will overwrite the
devmode
structure and most of the operating system! You may need a GHOST image or a recovery disk if you overwrite the devmode
structure with 50 shades of Grey!! (No, this will not put the printer into monochrome mode!) If the arraylist
is used, the printer name is used to call the correct printer first, the arraylist
is safer. - Changing the operating system and or printer driver may cause problems as the
devmode
structure could be different. Don’t forget that a printer driver for one operating system does not usually work on another.
I may work on a way of letting other applications know of a change to the printer settings so this program can be used as a standalone program for controlling printer settings for other applications, but that is for later, unless any of you want to have a go?
As I said, this solution is intended to be included, in modified form, into another application. If you see any way to improve the solution, post the amendments so anyone (including me) can benefit.
Licensing (What’s That For)
Anything you see from me I consider to be “Public domain”. If you want to use it, download the project and go ahead.
The ZIP file that can be downloaded is a VS2005 C# .NET project.