Link to home
Start Free TrialLog in
Avatar of aquila98
aquila98

asked on

How to send a .bmp file to a specific printer from a NT service runnin on a NT server (nt4sp6a)

Hi..

The problem is that I have this server ruiing a service which is responsible
for downloading weather data from various ftp sites at specific intervals.
Some weather products I would like to print automatically as soon as they are
downloaded...

I tried this:
winexec ("mspaint /p filename.bmp")

This is somewhat of a patch of course...

Now what I want to do is to be able to specify that for this of that product
you will print on that printer...

I have 2 problems whit this:
1) how to specify the printer I want to use? This is a nt service so I can't pop
up a printer dialog and let a user pick a printer... It all have to be done
via programmation and I don't know how to get the list of printer for instance...

2) how to send the file to the printer? Since this is a nt service I need to
somehow package the .bmp file before printing it? How do I go about doing this?
I really would like to bypass sending the file to mspaint because I'd have to
alter the default printer each time BEFORE calling mspaint /p (on the server
this might be annoying because the admin will have to check each time what's
his default printer!)... Do I have to built a special .exe file that will
receive the file name as a parameter and perform the encapsulation and printer
selection and handle the file printing??? This might be the cleaner way to do it?

Any hints/sugestions welcome ;-)

TIA
Avatar of Salte
Salte

Typically a service like this is supposed to be configured and you store the info about which printer to use and other parameters in registry during installation time.

In that case you simply retrieve the values from the registry. You can also have a property dialog attached and installed in the services control panel so that the user can easily change the values from there without having use regedit to change the values.

In this case you will also let your service subscribe to the event that the configuration has changed so that the user's changes will take effect immediately.

Check the Win32 functions around ControlService etc.

Alf
Avatar of aquila98

ASKER

All the param are kept in a .ini files (keeps the registry
clean!)...
And of couse no user intervention is possible.
The idea of the service is to automatically download files
and to send some of them to a specific printer in the
office of the weather analyst who's specialized in this
particular weather product...

So I expect to have a map of product name versus printer
like so:
map<string,string> ProductPrinterMap;

Product= ProductPrinterMap.Find(ThisProduct);
if (product != ProductPrinterMap.end()) // found match
    then print(ThisProduct, (*product).second);

And of couse the print function defined thus:
void print(const string& filename, const string& printername)

Now I don't know if it's better to write a .exe file
(that use MFC) to implement the print function...
Or if I can put it within the NT service's code (non MFC)?

I hope this clarify the problem somewhat ;-)

thanks
Read the map contents from an .ini file if you want. That's your decision.

As far as the printing is concerned you don't HAVE to use MFC if you don't want to. On the whole MFC doesn't really do much, 99% of the MFC functions are very simple one-line wrappers to the Win32 functions.

However, printing is one of those that actually do something so if you want to do printing outside of MFC is it possible but beware that it is some work connected to it.

You might want to consider having some MFC code to do the printing. Problem with MFC is that it assumes too much, it assumes you run a GUI program and it assumes you want a window and blah blah blah.

However, it is possible to fool it into thinking you're a GUI program. As long as you never actually do any attempt at displaying anything in any window you should be just fine :-)

As far as printing goes this uses the same mechanisms as a GUI program uses to display itself on the screen, i.e. you use a DeviceContext etc. You most likely have to call the Win32 function to get the device context to the printer and then just trick MFC into thinking it's been there all along or some such.

Another way is to simply do the printing yourself. As I said MFC does a lot of coding connected with printing but that is as far as pagination and breaking words etc goes. You don't really use any of that when you print a BMP and therefore you might as well just do the raw printing yourself. I.e. Get the Device Context, and then have a handle to the BMP and tell the device context to display the BMP in the 'window' that is the page. Make sure you scale it properly, a typical error when people try to do this first time is that they end up printing a mini version of the picture on the paper. The reason is that the printer uses a far higher resolution than your screen does so if you count pixels the pixels will take much smaller space on a printer page than it does on screen.

You must therefore blow up the image properly. One way to do it right is to just work on the dimensions of the printer's page size and then adjust margins and so on manually and just make sure the page is properly positioned on the page. If you want to center it and the picture is w * h pixels wide and the printer context has dimensions W * H then you can do the following:

