PDFs in C# unter Windows ausdrucken

Einfache PDF Ausdruckungen unter .NET Anwendung

Um pdf-Dokumente unter Windows aus einer .NET-Anwendung heraus auszudrucken, gibt es die verschiedensten Möglichkeiten. Es gibt zunächst einmal eine Vielzahl freier oder kostenpflichtiger Bibliotheken, mit denen mehr oder weniger komfortabel pdf-Dokumente aus .NET heraus erstellt werden können. Geht es allerdings darum, ein Dokument auszudrucken, genügen freie Angebote offenbar nicht mehr und man müsste auf eine kostenpflichtige Komponente zurückgreifen. Will man lediglich ein vorhandenes Dokument ausdrucken, schiesst man damit vermutlich mit Kanonen auf Spatzen. Ein einfacher Weg ist hier, eine vorhandene Installation von Acrobat oder dem Reader einzusetzen. Beide sind in der Lage, Dokumente auszudrucken und warum sollte man davon nicht Gebrauch machen? (Vorausetzung ist hier wie gesagt, dass eine der beiden Anwendungen auf Zielrechner vorhanden ist.)

Nun bieten der Acrobat, wie auch der Reader einem Entwickler durchaus die Möglichkeit, gegen ihre Dlls zu linken und so auf dem “offiziellen” Weg auf ihre Dienste zurückzugreifen. Da ist zunächst einmal nichts geheimnisvolles dabei. Adobe bietet zudem eine relativ umfangreiche Dokumentation der Objektmodelle des Acrobats (die sogar ein kleines Kapitel über die Anbindung an .NET beinhaltet). Der Haken hierbei ist jedoch, dass sich die Objektmodelle beider Anwendungen (und vermutlich sogar einzelner Versionen) voneinander unterscheiden. So muss bei der Erstellung der eigenen Anwendung berücksichtigt werden, welche Acrobat (Reader)-Version später auf dem Zielrechner vorgefunden wird. Jeder normale Mensch würde jetzt vermutlich erstmal an eine späte Bindung denke, um diese Unterscheidung zur Laufzeit vornehmen zu können. Das Problem hierbei ist allerdings, dass die Namen von Klassen und Methoden der installierten Acrobat-Version dann spätestens zur Laufzeit bekannt sein müssen. Sicher kriegt man das irgendwie hin – es geht aber auch einfacher.

Es gibt nämlich ausser unserer Anwendung auf dem Zielrechner noch jemanden, der das gleiche Problem hat: die Shell, resp. der Explorer. Über das Kontextmenü kann ein Dokument ausgewählt und ein Druckauftrag angestossen werden. Die Shell muss also auch wissen, welche Anwendung sich für einen bestimmten Dateityp verantwortlich fühlt, ohne einzelne Versionen berücksichtigen zu müssen. Zu diesem Zweck sind in Windows (genau genommen in der Registry) bekanntlich Dateitypen mit Anwendungen verknüpft. In .NET kann man nun genau diese Verknüpfung ausnutzen. Dazu erstellt man eine Instanz von Process aus dem Namespace System.Diagnostics. Parametriert wird diese Instanz über eine Property StartInfo. Zu diesen Parametern gehört nicht nur der Dateiname der zu öffnenden Datei, sondern auch ein sogenanntes Verb. Verbs sind die Befehle, welche die Shell an eine Anwendung beim Start (per COM, DDE oder als Kommandozeilenparameter) überträgt. Die Anwendung auf der Gegenseite sollte natürlich im Stande sein, dieses Verb zu verstehen. Windows ist hier lediglich bei der Übetragung involviert. Ein Verb wie print wird allerdings im Allgemeinen bei der Installation einer Anwendung angelegt.

Versucht man nun, aus dem Anwendungscode heraus über die Shell ein pdf Dokument auszudrucken, tritt das Problem auf, dass eine Instanz des Acrobat (Readers) in der Taskleiste erhalten bleibt. Über Process.StartInfo kann ihr das abgewöhnt werden. Der folgende Code ermöglicht es, ein pdf auszudrucken und beendet anschließend den Acrobat Reader.

Process proc = new Process ();
proc.StartInfo.CreateNoWindow = false;
proc.StartInfo.Verb = “print”;
proc.StartInfo.FileName = “C:\\test.pdf”;
proc.Start();
proc.WaitForExit(10000);
proc.CloseMainWindow();
proc.Close();

Sollen mehrere Dokumente hintereinander gedruckt werden, ist hier eine kleine Optimierung möglich: Verzichtet man auf die Beendigung des Prozesses, bleibt die Instanz des Acrobat auch nach dem Druckauftrag noch (minimiert) erhalten. Bei weiteren Druckaufträgen wird dann keine neue Instanz gebildet, sondern das Verb an diese übertragen. Man spart dadurch die Zeit für den Programmstart des Acrobat.

 

