?
Solved

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

Posted on 2003-03-25
14
Medium Priority
?
356 Views
Last Modified: 2009-12-16
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
0
Comment
Question by:aquila98
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 7
  • 5
  • 2
14 Comments
 
LVL 12

Expert Comment

by:Salte
ID: 8204260
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
0
 
LVL 2

Author Comment

by:aquila98
ID: 8204355
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
0
 
LVL 12

Expert Comment

by:Salte
ID: 8204585
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
0
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 2

Author Comment

by:aquila98
ID: 8204742
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?

??



0
 
LVL 2

Author Comment

by:aquila98
ID: 8205889
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????
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 8206453
>> 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
0
 
LVL 2

Author Comment

by:aquila98
ID: 8210186
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?

0
 
LVL 49

Expert Comment

by:DanRollins
ID: 8212657
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
0
 
LVL 2

Author Comment

by:aquila98
ID: 8212740
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?

?
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 8213195
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
0
 
LVL 2

Author Comment

by:aquila98
ID: 8213327
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?
0
 
LVL 49

Accepted Solution

by:
DanRollins earned 800 total points
ID: 8214071
I think there are parts of the DEVMODE that you can't serialize because they are handles and/or black-box items.

Most programs don't serialize the entire DEVMODE -- just get the current defaults and apply a few changes (such as setting landscape or the paper bin selection) and so most programs simply extract these essential items and save them in the registry or other configuration file.

-- Dan
0
 
LVL 2

Author Comment

by:aquila98
ID: 8217608
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
0
 
LVL 49

Expert Comment

by:DanRollins
ID: 8219951
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
0

Featured Post

On Demand Webinar - Networking for the Cloud Era

This webinar discusses:
-Common barriers companies experience when moving to the cloud
-How SD-WAN changes the way we look at networks
-Best practices customers should employ moving forward with cloud migration
-What happens behind the scenes of SteelConnect’s one-click button

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

What is C++ STL?: STL stands for Standard Template Library and is a part of standard C++ libraries. It contains many useful data structures (containers) and algorithms, which can spare you a lot of the time. Today we will look at the STL Vector. …
Basic understanding on "OO- Object Orientation" is needed for designing a logical solution to solve a problem. Basic OOAD is a prerequisite for a coder to ensure that they follow the basic design of OO. This would help developers to understand the b…
The goal of the tutorial is to teach the user how to use functions in C++. The video will cover how to define functions, how to call functions and how to create functions prototypes. Microsoft Visual C++ 2010 Express will be used as a text editor an…
The goal of the video will be to teach the user the concept of local variables and scope. An example of a locally defined variable will be given as well as an explanation of what scope is in C++. The local variable and concept of scope will be relat…
Suggested Courses

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question