if w/h > W/H (or w*H > W*h) then the image is wider per height than the printer page is, in this case you must scale it based on the width, so if you adjust it by a factor f = W/w then the image becomes w*f x h*f is approximagely W x h * W/w. The height of the image is then h * W/w so the free space is H - h * W/w > 0 and if you want to center the image you should put it so that the top line of the image comes at position (H - h*W/w)/2 lines from the top. If you want to align it with the top you of course place it at the top.

If w/h < W/H the image is higher per width than the printer page and so you must scale it by f = H/h and so the image is w*f x h*f is approximately w * H / h x H. The available space in x direction is W and W - w * H / h is the amount of free space. If you want to center the image you position it so that the leftmost edge comes at position (W - w*H/h)/2.

Once you have those co-ordinates and scaling factor it's just to print it out by a simple Win32 function.

No need for MFC really.

Alf
Paper format is one more of those things I'll run into...
Some product require a 11x17 landscape while others must
be printed on 11x17 portrait (some on 8.5x11 too!)...
So in choosing the printer I'd also have to do a bit of
printer setup via programmation...

How can I get a device context for a particular printer on
my network?

How can I choose the paper size and paper orientation for
that printer? (each printer has its own driver loaded on the
server and is available for use by the service, for instance
not all printer have 11x17 paper size available)

I must retreive the printer's properties once I have
selected it and chack that it is possible to print the
bmp there. Once I have selected the printer, then the
paper size and orientation... That's where I'd stretch
the bitmap to the right (paper) size and then print it?

Any exemple of code that do this? stretch a bitmap and
print it without MFC? Any bits of code that selects a
printer, get its properties (ou printcap) and then
select paper size and orientation.. and then print?

??



playing around I found a way to select any printer on
my network given its name...
So far so good.

I can even rotate the image before stretching it so that
it fills a full printed page (it's easier to rotate the
bitmap than figure out how to change the paper setup!!!)

But...

Now I hit the brick wall of the paper setup!
Because I need to specify the paper size I want...
Sometimes I'll want the 11x17 and some other time the "letter"
size...

How can I do this when all I have is a handle to the printer
(HANDLE from ::GetPrinter()) or a printerDC
??

How can I change paper from the .exe file without any user
intervention????
Avatar of DanRollins
>> How can I change paper from the .exe file without any user intervention????

For most printers... you just can't.  Somebody will need to remove the 11-inch paper from the tray and put 17-inch paper into the tray (software can only drive hardware, not do magic!)

That said... here is some code I use in an MFC program to set the printer to landscape mode.  It actually boils down to getting the DEVMODE structure and changing one field:

void CAppCb3::SetLandscape( BOOL fSet /*=TRUE*/ )
{
    PRINTDLG   pd;
    pd.lStructSize= sizeof(PRINTDLG);
     if ( GetPrinterDeviceDefaults(&pd) ) {
        // Lock memory handle.
        DEVMODE FAR* pDevMode= (DEVMODE FAR*)::GlobalLock(m_hDevMode);
        LPDEVNAMES lpDevNames;
        LPTSTR lpszDriverName, lpszDeviceName, lpszPortName;
        HANDLE hPrinter;

        if (pDevMode) {
            // Change printer settings in here.
            pDevMode->dmOrientation= fSet ? DMORIENT_LANDSCAPE : DMORIENT_PORTRAIT;
           // Unlock memory handle.
             lpDevNames = (LPDEVNAMES)GlobalLock( pd.hDevNames );
             lpszDriverName = (LPTSTR )lpDevNames + lpDevNames->wDriverOffset;
             lpszDeviceName = (LPTSTR )lpDevNames + lpDevNames->wDeviceOffset;
             lpszPortName   = (LPTSTR )lpDevNames + lpDevNames->wOutputOffset;

             ::OpenPrinter( lpszDeviceName, &hPrinter, NULL);
             ::DocumentProperties(NULL,hPrinter,lpszDeviceName,pDevMode,
                                      pDevMode, DM_IN_BUFFER|DM_OUT_BUFFER);

             // Sync the pDevMode See SDK help for DocumentProperties for more info.
             ::ClosePrinter(hPrinter);
             ::GlobalUnlock(m_hDevNames);
             ::GlobalUnlock(m_hDevMode);
       }
    }
}