Folgende Kommentare wurden auf unserem alten Blog zu diesem Beitrag veröffentlicht:

Magier schreibt: Oktober 9th, 2007 at 9:47

Hallo.

Zitat:
“Nun bieten der Acrobat, wie auch der Reader einem Entwickler durchaus die Möglichkeit, gegen ihre Dlls zu linken und so auf dem “offiziellen” Weg auf ihre Dienste zurückzugreifen.”

Kann mir jemand sagen wie das denn geht? Ich habe bisher mit Acrobat 6 gedruckt, per /t command. Nun möchte ich den 8 benutzen, bei dem dies nicht mehr funktioniert. Ich weiß also dass ich immer mit 8 meine PDF drucken möchte. Wie linke ich die Funktionen des 8 denn in mein Projekt ein um einen internen druckbefehl an den Reader abzusetzen?

Danke & Gruß

 

Magier schreibt: Oktober 9th, 2007 at 10:01

Was ich noch vergessen habe: Eine meiner Anforderungen ist, dass ich auf einem bestimmten Drucker drucken muss, ohne den Standarddrucker umzuhängen. Dies bietet die print-with-verb Methode leider nicht, kann man dies über die DLL Einbindung erreichen?

Gruß

 

szimmer schreibt: Oktober 9th, 2007 at 11:16

Um aus einer .NET-Anwendung heraus ein Pdf-Dokument zu drucken, brauchen wir erstmal eine passende Dll. Um von Visual Studio aus gegen die Dll zu linken, fügt man dem aktuellen Projekt einen Verweis hinzu und gibt als Verweisziel die COM-Komponente der lokal installierten Acrobat-Type-Library an.
Dann kann man bspw. mit dem folgenden Codesegment
AcroPDFLib.AcroPDF pdf = new AcroPDFLib.AcroPDFClass ();
pdf.LoadFile (p_strFileName);
pdf.Print ()
eine Datei drucken.

Eine andere Möglichkeit ist, über die Programm-ID einen in der Registry registrierten Typ zu instantiieren. Das kann dann so aussehen (Reader):
Type tpAcrobat = Type.GetTypeFromProgID (”AcroPDF.PDF”);
oder so (Acrobat)
Type tpAcroApp = Type.GetTypeFromProgID (”AcroExch.App”);
Type tpAcroPDDoc = Type.GetTypeFromProgID (”AcroExch.PDDoc”);
Type tpAcroAVDoc = Type.GetTypeFromProgID (”AcroExch.AVDoc”);
Hat man ein Typ-Objekt, kann man mit dem Activator eine Instanz bilden und mit dieser den Druckvorgang anstoßen.

Der Vorteil ist hier, dass man zur Übersetzungszeit die Dll nicht kennen muss. Allerdings verlässt man sich im Gegenzug darauf, dass auf dem Rechner, auf dem die Applikation später läuft, eine passende Version vom Acrobat (resp. Reader) installiert ist.

Was die Wahl des Druckers angeht, weiss ich im Augenblick leider auch kein Mittel. Über die Methode AcroPDFLib.printWithDialog () kann man
einen Druckdialog einblenden und den Drucker manuell auswählen. Ich vermute aber mal, dass das nicht ausreicht.

Gruß

 

Jan schreibt: März 19th, 2009 at 5:12

coole Sache. Wie kann ich denn jetzt noch den Drucker auswählen?

Grüße
Jan

 

Felix schreibt: Mai 27th, 2009 at 5:50

Habe die gleiche Frage wie Jan. Hat da jemand eine Idee?
Kann ich in C# irgendwie “kontrollieren”, ob alles geklappt hat? Jetzt mal abgesehen von try, catch…

Gruß Felix und vielen Dank für den Tipp!

 

Christian schreibt: April 7th, 2010 at 11:55

Open Source Projekt

PdfSharp

http://www.pdfsharp.net/Features.ashx

Dort sind allle Funktionen implementiert.

 

Quinte schreibt: Juni 15th, 2010 at 12:00

“.. Dort sind alle funktionen implementiert.”

Na Klasse, aber nur eine brauchbare Druckfunktion fehlt da leider auch.

Irgendwie scheint es keine vernünftige Lösung zu geben ohne gravierende Einschränkungen aus dem Programm heraus über ein Printerdialog ein bereits existierendes PDF-File zu drucken.

 

Kai schreibt: Juni 30th, 2010 at 6:12

