B
BJS
Sorry for the cross-posting, but based on the number of people I have
seen ask for a solution to this problem, I hope by cross-posting this,
that it will help a lot of people out of a common problem.
Have you ever wanted to use print to a file using the
System.Drawing.Printing namespace only to find out that the only way
to do it is by popping a dialog box through the PrintDialog? What if
you want to print to file without user interaction, say in an
unattended application like a Windows Service or on a web server?
Well, according to the SDK documentation and KB article 820644, you
can't do it: "The PrinterSettings.PrintToFile property can only be set
by the System.Windows.Forms.Printing.PrintDialog class . . . for more
information, see the KB article 'PrintDocument Class Does Not
Implement The PrintToFile Feature (820644)'."
Many people have run into this problem. The newsgroups are full of
references to the issue, but there aren't many solutions. I needed
this functionality and I think I came up with a good solution. And
because I know other people have had this issue in the past, and
others will have it in the future, I figured I would share it with
everyone.
I wrote a simple multi-threaded listener that acts as a TCP/IP
printer. The output of the printer is directed to the listener, which
then saves the data to a file of your choosing. If you are interested
in getting the code, drop me an email.
Here is how it works:
1. Install a new TCP/IP printer port. Configure it to point to the IP
address of the machine where you will install the listener. Use port
9100 (the default.)
2. Install a postscript printer. Any of the ones that come with the OS
are fine. I needed color output that I could convert to Acrobat / PDF
using GhostScript, so I installed the HP Color LaserJet 8550-PS.
Configure the printer to use the port created in step 1.
3. Write your code and configure your PrintDocument object to print to
the printer created in step 2.
4. VERY IMPORTANT: Set the PrintDocument.DocumentName property to the
complete absolute file path of the file where you want the data to be
saved. This should include the file extension. It can be a UNC name if
you like. ex: C:\myoutputfile.prn or
\\MYSERVER\MYSHARE\myoutputfile.ps or whatever - just make sure it is
the complete file path and that the listener has permission to write
the location
5. Call the Print method of the PrintDocument object.
6. Handle the PrintPage event.
7. Compile your code
8. Start the listener.
9. Run your code.
That's it! Your document is spooled to printer driver specified in #2
by the framework. The printer driver then forwards the data over the
TCP/IP port specified in #1. The listener accepts the data being
forwarded by the driver and saves it to the file you specified in the
DocumentName property. Then you can whatever you need to do with the
outputed file.
The listener is implemented as a command line application, but it
could easily be modifed to run as a service. It is written in VB.NET.
It is multi-threaded and seems to work decently enough. Combined with
Ghostscript this is a great way of generating PDF documents on the fly
without any user interaction.
Caveats:
- It's a hack compared to Microsoft just including the damn
functionality in the framework to begin with - why they chose not to
expost that feature, I have no idea. It is obviously there in the
framework since PrintDialog uses it. grrr.
- Hasn't been tested too much
- Code isn't commented.
- Could use better error handling.
- No instrumentation, logging, or other status notifications.
- Only works with postscript printer drivers
- Contains a kludge: gets the location to which to save the file (that
you specified in the DocumentName property) using RegEx on the
%%Title: postscript comment (%% is comment indicator in postscript)
that is outputed with each postscript document. It's a potential that
not all PS drivers would output this information, but the couple that
I tested all worked. YMMV.
Obviously it would be better if this support was in the framework, but
it isn't. So this is a fairly decent (and free) solution as opposed to
some of the other ones that are possible alternatives: use unmanaged
GDI calls through PInvoke, use Office automation, use a local port
with a fixed file name, use Distiller, use FILE: and hire someone to
enter in the file name each time, etc.
If you'd like the code, drop me an email. It's free.
I'll send you a Zip file with the compiled command line application
and the VB.NET source code. You can play around with the source and
implement it in a different way if you like or just fire up the .exe
and starting printing.
Be sure to give me feedback. Let me know what you think.
Thanks,
Brian
(e-mail address removed)
seen ask for a solution to this problem, I hope by cross-posting this,
that it will help a lot of people out of a common problem.
Have you ever wanted to use print to a file using the
System.Drawing.Printing namespace only to find out that the only way
to do it is by popping a dialog box through the PrintDialog? What if
you want to print to file without user interaction, say in an
unattended application like a Windows Service or on a web server?
Well, according to the SDK documentation and KB article 820644, you
can't do it: "The PrinterSettings.PrintToFile property can only be set
by the System.Windows.Forms.Printing.PrintDialog class . . . for more
information, see the KB article 'PrintDocument Class Does Not
Implement The PrintToFile Feature (820644)'."
Many people have run into this problem. The newsgroups are full of
references to the issue, but there aren't many solutions. I needed
this functionality and I think I came up with a good solution. And
because I know other people have had this issue in the past, and
others will have it in the future, I figured I would share it with
everyone.
I wrote a simple multi-threaded listener that acts as a TCP/IP
printer. The output of the printer is directed to the listener, which
then saves the data to a file of your choosing. If you are interested
in getting the code, drop me an email.
Here is how it works:
1. Install a new TCP/IP printer port. Configure it to point to the IP
address of the machine where you will install the listener. Use port
9100 (the default.)
2. Install a postscript printer. Any of the ones that come with the OS
are fine. I needed color output that I could convert to Acrobat / PDF
using GhostScript, so I installed the HP Color LaserJet 8550-PS.
Configure the printer to use the port created in step 1.
3. Write your code and configure your PrintDocument object to print to
the printer created in step 2.
4. VERY IMPORTANT: Set the PrintDocument.DocumentName property to the
complete absolute file path of the file where you want the data to be
saved. This should include the file extension. It can be a UNC name if
you like. ex: C:\myoutputfile.prn or
\\MYSERVER\MYSHARE\myoutputfile.ps or whatever - just make sure it is
the complete file path and that the listener has permission to write
the location
5. Call the Print method of the PrintDocument object.
6. Handle the PrintPage event.
7. Compile your code
8. Start the listener.
9. Run your code.
That's it! Your document is spooled to printer driver specified in #2
by the framework. The printer driver then forwards the data over the
TCP/IP port specified in #1. The listener accepts the data being
forwarded by the driver and saves it to the file you specified in the
DocumentName property. Then you can whatever you need to do with the
outputed file.
The listener is implemented as a command line application, but it
could easily be modifed to run as a service. It is written in VB.NET.
It is multi-threaded and seems to work decently enough. Combined with
Ghostscript this is a great way of generating PDF documents on the fly
without any user interaction.
Caveats:
- It's a hack compared to Microsoft just including the damn
functionality in the framework to begin with - why they chose not to
expost that feature, I have no idea. It is obviously there in the
framework since PrintDialog uses it. grrr.
- Hasn't been tested too much
- Code isn't commented.
- Could use better error handling.
- No instrumentation, logging, or other status notifications.
- Only works with postscript printer drivers
- Contains a kludge: gets the location to which to save the file (that
you specified in the DocumentName property) using RegEx on the
%%Title: postscript comment (%% is comment indicator in postscript)
that is outputed with each postscript document. It's a potential that
not all PS drivers would output this information, but the couple that
I tested all worked. YMMV.
Obviously it would be better if this support was in the framework, but
it isn't. So this is a fairly decent (and free) solution as opposed to
some of the other ones that are possible alternatives: use unmanaged
GDI calls through PInvoke, use Office automation, use a local port
with a fixed file name, use Distiller, use FILE: and hire someone to
enter in the file name each time, etc.
If you'd like the code, drop me an email. It's free.
I'll send you a Zip file with the compiled command line application
and the VB.NET source code. You can play around with the source and
implement it in a different way if you like or just fire up the .exe
and starting printing.
Be sure to give me feedback. Let me know what you think.
Thanks,
Brian
(e-mail address removed)