-=-=-=-=-=-=-=-==--=-=
The only non Win32 code is the call to GetPrinterDeviceDefaults(&pd) and it is about a page of code that you can find at the top of a file named APPPRNT.CPP in the MFC/src directory.  It basically boils down to some code like:

    m_pd.Flags |= PD_RETURNDEFAULT;
    return ::PrintDlg(&m_pd);

The ::PrintDlg (or PrintDlgEx) in which the PD_RETURNDEFAULT flag is set just obtains the same data that is normally obtained when the user invokes the common dialog for setting printer options.

-=-==--=-=-==-=--=-==-=--=-=
Check out the DEVMODE structure.  See the field named
dmPaperSize?  Set it to DMPAPER_11X17
-=-==--=-=-==-=--=-==-=--=-=

Now, all of this would be handy if you were then going to do your own printing.  Printing with raw Win32 is rather a bit too much to explain here.  However, in your service, you can set the default settings for the current user (who is probably LocalSystem) and will not affect the admistrator's settings.

So I'd recommend using the above techniques change the default settings, then launch Paint to print the BMP file.  As an alternative, you can use
     ShellExecute(0,"print",....)

which will probably run ms Paint anyway :)

Once this is all working, you can look into saving the current default settings and restoring them later.  Did I mention that under Win2K, default DEVMODE settings for the current user can be obtained and changed via
   ::GetPrinter(hPrn, PRINTER_INFO_9,...)
and
   ::SetPrinter(hPrn, PRINTER_INFO_9,...)

-- Dan
Interesting bits of code DanRollins, Thanks.

Of course our printers have 3 or 4 paper tray and they can
change paper according to the print setup ;-)

Is there any way to get the GetPrinterDeviceDefaults()
to fill up the printdlg struct when all I have is either
a HANDLE to a printer (from openprinter) or a HDC for that
printer (obtained via createDC)...

I want to fill up that printdlg struct but now with the
default printer stuff... Instead I want to fill up printdlg
with data for the printer I choosed and for which I have
a valid DC and a handle...

With this PRINTDLG I could use your code and call
DocumentProperties with the handle and set landscape
or paper size easy for that particular printer and
print to it...

The printing part I can do, its the paper size and
orientation I am having problem with but with this
code I think there might be a way to do it... provided
I can fill up PRINTDLG from a DC or a handle ???
Is this possible?

How to you come to have that DC or that HANDLE?  You specified a printer name or specified the defualt for the systeem or for a user, right?  PrintDlg will let you specify a printer using similar criteria.  Once you get a DEVMODE, just tweak a few fields and use that in the OpenPrinter call.

-- Dan
I did... In the CreateDC I passed the name of the printer on
my network...

But HOW can I pass that name to the printDlg function
without the dialog box openning?
How can I Get the devmode for a particuler printer, and change
the paper size...

I noticed that some of the function in DeviceCapabilities()
are available ONLY to XP... I will be running on a Windows 2000 advance server (a cluster really) but my software (the service) is compiled on a nt computer (running nt4 sp6a).
Maybe this might change something in the solution?

?
Hmmm... it looks like the PrintDlg fn will fill it in for you only if you use the PD_RETURNDEFAULT flag and it will fill-in settings for the default printer.  I suppose you could set the default printer (temporarily) before making the call.  

But I think this is off on a slight tangent.  Most or all of what you need to do can be done in the DEVMODE record.  Check the info on the DocumentProperties API function.

-- Dan
That's what I was affraid also...
I guess it's not too bad to set the default printer to
one on the network... The service is running with the admin
account so I guess it's possible to use this patch.

Can I store this devmode into a file by and chance?
Is it serializable? I guess this will never change for the
life of the printer so if I fill up this DEVMODE struct
from a saved copy in a file whould this work? Any idea
how I could do this?
ASKER CERTIFIED SOLUTION
Avatar of DanRollins
DanRollins
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
thanks for your efforts.
I have been able to progress somewhat closer to what I
wanted to acheive... I guess if we select a printer
and keep only one kind of paper per printer that way it
would work ;-)

enjoy the points
Thanks for selecting my comment as an answer.

>>...keep only one kind of paper per printer...
If the printer supports multiple paper bins, there is a DEVMODE.dmDefaultSource that might work.  Just examine this DEVMODE after a normal (interactive) call to PrintDlg to see what should go in there.

-- Dan