Mit der "privaten Klasse" FormPrint kann man ein beliebiges Formular oder anderes Control drucken oder als Image speichern. Dazu dienen die öffentlichen statischen Methoden Print() bzw. Save(). Dies ist nützlich für Entwickler zur Dokumentation einer Anwendung und für Kontrolldrucke der Nutzer.
Andere Lösungen in Foren halte ich für ein einfaches Verfahren zu umfangreich oder komplex; oder die Suche nennt viel zu viele andere Probleme. Ich habe mich deshalb zu einem eigenen Verfahren entschlossen.
Aufruf und Nutzung der Klasse
Der Code ist vorbereitet für NET 2.0. Unten unter Anpassung an NET 1.1 habe ich die einfache Änderung beschrieben.
Der Dateianhang enthält die eigentliche Klasse FormPrint.cs sowie ein Formular FormPrintDemoForm.cs für eine Demo-Applikation.*Füge in den Quelltext eine Referenz auf diese Klasse ein:
using JThomas.Tools;
*Wenn diese Klasse in einer eigenen Assembly Tools steht, wird natürlich auch eine Referenz in der Anwendung auf die Tools.Dll benötigt. *In den Quelltext - z.B. beim Click-Ereignis für einen Button oder ein ToolStripMenuItem - gehört der Aufruf einer der gewünschten Methoden.
// Variante 1 a (einfach) zum Drucken des aktuellen Formulars:
FormPrint.Print(this);
// Variante 1 b (einfach) zum Drucken eines anderen Controls:
FormPrint.Print(this);
// Variante 1 c mit allen möglichen Parametern:
FormPrint.Print(this, ColorDepthReduction.Colored8bpp, "PDF Printer", aMargins, false);
// Variante 2 zum Speichern des aktuellen Formulars z.B. im PNG-Format im Programmverzeichnis:
FormPrint.Execute(this, ImageFormat.Png, "E:\\Temp\\" + this.Name + ".png");
Die meisten Parameter können weggelassen werden.
Mögliche Parameter
Die folgenden Parameter werden für alle Varianten benötigt:*Das gewünschte Control Ctrl ist immer anzugeben. *Für ImageFormat fImage wird standardmäßig PNG verwendet.
Die folgenden Parameter können Druckvorgänge anpassen:*ColorDepthReduction f8bitPixel regelt die Art des Bitmaps: Standard ist None (d.h. PixelFormat.Canonical, also 32-bit-Farben); mit Colored8bpp wird es in PixelFormat.Format8bppIndexed konvertiert und mit Grayscaled8bpp in 8bpp-Graustufen konvertiert; siehe unten Druckerprobleme. *string sPrinterName wählt einen anderen Drucker als den Standarddrucker aus. Ohne diesen Parameter wird der Standarddrucker verwendet. *Margins aMargins setzt besondere Druckränder. Ohne diesen Parameter werden die Default-Ränder des aktuellen Druckers verwendet. *bool bPortrait setzt die Orientierung: true = Hochformat, false = Querformat. Ohne diesen Parameter wird Hochformat verwendet.
Der folgende Parameter kann das Speichern anpassen:*string sFileName enthält den vollständigen Dateinamen. Bei einem Namen ohne Pfad wird der aktuelle Pfad verwendet. Ohne diesen Parameter wird ein Standardname erzeugt aus: Benutzer-Dateien "Meine Bilder", Name des Controls, Extension gemäß ImageFormat.
**Inhalt und Arbeitsablauf**1.Aufgerufen wird eine der überladenen statischen Methoden FormPrint.Print() bzw. FormPrint.Save() mit den genannten Parametern. Fast alle Parameter können weggelassen werden; diese werden dann durch Standardwerte ersetzt. 1.Alle Varianten verwenden den gleichen Ablauf über eine private statische Methode Start() mit passenden Parametern. 1.Diese prüft, ob alle nötigen Parameter vorhanden sind. 1.Danach erzeugt sie eine private Instanz cls der FormPrint-Klasse und setzt dabei die Parameter. 1.Der Konstruktor erstellt ein Bitmap-Objekt bmp mit der Größe und Graphics-Instanz des Controls und kopiert den Bildschirm-Inhalt des Controls in das Bitmap-Objekt bmp. 1.Je nachdem ruft Start() die Methode StartPrinting() bzw. StartSaving() auf. 1.Zum Drucken wird das Bitmap-Objekt bmp an ein PrintDocument übergeben. Soweit erforderlich, werden Drucker und Einstellungen angepasst. Wenn Ränder gesetzt werden, wird ggf. die Größe der Darstellung auf die Papiergröße reduziert. 1.Zum Speichern wird direkt bmp.Save() ausgeführt.
Zusätzlich enthält die Klasse try-catch-Blöcke, den nötigen PrintPageEventHandler, die Freigabe von Ressourcen sowie Hilfsmethoden GetThumbnail() zur Anpassung der Bitmap und ConvertBitmapTo8Bpp(). Weil das Konvertieren etwas länger dauert, ist auch ein kleines ProgressForm enthalten; dies wird nur angezeigt beim Drucken von mehr als 40.000 Pixeln (z.B. 200x200).
Ergänzende Hinweise
Tatsächlich sichtbarer Ausdruck: Ein Aufruf durch Menu-Click sollte auch einen ShortCut erhalten; andernfalls wird das geöffnete Menu ebenfalls gedruckt. Es wird immer der aktuelle Bildschirminhalt kopiert: wenn ein Formular teilweise verdeckt wird, wird statt des eigentlichen Formulars der tatsächlich sichtbare Bereich gedruckt. Controls, die nicht zusammenpassen, werden nicht immer gedruckt, z.B. ein normaler Button auf einer Toolbar. Bei AutoScroll müssen die Hinweise der SDK-Doku beachtet werden.
Anpassung an NET 1.1: Ich habe die Klasse unter NET 2.0 erstellt. Zwei Befehle sind unter NET 1.1 nicht möglich; einer davon muss "umständlich" ersetzt werden. Dies wird durch #define gesteuert:
/// unter NET 2.0 wie folgt:
#define NET_V20
#undef NET_V11
/// unter NET 1.1 wie folgt:
#define NET_V11
#undef NET_V20
Druckerprobleme: In beiden Versionen kann ein sehr großes Formular zu Druckerproblemen führen, z.B. Speicher voll oder Zu viele Daten. Dafür kann der Parameter ColorDepthReduction f8bitPixel verwendet werden. Wenn das Problem weiter besteht, sollte statt des Formulars ein kleineres Control, z.B. ein Panel oder eine GroupBox gedruckt werden. Ich empfehle, dann das Formular mit eigenen Einstellungen zu verbinden, welches Control ggf. zu drucken ist.
Demo-Formular: Eine Demo-Anwendung kann aus dem beiliegenden FormPrintDemoForm.cs mit der Klasse FormPrint.cs erzeugt werden. Das Formular enthält eine ListBox zur Auswahl jeder Variante sowie ein Panel mit 256 farbigen Panels. Anzupassen sind die konstanten Werte in buttonStart_Click() sowie bei NET 1.1 drei Kleinigkeiten.
Mögliche Erweiterungen: Ich habe mehr Parameter berücksichtigt als ursprünglich geplant. Ich verzichte aber auf PageSetupDialog und Erfolgsmeldung, wenn die Datei gespeichert wurde.
**Dank für Hilfe**1.Ich habe den ursprünglichen Lösungsweg aus der MSDN-Library Gewusst wie: Drucken eines Windows Form übernommen. 1.Für das Kopieren verwende ich jetzt Control.DrawToBitmap() nach einem Hinweis von Tim van Wassenhove. 1.Für NET 1.1 habe ich die Verfahren von Programmierhans (Beitrag #5) aus Drucken eines Controls sowie die Methode GetThumbNail() eingebaut. 1.robertico entwickelte ein Verfahren, um ein Bitmap schnell auf 8-bit-Farbtiefe und Graustufen zu konvertieren; siehe auch hier. (Die MSDN-Lösung mit GetPixel/SetPixel ist unerträglich langsam.) 1.Meine Arbeit fasst alles in einer Klasse FormPrint mit variablen Parametern zusammen.
Für Änderungsvorschläge bin ich weiterhin offen.
Frohes Nutzen! Jürgen
**Änderungen**1.19.12.2006
1.25.12.2006
1.13.01.2007
1.22.01.2007 (gespeichert am 23.01.2007)
1.22.03.2007
RichTextBox und WebBrowser werden auch unter NET 2.0 dargestellt
Das MSDN-Beispiel ist aber nur für VS 2005. Zudem wird im MSDN-Sample ein Graphics-Objekt nicht disposed was ich für Suboptimal halte (ich hoffe Du hast diesen Fehler nicht ungeschaut übernommen).
Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...
Danke für den Hinweis - geändert. Ich suche noch nach einem Weg, wie Graphics.CopyFromScreen() unter NET 1.1 ersetzt werden kann.
Weitere Änderung: Druck im Hochformat oder Querformat möglich.
Jürgen
Download und Beschreibung im ersten Beitrag geändert
So, jetzt funktioniert es (im Prinzip) auch unter NET 1.1. Dank an Programmierhans.
Jürgen
Download und Beschreibung im ersten Beitrag geändert
Beschreibung #1
Bekannte Probleme
Unter NET 1.1 wird ein Formular ohne Caption, Menu und Toolbar gedruckt; stattdessen ist ein kleiner Bereich des Hintergrunds zu sehen. Controls werden richtig übernommen.
Dieses Problem ist jetzt bei mir gelöst.
Beschreibung #1
In beiden Versionen kann ein sehr großes Formular zu Druckerproblemen führen, z.B. Speicher voll oder Zu viele Daten.
Für dieses Problem habe ich eine Lösung gefunden: im Fall eines Falles nicht als PNG (24-bit-Farbtiefe), sondern als GIF (8-bit-Farbtiefe) drucken. Ich bin dabei, dies als weitere Variante beim Aufruf einzubauen.
Außerdem Erweiterung: Ich möchte ein maximiertes Formular auf Seitengröße zusammenstauchen.
Alle diese Änderungen werde ich vermutlich am Wochenende bereitstellen.
Frohes Neues Jahr! Jürgen
@juetho
ich möchte dir zu deiner klasse FormPrint gratulieren! du hast mir wirklich weitergeholfen!
da ich NET 1.1 verwende, wird auch mein Formular ohne Caption, etc. gedruckt; kannst du mir vielleicht bei diesem problem bitte weiterhelfen?
mfg
Buck
Erweitere die Methode wie folgt:
private Bitmap GetBitmap(Control Ctrl) {
// create the source graphics and the required bitmap
Graphics grCtrl = Graphics.FromHwnd(Ctrl.Handle);
Bitmap Result = new Bitmap(Ctrl.Width, Ctrl.Height, grCtrl);
// we need handles to copy graphics
IntPtr hdcCtrl = grCtrl.GetHdc();
Graphics grDest = Graphics.FromImage(Result);
IntPtr hdcDest = grDest.GetHdc();
// copy the image from source hdcCtrl to destination hdcDest
// using BitBlt (bit block transfer) of Windows GDI
// a form would normally printed without title or menu bar;
// therefore, one has to move the graphics
int SourceX, SourceY;
if (Ctrl is Form) {
SourceX = Ctrl.ClientSize.Width - Ctrl.Width + 4;
SourceY = Ctrl.ClientSize.Height - Ctrl.Height + 4;
} else {
SourceX = 0;
SourceY = 0;
}
BitBlt(hdcDest, 0, 0, Ctrl.Width, Ctrl.Height, hdcCtrl, SourceX, SourceY, SRCCOPY);
// free the used objects
grCtrl.ReleaseHdc(hdcCtrl);
grDest.ReleaseHdc(hdcDest);
// that's all work for the bmp using NET 1.1 instructions
return Result;
}
Ich grüble noch über dem Problem, wie ich ein "maximized form" drucke; das führt bei mir nämlich fast immer zum Druckerproblem "memory full - too many data". Deshalb gibt es noch keine neuere Version, die alles sauer regelt.
Ich hoffe, ich konnte damit "auf die Schnelle" helfen. Jürgen
Hallo,
ich habe die Klasse FormPrint um diverse Möglichkeiten erweitert und verbessert. Ausführliche (angepasste) Beschreibung und Download siehe im ersten Beitrag; folgende Punkte sind eingebaut:
1.NET 1.1 druckt jetzt auch Formulartitel und Menüleiste
1.Aufruf geändert: statt Execute() aufgeteilt in Print() und Save()
1.Print() erlaubt mehrere Parameter: PrinterName, Margins, Orientation
1.Drucker kann auf Papiergröße komprimieren, wenn Ränder gesetzt werden
1.die meisten Parameter können weggelassen werden
1.Problem mit Druckerspeicher kann geregelt werden, indem 8-bit-Farbtiefe oder Graustufen statt 32-bit-Farbtiefe verwendet werden
1.ProgressLabel zum Drucken eines großen Controls eingefügt
1.Arbeitsablauf auf StartPrinting() bzw. StartSaving() aufgeteilt
Vermutlich ist das die "abschließende" Version.
Viel Erfolg! Jürgen
Dieser Beitrag ist eine Forsetzung von folgendem Thread: Drucken eines Controls????
Danke für die schnelle Antwort juetho.
Also ich habe deine Klasse folgendermassen aufgerufen:
PrintDialog prnDialog = new PrintDialog();
prnDialog.PrinterSettings = new System.Drawing.Printing.PrinterSettings();
prnDialog.ShowDialog();
System.Drawing.Printing.PageSettings ps = new System.Drawing.Printing.PageSettings(prnDialog.PrinterSettings);
JThomas.Tools.FormPrint.Print(this.tabProperties.TabPages[0].Controls[0], ps.PrinterSettings.PrinterName, true);
So hatte ich meine Klasse gar nicht geschrieben, sondern so, dass eine Control-Instanz übergeben wird.
Das mach ich auch, allerdings habe ich als Drucker einfach Adobe PDF gewählt. Denke nicht das, dass dies ein Unterschied macht, zumal das Abspeichern über die Save() Methode das gleiche Ergebnis bringt.
Für weitere Hilfe bin ich nach wie vor dankbar :p
Hallo bubblez,
jetzt ist mir klar, wie Du zum Acrobat Reader kommst. Dieses Print-Verfahren ist ja auch eines meiner Beispiele...
Aber Du lässt nur das 0-te Control der 0-ten TabPage drucken. 1.Um was für ein Control handelt es sich dabei? 1.Dein PDF-Ausdruck als Png-Anhang enthält doch mehr, als von "mir" erzeugt. Kannst Du genau den Bereich markieren (oder ausschneiden), der "von mir" geliefert wird? 1.Kannst Du kurz beschreiben, was ggf. fehlt und was zuviel angezeigt wird? 1.Handelt es sich in erster Linie um das AutoScroll-Problem, das Du außerdem angesprochen hattest?
Danke für nähere Angaben! Jürgen
Hm gleich ein haufen Fragen.
Ich hab im Anhang folgende Screenshots:
-Print mit Autoscroll und Scrollbars
-Print mit Autoscroll ohne Scrollbars und der .Net 1.1 Methode (nicht ganzes Control abgebildet, da zu gross -> einfach um den Inhalt des controls zu zeigen)
-Print ohne Autoscroll, wo aber welche wären wenn true
Mit der .Net 2.0 Methode krieg ich kein vollständiges Bild auch wenn das Control ganz sichtbar ist und AutoScroll auf false ist.
Ich hoffe ich konnte deine Fragen soweit beantworten.
Mysteriös. Danke für die Informationen, ich werde mich damit befassen und hoffe, dass ich schnell zu einem Ziel komme. Jürgen
Hallo bubblez,
es wird immer mysteriöser. Nach den ersten Tests weiß ich definitiv:*AutoScroll = true bringt alles durcheinander. Ich werde sehen, ob Änderungen bei AutoMargins etwas bringen. *Unter NET 2.0 werden Elemente der ToolBar nicht immer gedruckt.
Ich vermute, dass die Reihenfolge, in der die Controls aufgebaut und gezeichnet werden, schuld an der falschen Ausgabe per Bitmap ist, und melde mich wieder. Wahrscheinlich muss ich zwischendurch einige Diskussionen mit konkreten Fragen eröffnen.
Gruß Jürgen
Hallo, vor allem bubblez,
weitere Versuche waren bei mir erfolgreich.
UserControl innerhalb von TabControl.TabPages[0]
Mit den folgenden Festlegungen klappte es auf jeden Fall:
// public partial class TestUserControl enthält u.a.:
this.Size = new System.Drawing.Size(400, 400);
// das Formular enthält vor allem:
//
// tabControl1
this.tabControl1.Controls.Add(this.tabPage1);
this.tabControl1.Controls.Add(this.tabPage2);
this.tabControl1.Size = new System.Drawing.Size(520, 256);
//
// tabPage1
this.tabPage1.Controls.Add(this.testUserControl1);
this.tabPage1.Size = new System.Drawing.Size(512, 230);
//
// testUserControl1
this.testUserControl1.AutoScroll = true;
// implizit mit Dock.None:
this.testUserControl1.Size = new System.Drawing.Size(506, 230);
// oder explizit mit Dock.Fill:
this.testUserControl1.Dock = System.Windows.Forms.DockStyle.Fill;
Jeder Versuch unter NET 2.0 (Drucken oder Speichern) war erfolgreich. Ich werde es noch mit NET 1.1 versuchen.
Formular mit ScrollBars (meine ersten Tests mit falschem Ergebnis)
Wichtig war folgender Hinweis aus der SDK-Doku:
ScrollableControl.AutoScrollMargin-Eigenschaft
Es wird empfohlen, für Steuerelemente, die innerhalb eines bildlauffähigen Steuerelements angedockt werden sollen, ein untergeordnetes bildlauffähiges Steuerelement wie Panel hinzuzufügen, das alle weiteren Steuerelemente aufnimmt, für die ein Bildlauf erforderlich sein kann. Sie müssen das untergeordnete Panel-Steuerelement dem bildlauffähigen Steuerelement hinzufügen, dessen Dock-Eigenschaft auf DockStyle.Fill und die AutoScroll-Eigenschaft auf true festlegen. Sie müssen die AutoScroll-Eigenschaft des übergeordneten bildlauffähigen Steuerelements auf false festlegen.
Nicht das Formular läuft also über AutoScroll, sondern das Panel! Ein solcher Hinweis ist unter Form.AutoScroll nicht enthalten. Jeder Versuch unter NET 2.0 (Drucken oder Speichern) war damit erfolgreich.
Offen bleibt noch das neu festgestellte Problem der ToolBar.
Vorläufige Zusammenfassung: AutoScroll unter NET 2.0 sollte funktionieren. Ich werde aber wohl in die Beschreibung noch einige Hinweise aufnehmen. Jürgen
@bubblez
Wenn Du weiterhin Probleme hast, dann sende mir doch bitte (soweit möglich) die Quelltexte des Formulars und des UserControls an delphi(at)vs-polis.de
Alle meine weiteren Versuche sowohl unter NET 1.1 als auch unter NET 2.0 haben ein korrektes Ergebnis gebracht, wenn auch zum Teil erst nach Umwegen.
UserControl mit AutoScroll: Alles wird so dargestellt wie gewünscht. Bitte beachtet die bekannte Einschränkung: es wird immer nur der tatsächlich sichtbare Bereich gedruckt.
Formular mit ScrollBars: Das ist nur sinnvoll, wenn nicht mit Dock.Fill gearbeitet wird; aber dann klappt es auch. Andernfalls ist das zentrale Panel mit AutoScroll zu versehen!
Nicht gedruckter Button auf einer Toolbar: Auf einer (alten) Toolbar werden nur ToolBarButtons gedruckt. "Normale" Buttons werden zwar zur Laufzeit angezeigt, aber bei der Übertragung in das Bitmap seltsamerweise nicht berücksichtigt.
Nach meinen Versuchen kann ich also die Überarbeitung abschließen (bis zur nächsten Meldung). Jürgen
@bubblez
Wenn Du weiterhin Probleme hast, dann sende mir doch bitte (soweit möglich) die Quelltexte des Formulars und des UserControls an delphi(at)vs-polis.de
so, Änderungen werden jetzt bereitgestellt:*#define für NET-Version eingebaut *zwei bool-Parameter durch eine Enumeration zusammengefasst *Beschreibung ergänzt *Demo-Formular verwendet Panel statt ToolBar
Alles andere steht im ersten Beitrag. Jürgen
Ok. Danke juetho. Ich habe jetzt einfach mal vom XPStyle auf Windows-Classic umgestellt. Und siehe da, alles funktioniert wunderbar.
Irgendwie kann ich mir dann nicht vorstellen, dass es in dem Fall irgendwas mit dem AutoScroll zu tun hat. Werde aber weiterhin nach einer Lösung suchen die funktioniert. Hast du im WindowXP-Style gedruckt oder Classic?
Ach je, das könnte es tatsächlich sein. Darauf hatte ich bisher überhaupt nicht geachtet; aber ich habe nicht EnableVisualStyles() verwendet.
Aber wenn das ein falsches Ergebnis liefert - was kann man dann bloß machen?! Mal sehen. Jürgen
Ich bin jetzt endlich dazu gekommen, VisualStyles zu testen. Bei mir werden ScrollBars in allen Fällen korrekt ausgegeben: Formular oder Control, mit oder ohne VisualStyles, NET 2.0 oder 1.1.
Wer solche (natürlich: oder andere) Probleme feststellt, bitte melden, ggf. mit genauer Beschreibung. Danke! Jürgen
Diese Controls wurden unter NET 2.0 bisher nicht dargestellt, weil die verwendete DrawToBitmap()-Methode für diese Controls nicht zur Verfügung steht. Ich benutze jetzt das (umständlichere) Verfahren von NET 1.1, sofern eines dieser Controls enthalten ist.
Im ersten Beitrag habe ich die aktuelle Version zur Verfügung gestellt. Jürgen
Hallo,
hab mich zwar nicht an der Diskussion beteiligt (leider), aber ich muss sagen respekt!!!!
Also ich kann echt sagen (ich glaub auch im Namen aller anderen) dass ich von deiner Proffessionalität und Hilfsbereitschaft echt beeindruckt bin!
Danke Danke Danke!!
lg, Philipp
P.S.: funktioniert perfekt!
Danke für die Fleißarbeit.
Ich brauchte zwar weder Drucker noch Datei aber der MemStream (bzw. das Byte[]) war schnell integriert. Damit kann ich jetzt einige meiner Controlls problemlos auch als Bilder an XML übergeben.
Ich dokumentiere das ganze mal und schick Dirs in den nächsten Tagen.
Was haben ein Biologe und ein Programmierer gemeinsam? Sie stochern beide immer im : ˈkōd\
Hallo,
vielen Dank für die tolle Klasse!
Das Drucken klappt ganz gut nur eine Sache sieht bei mir schlecht aus, und zwar wenn mein Programm maximiert ist (also eine Aflösung von 1900x1200 hat) und ich es ausdrucke dann sieht das Bild recht pixelig aus und man kann da dann kaum was lesen.
Ist das Problem dir bekannt? Was kann man da machen?
Vielen Dank im Voraus
Steffen
Hallo Steffen,
Ist das Problem dir bekannt? Was kann man da machen?
Ich befürchte, da muss ich passen. Zum einen habe ich keinen solchen Bildschirm zur Verfügung, um das auszuprobieren. Zum anderen hatte ich seinerzeit Probleme, um zwischen "großer Darstellung" und "Druck auf eine Seite" einen sinnvollen Kompromiss zu finden. Da ich außerdem kein ScreenShot-Programm machen wollte, bei dem der Druckbereich ausgewählt werden kann, habe ich aufgehört zu versuchen.
Vielleicht findest du im Quelltext selbst eine Möglichkeit zum Eingreifen.
Jürgen