Printing Invoices using C# and .NET


The other day, I decided to cruise ZDNET to look for a simple shareware program to make invoices. I have to say, I couldn't find what I was looking for, so I turned to C# and .NET.  This program can stand improvements but it will get you started in creating an invoice and printing it to the printer. You can customize the invoice by changing the bitmap supplied in the download to an invoice of your choice and then move the controls to fit into the proper locations on the Background bitmap.  This invoice layout was scanned in  from Intuit's, Quicken 99 and modified to add a few features.

Figure 1 - The Invoice in a Windows Form

Below is the design for the InvoiceMaker.NET program in UML:

Figure 2 - UML Diagram of InvoiceMaker.NET reverse engineered using WithClass 2000

The program has several pieces of code probably worth discussing.  In this article we'll concentrate on printing and serialization.  If you are curious about how the printing was designed, you'll want to check out my article on the W-2 Form in which I borrowed most of the printing code. The only section that differs radically is how I printed out the ListView.  Below is the code for printing a ListView called from the printDocument1_PrintPage event handler:

private void DrawAll(Graphics g)
{......
// handle List View Control printing
if (Controls[i].GetType() == this.listView1.GetType())
{
for (int row = 0; row < listView1.Items.Count; row++)
{
int nextColumnPosition = listView1.Bounds.X;
for (int col = 0; col < listView1.Items[row].SubItems.Count; col++)
{
g.DrawString(listView1.Items[row].SubItems[col].Text, listView1.Items[row].Font,Brushes.Black,
(nextColumnPosition + 3)*scalex,(listView1.Items[row].Bounds.Y + listView1.Bounds.Y)* scaley,
new StringFormat());
nextColumnPosition += listView1.Columns[col].Width;
}
}
}
...
}

This code simply cycles through the ListView Items and through each ListView Item's Subitem and prints them to the Graphics surface using DrawString.  The horizontal position of the string is determined using the widths in the Columns collection of the ListView. (Note that the position is scaled to the printing surface as in the W-2 Forms article).

Serialization

In order to serialize the form, I created a separate object that can serialize itself called InvoiceData and then filled and unfilled the form using this object.  (I tried to make the Form serializable, but .NET didn't like that).  The InvoiceData class also contains a collection of objects called InvoiceDataRows, which are also serializable.  To make an object serializable in .NET, simply stick the attribute Serializable above the class as shown below:

[Serializable]
public class InvoiceData
{
public InvoiceDataRow[] DataRows = new InvoiceDataRow[23];
public string LogoFile = "";
public string BillerAddress = "";
public string BillToAddress = "";
public string ShipToAddress = "";
public string InvoiceDate = "";
public string InvoiceNumber = "";
public string DueDate = "";
public string PONumber = "";
public string PercentTax = "";
public string Subtotal = "";
public string Total = "";
public int RowCount = 0;
....
}

If we want to save the data
in the form, the following method is used:

void SaveForm()
{
// find a suitable place to put the invoice output
if (saveFileDialog1.ShowDialog() == DialogResult.OK)
{
// Fill the InvoiceData Object with the Form Input
FillInvoiceDataWithForm();
// Create a BinaryFormatter object for streaming data out to a file
IFormatter formatter = new BinaryFormatter();
// Create a file stream for writing
Stream stream = new FileStream(saveFileDialog1.FileName, FileMode.Create,
FileAccess.Write, FileShare.None);
// Serialize the members of the InvoiceData class into the file
formatter.Serialize(stream, TheInvoiceData);
stream.Close();
}
}

The BinaryFormatter object spits out the data into a file containing all the members of the InvoiceData class.  Because InvoiceDataRow is also serializable and because its a member of the InvoiceData class as an array, the entire InvoiceDataRow[] array is also spit out to the file. (This is a serious time saver, still it would be nice if Forms were serializable).

To Deserialize the data, we use the method below:

void OpenForm()
{
// Get the name of the file you want to load into the form
if (openFileDialog2.ShowDialog() == DialogResult.OK)
{
// Create a BinaryFormatter object for reading in the data
IFormatter formatter = new BinaryFormatter();
// Open a stream to the file containing the data
Stream stream = new FileStream(openFileDialog2.FileName, FileMode.Open,FileAccess.Read, FileShare.None);
// Deserialize the Data into the InvoiceData Object
TheInvoiceData = (InvoiceData)formatter.Deserialize(stream);
stream.Close();
// Transfer the data from the InvoiceData object into the Form
FillFormWithInvoiceData();
// ** This section is for UI maintenance after reading the file, and unrelated to serialization **
// Initialize Itemized row for editing row 0
InitializeRowEditing(0);
// Dump the first row of the List View into the row of edit
boxes
DumpListViewToRow(0);
// ******************************************************
}
}
}

Deserialization is just as simple.  TheInvoiceData class knows how to populate itself from the BinaryFormatter since its Serializable.  To get the data from the file into the InvoiceData object, you simply call Deserialize on the file stream you want to populate from. (Of course the file must be one that you previously created using serialize on this object, otherwise it won't work).

DateTime Controls

The Invoice program uses DateTime Controls to populate the dates in the form.  Once nice featrue of these controls is you can create custom formats by setting the Format property of the control to Custom and the CustomFormat property to the format you wish to display (mine are set to MM/dd/yy).

String Formatting an EditBox

To get the amounts converted into currency format, I used a nice feature in .NET that allows you to specify how you want a double value to look in the ToString method.  Below is the line of code that will format the sum(a double type) of the Amount column to look like a dollar and cents value.

SubtotalTextBox.Text = sum.ToString("#,###.00");

Improvements

This program would be better, if it didn't use serialization, but dumped its contents into a database.  Then you could track customer information, marketing data, and other nice things a business cares about in their invoices.  Anyway, if you want to spit out a quick invoice to your printer, this program should do the job. Happy billing!

Up Next
    Ebook Download
    View all
    Learn
    View all