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 dot 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.
http://www.pinvoke.net/default.aspx/Structures/DEVMODE.html
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 the link below to see how he did it.
http://nicholas.piasecki.name/blog/2008/11/programmatically-selecting-complex-printer-options-in-c-shar/
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 strucure 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 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.