Due to the overwhelming complexity and/or limited license capabilities of the components available for this job, I have decided to write this component from scratch. This is something I have fully functional in PHP, and in VB6. but I am hitting a wall when trying to add a page .
A lot of great examples on how to print from file, or how to print a single page (all graphics etc are hard coded for the pages inside the Print event), but nothing on how to setup a collection to hold the page data, and then send those to be printed.
In vb6, you can obtain the pagebounds and call new page, but in .NET, there doesn't seem to be a new page method.
Following is the source I have so far, which is pretty rough due to the apparent lack of this basic functionality.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using PdfFileWriter;
using System.Drawing.Printing;
using System.ComponentModel;
using System.IO;
using System.Drawing.Printing;
class PDF : PrintDocument {
/// <summary>
/// Logo to display on invoice
/// </summary>
public Image Logo { get; set; }
/// <summary>
/// Pages drawn in document
/// </summary>
private List<Graphics> Pages;
private int CurrentPage;
private string directory;
private string file;
/// <summary>
/// Current X position
/// </summary>
public int X { get; set; }
/// <summary>
/// Current X position
/// </summary>
public int Y { get; set; }
/// <summary>
/// Set the folder where backups, downloads, etc will be stored or retrieved from
/// </summary>
[Editor( typeof( System.Windows.Forms.Design.FolderNameEditor ), typeof( System.Drawing.Design.UITypeEditor ) )]
public string Folder { get { return directory; } set { directory=value; } }
public PDF() {
file = (string)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds.ToString();
directory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
CurrentPage = 0;
// initialize pages array
Pages = new List<Graphics>();
PrinterSettings = new PrinterSettings() {
PrinterName = "Microsoft Print to PDF",
PrintToFile = true,
PrintFileName = Path.Combine(directory, file + ".pdf"),
};
DefaultPageSettings = new PageSettings(PrinterSettings) {
PaperSize=new PaperSize("Letter", 850, 1100 ),
Landscape = false,
Margins = new Margins(left: 50, right: 50, top: 50, bottom: 50),
};
}
/// <summary>
/// Get specific page
/// </summary>
/// <param name="page">page number. 1 based array</param>
/// <returns></returns>
public Graphics GetPage( int page ) {
int p = page - 1;
if ( p<0||p>Pages.Count ) { return null; }
return Pages[p];
}
public Graphics GetCurrentPage() {
return GetPage(CurrentPage);
}
protected override void OnBeginPrint( PrintEventArgs e ) {
base.OnBeginPrint( e );
}
protected override void OnPrintPage( PrintPageEventArgs e ) {
base.OnPrintPage( e );
}
protected override void OnEndPrint( PrintEventArgs e ) {
base.OnEndPrint( e );
}
/// <summary>
/// Add a new page to the document
/// </summary>
public void NewPage() {
// Add a new page to the page collection and set it as the current page
Graphics g = Graphics.CreateCraphics(); // not sure if this works, but no CreateGraphics is available
Pages.Add( g );
}
/// <summary>
/// Add a new string to the current page
/// </summary>
/// <param name="text">The string to print</param>
/// <param name="align">Optional alignment of the string</param>
public void DrawString(string text, System.Windows.TextAlignment align = System.Windows.TextAlignment.Left ) {
// add string to document
Pages[CurrentPage].DrawString(text, new Font("Arial", 10), new SolidBrush(Color.Black), new PointF(X, Y));
}
/// <summary>
/// Save the contents to PDF
/// </summary>
/// <param name="FileName"></param>
public void Save( string FileName ) {
// Start the print job looping through pages.
foreach ( Graphics page in Pages ) {
// there doesn't seem to be an addpage method
}
/*
* From stackoverflow article on how to 'print' a pdf to filename as the poster complained
* that the PrinterSettings.PrintFileName property is ignored. Havn't tested yet. Also, no
* such function as 'PrintOut' so further research required.
*
PrintOut(
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
FileName,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value,
System.Reflection.Missing.Value
);
*/
}
}
I am not looking for a really long winded massive project on how to write PDF documents as they are all very restrictive, each of them has at least one limitation which is a problem for the layout I intend to design (upgrade from PHP which was an upgrade from VB6). The end result layout looks like this >
First Page (Invoice main page )
Second Page(s) [summary]
This report may have more pages depending on how many items are in payments and services. Header of the sub-report that continues rolls over to the next page if there are many items. For example, if a customer has 200 services, those items will continue in a similar fashion using the same "Payments" header block at the start of each consecutive page.
Detailed Reports
There may be multiple detail reports, each one starting at the beginning of a new page, and the page counter is reset and printed for those pages. So page 6 of the invoice might actually be Page 3 of the second detail report. Each report starts and ends like the following (and picture depicts layout of field data, etc)
Report first page
Report last page
What am I looking for ?
A reliable way to make the above multi-report invoice layout work in Visual Studio .NET. I am looking to port code away from php and vb6, and I am not interested in using libraries that are either massive in distribution size, or ridiculously complex / limited license restrictions. Microsoft provides some very powerful tools built-in, and I am not adverse to using the built-in PDF print driver and spooling the data, even though that is a bit of a hack, it does seem to be the least complex method to make this functional without the restrictions or bloat of 3rd party controls. (including open source, as the ones I looked at tend to do some very strange conversions to char, then maybe latex or something, not entirely sure what all the conversion stuff is about).
NOTE
It is very important to understand that the combination of the above report styles make up ONE invoice, and thus only one pdf file per client. If it helps, here is a VB6 backwards compatability method exposing the traditional 'Print' object printing compatability vb6. This should help clarify the native functionality I am looking to create/use.
I am having a difficult time swallowing the above "no direct equivalent" statement, as adding a new page when creating a document in memory seems to be a pretty basic (and essential) function of creating a document for print. It doesn't make sense that everything needed to be printed MUST be loaded from a file first.







This is my implementation of a working solution to this problem, allowing a user to completely design the document in a reusable manner without ever requiring to send the
.Printcommand.The concept of using an Image to store the data was in part due to a comment by Bradley Uffner on this question regarding combining two
GraphicsobjectsThere are several advantages and disadvantages to handling the process in this manner broken down below.
Advantages
OnPrintPageisn't overly complexDisadvantages
Class File
This also demonstrates how portable this is, as anyone can reuse it quickly. I am still working on wrapping the Draw methods, however this code demonstrates the objective needing only to really extend with more draw methods, and possibly some other features I may have missed.
Example Usage