Der Druck über Acrobat mit “shell” oder ähnlichen “Scherzen” ist total sinnlos. Auf die Methode bekommt man vielleicht mal ein oder zwei Blatt gedruckt aber mehr auch nicht. Das Problem mit dem Standardrucker ist nicht lösbar (meines Wissens) und den Prozess wird man eben nicht ordentlich los… mal abgesehen davon, dass das Fenster des Readers nervt. Wir haben sehr viel probiert – wird nix! Ist irgendwo aber auch einzusehen – schließlich bietet Adobe entsprechende Bibliotheken für sehr(!!) viel Geld an und die soll ja auch jemand kaufen..
Die m.E. beste Methode findet man hier: http://www.wpcubed.com/ . Die Leistung, die man erwartet (wenngleich noch kleinere Macken vorhanden sind) und ein faires Lizenzmodell.
Ne Demo kann man runterladen und mal probieren…

 

Nico schreibt: November 24th, 2010 at 1:26

Man kann auch das pdf mit GhostScript (OpenSource) in ein Bild umwandeln und das Bild wiederrum ausdrucken. Da kann man dann auch den Drucker bestimmen, auf dem das Bild ausgedruckt werden soll.
Gruß

 

Oli schreibt: Dezember 4th, 2010 at 11:37

Hi,
nun genau das ist mein Problem. Ich möchte ein vorhandenes pdf – Dokument ausdrucken, allerdings ohne das Zutun eines Benutzers. Der Benutzer klickt in meiner Anwendung einen Button und entsprechend der von ihm vogenommenen Auswahl soll nun ein pdf an einen bestimmten (immer gleichen) Drucker gesendet werden.
Der Benutzer soll dabei nichts machen müssen, nachdem der Button gedrückt wurde (außer zum Drucker zu laufen und das Dokument rausnehmen :-) )

Wie ist das zu realisiseren, ohne Acrobat Reader zu öffnen?

Grüße Oli

 

Patte schreibt: Dezember 8th, 2010 at 9:54

Hallo,
dieses Problem haben sehr viele Leute aber keine Passende Lösung dazu…

Ich habe dieses Problem teilweise Recht gut mit dem FoxitReader 4.3 lösen können. (VB.NET Code, kann aber leicht umgestellt für C# Verwendet werden!)

[Datei] Pfad zu der PDF Datei die Gedruckt werden soll
[Anzahlkopien] Wie oft das Dokument gedruckt werden soll
[Druckername] Name des Druckers auf dem Gedruckt werden soll
Wichtig dabei ist das der Dateipfad und der Druckername in doppelten Anfürhungszeichen ” sein müssen.

Hier nun der Code:
For i As Integer = 1 To [Anzahlkopien]
Dim myProc As New Process
myProc.StartInfo.FileName = “”"” & My.Application.Info.DirectoryPath & “\FoxitReader\FoxitReader.exe”"”
myProc.StartInfo.Arguments = “-t “”" & [Datei] & “”" “”" & [Druckername] & “”"”
myProc.StartInfo.CreateNoWindow = False
myProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
Try
If myProc.Start() Then
myProc.WaitForExit(30000)
Else
GoTo printerror
End If
Catch ex As Exception
MsgBox(ex.Message)
Exit For
End Try
Application.DoEvents()
Next
printerror:
hier die Behandlung einfügen, wenn ein Fehler auftritt.

—————-CODE ENDE——————-
Man kann mit Prozess auch die Fehlermeldungen Abfangen und diese ausgeben.

Hier der Code
Dim sFeh As String = “”
For i As Integer = 1 To [Anzahlkopien]
Dim myProc As New Process
myProc.StartInfo.FileName = “”"” & My.Application.Info.DirectoryPath & “\FoxitReader\FoxitReader.exe”"”
myProc.StartInfo.Arguments = “-t “”" & [Datei] & “”" “”" & [Druckername] & “”"”
myProc.StartInfo.CreateNoWindow = False
myProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
myProc.StartInfo.RedirectStandardError = True

Try
If myProc.Start() Then
myProc.WaitForExit(30000)
Else
Dim input As Integer = myProc.StandardOutput.Read
Do Until input = -1
sFeh &= ChrW(input)
input = myProc.StandardError.Read
Loop
GoTo printerror
End If
Catch ex As Exception
MsgBox(ex.Message)
Exit For
End Try
Application.DoEvents()
Next
printerror:
MsgBox(sFeh)

 

Nico schreibt: Dezember 14th, 2010 at 12:48

http://www.codeproject.com/KB/cs/GhostScriptUseWithCSharp.aspx
das umgewandelte Bild einfach ausdrucken.
Funktioniert einwandfrei.

Gruß