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:
  1. private void DrawAll(Graphics g) {  
  2.  ......  
  3.  // handle List View Control printing  
  4.  if (Controls[i].GetType() == this.listView1.GetType()) {  
  5.   for (int row = 0; row < listView1.Items.Count; row++) {  
  6.    int nextColumnPosition = listView1.Bounds.X;  
  7.    for (int col = 0; col < listView1.Items[row].SubItems.Count; col++) {  
  8.     g.DrawString(listView1.Items[row].SubItems[col].Text, listView1.Items[row].Font, Brushes.Black,  
  9.      (nextColumnPosition + 3) * scalex, (listView1.Items[row].Bounds.Y + listView1.Bounds.Y) * scaley,  
  10.      new StringFormat());  
  11.     nextColumnPosition += listView1.Columns[col].Width;  
  12.    }  
  13.   }  
  14.  }  
  15.  ...  
  16. }
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:
  1. [Serializable]  
  2. public class InvoiceData  
  3. {  
  4. public InvoiceDataRow[] DataRows = new InvoiceDataRow[23];  
  5. public string LogoFile = "";  
  6. public string BillerAddress = "";  
  7. public string BillToAddress = "";  
  8. public string ShipToAddress = "";  
  9. public string InvoiceDate = "";  
  10. public string InvoiceNumber = "";  
  11. public string DueDate = "";  
  12. public string PONumber = "";  
  13. public string PercentTax = "";  
  14. public string Subtotal = "";  
  15. public string Total = "";  
  16. public int RowCount = 0;   
  17. ....  
  18. }  
If we want to save the data in the form, the following method is used:
  1. void SaveForm() {  
  2.  // find a suitable place to put the invoice output  
  3.  if (saveFileDialog1.ShowDialog() == DialogResult.OK) {  
  4.   // Fill the InvoiceData Object with the Form Input  
  5.   FillInvoiceDataWithForm();  
  6.   // Create a BinaryFormatter object for streaming data out to a file  
  7.   IFormatter formatter = new BinaryFormatter();  
  8.   // Create a file stream for writing  
  9.   Stream stream = new FileStream(saveFileDialog1.FileName, FileMode.Create,  
  10.    FileAccess.Write, FileShare.None);  
  11.   // Serialize the members of the InvoiceData class into the file  
  12.   formatter.Serialize(stream, TheInvoiceData);  
  13.   stream.Close();  
  14.  }  
  15. }
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:
  1. void OpenForm() {  
  2.  // Get the name of the file you want to load into the form  
  3.  if (openFileDialog2.ShowDialog() == DialogResult.OK) {  
  4.   // Create a BinaryFormatter object for reading in the data  
  5.   IFormatter formatter = new BinaryFormatter();  
  6.   // Open a stream to the file containing the data  
  7.   Stream stream = new FileStream(openFileDialog2.FileName, FileMode.Open, FileAccess.Read, FileShare.None);  
  8.   // Deserialize the Data into the InvoiceData Object  
  9.   TheInvoiceData = (InvoiceData) formatter.Deserialize(stream);  
  10.   stream.Close();  
  11.   // Transfer the data from the InvoiceData object into the Form  
  12.   FillFormWithInvoiceData();  
  13.   // ** This section is for UI maintenance after reading the file, and unrelated to serialization **  
  14.   // Initialize Itemized row for editing row 0  
  15.   InitializeRowEditing(0);  
  16.   // Dump the first row of the List View into the row of edit  
  17.   boxes  
  18.   DumpListViewToRow(0);  
  19.   // ******************************************************  
  20.  }  
  21. }
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.
  1. 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!