Introduction
Ordermate was created to simplify the process of building an invoice of materials
based on Movie 'product' requests.
The actors of this scenario are: Distribution Company Employee (i.e. User),
Theater Company Contact (i.e. Customer).
Ordermate covers the following workflow for movie order processing.
- Movie Theater 'Customer' requests to place an order for one or more movies.
- Distributor company Employee selects Customer from ComboBox dropdown menu.
- Requested products are selected from a CheckedListBox.
- Employee enters the Quantity of items ordered.
- Employee clicks the [Add] button to update form.
- Employee has option to View the invoice on screen and/or
Export the invoice to Microsoft Excel.
Purpose
Ordermate is a demonstration of using xml as a flat file type data store.
XML can be converted, formatted, and imported to any type database you choose
(i.e. MS Access, SQL Server, Oracle, MYSQL, etc.)
The purpose of this article is to give you a sense of "what it is like" to build a .NET
application using XML Serialization. It demonstrates a variety of programing topics
for the entry to mid-level VB.NET programmer.
Topics covered:
- XmlSerializer: Serializes and deserializes objects into and from XML documents.
- PrintDocument: Create custom invoice forms for Print and Print preview.
- DocumentFormat.OpenXml: Open XML Format to create/output Excel
spreadsheet invoices.
- SpreadsheetLight:
SpreadsheetLight is an open
source Open XML spreadsheet library for .NET Framework.
- DataBindings:
Data
binding is the process that establishes a connection between the application UI
and business objects.
- StringRandomGenerator: Helps to generate random invoice numbers.
Ordermate generally involves the following: Process movie inventory,
create invoices, create, edit, and update product supplies. It can be used
to Read/Write XML files. XML input/output and to export to a Microsoft Excel
worksheet. Invoices maybe viewed for Print and Print preview.
Read Write XML.
Background
History
Ordermate is actually and update to a program that I created about 10 yrs ago.
It was recently revisited due to a client's need to reformat some Nessus Scanner
By Tenable report files.
The files themselves are just plain vanilla xml datasets that needed to be
extracted, formatted, and then dropped into a Microsoft Excel spreadsheet.
Steps
The following steps were taken to generate class files based on existing xml file(s):
- Create xml file(s) using standard text editor.
- Use xsd.exe to generate schema file(s) (*.xsd).
- - start Visual Studio Tools 'Developer Command Prompt'
- - Format / correct type schema properties
- Use xsd.exe to generate class file(s) from Step 2
- - xsd products.xsd /classes /language:VB
- - xsd customers.xsd /classes /language:VB
- Clean up the generated class file(s) (*.vb)
- Create Visual Studio Solution, include (*.vb) file(s)
XmlSerializer
DataCollection Class
The DataCollection
class represents the primary -top level element
of the Xml file. Only one top level element is allowed in an XML document.
It holds a reference to all Customer and Product objects found within a deserialized
xml file. Once all Products and Customers are loaded it triggers a
PropertyChangedEvent
which a listener (i.e. Form control) can
respond to.
Public Class DataCollection
Implements System.ComponentModel.INotifyPropertyChanged
Private _Products As List(Of Product)
Private _Customers As List(Of Customer)
Public Property Products() As List(Of Product)
Get
Return _Products
End Get
Set(ByVal value As List(Of Product))
_Products = value
RaisePropertyChanged("Products")
End Set
End Property
Public Property Customers() As List(Of Customer)
Get
Return _Customers
End Get
Set(ByVal value As List(Of Customer))
_Customers = value
RaisePropertyChanged("Customers")
End Set
End Property
Public Event PropertyChanged As _
System.ComponentModel.PropertyChangedEventHandler _
Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Protected Sub RaisePropertyChanged(ByVal propertyName As String)
Dim propertyChanged As _
System.ComponentModel.PropertyChangedEventHandler _
= Me.PropertyChangedEvent
If (propertyChanged IsNot Nothing) Then
propertyChanged(Me, _
New System.ComponentModel.PropertyChangedEventArgs(propertyName))
End If
End Sub
End Class
Deserialization
In order to deserialize data from an xml file into class objects.
We need to utilize the XmlSerializer
class.
This procedure is taken care of in the Initialize
method of the FrmMain
class. As Deserialization
occurs connect an EventHandler to receive and handle events.
Private Sub Initialize()
_Data = New DataCollection
AddHandler _Data.PropertyChanged, AddressOf OnDataChanged
Dim XmlDataCollection As DataCollection = Nothing
Dim format As New XmlSerializer(GetType(DataCollection))
Using CustomerStream As Stream = _
File.OpenRead(DataDirectory & CustomersXml)
XmlDataCollection = DirectCast(format.Deserialize(CustomerStream), _
DataCollection)
If Not XmlDataCollection Is Nothing Then
_Data.Customers = XmlDataCollection.Customers
End If
End Using
...
End Sub
Serialization
The opposite of Deserialization
is Serialization
.
Serialization involves saving data back to an xml file.
You can do this by again using the XmlSerializer
class.
Each time an edit is made to a Customer's data it can be written
back to the hard disk. For the FrmCustomer
class this is done in
the SaveCustomers
method.
Private Sub SaveCustomers()
Dim serializer As New XmlSerializer(GetType(DataCollection))
Dim Data As New DataCollection
Data.Customers = _Customers
Using CustomerStream As Stream = _
New FileStream(DataDirectory & "Customers.xml", FileMode.Create)
Dim writer As New XmlTextWriter(CustomerStream, Encoding.Unicode)
serializer.Serialize(writer, Data)
End Using
End Sub
PrintDocument
The PrintDocument
class defines a reusable object that sends
output to a printer, when printing from a Windows Forms application.
The following method creates the invoice on screen for print out.
Private Sub CreateInvoiceDocument(ByVal g As Graphics)
Dim srcRect As RectangleF = New Rectangle(0, 0, InvoiceSize.Width, _
InvoiceSize.Height)
Dim nWidth As Integer = _
docInvoice.PrinterSettings.DefaultPageSettings.PaperSize.Width
Dim nHeight As Integer = _
docInvoice.PrinterSettings.DefaultPageSettings.PaperSize.Height
Dim destRect As RectangleF = New Rectangle(0, 0, nWidth, nHeight)
Dim scalex As Single = CSng(destRect.Width / InvoiceSize.Width)
Dim scaley As Single = CSng(destRect.Height / InvoiceSize.Height)
Dim aPen As New Pen(Brushes.Black, 1)
If picGroupLogo.BackgroundImage IsNot Nothing Then
Dim gu As GraphicsUnit = GraphicsUnit.Pixel
Dim scaledRectangle As RectangleF = GetScaledRectangle(scalex, _
scaley, picGroupLogo.Bounds)
Dim myImage As Image = CType(picGroupLogo.BackgroundImage.Clone(), _
Image)
g.DrawImage(myImage, scaledRectangle, _
picGroupLogo.BackgroundImage.GetBounds(gu), GraphicsUnit.Pixel)
End If
WriteInvoiceHeader(g, scalex, scaley)
WriteInvoiceLineItems(g, scalex, scaley)
End Sub
SpreadsheetLight
Probably the most exciting portion of this program for me was finding the
OpenSource
SpreadsheetLight
[^]
library. Although the source is written in C# I am still a fan of this nifty little utility.
Maybe someday later I will translate it over to a more sensible language like
Visual Basic (i.e. VB.NET). ;-)
All jokes aside SpreadsheetLight is a great utility.
Below is an example of how toExport invoice data into a
Microsoft Excel spreadsheet. A preformatted template spreadsheet is used and
invoice data is dropped onto it. Creating a Helper class makes is simple to format
and manipulate the eventual output.
Public Class Helper
...
Private Sub Create(ByVal inInvoice As Invoice, info As FileInfo)
Dim sl As New SLDocument(TemplateDirectory & "Invoice.xlsx", "Invoice")
...
For Each item As LineItem In SortedHost
...
Next item
sl.SaveAs(OutputFile)
End Sub
...
End Class
Databinding
Databinding makes it easier and much simpler to bind class properties to
System.Windows.Forms.Control
properties.
In the Databind method of the FrmProduct form we bind all the neccessary controls
to a specific Product object.
Private Sub Databind(ByVal item As Product)
ClearDatabindings()
cboCategory.DataBindings.Add("Text", item, "Category")
nbrPrice.DataBindings.Add("Value", item, "UnitPrice")
txtYear.DataBindings.Add("Text", item, "Year")
txtTitle.DataBindings.Add("Text", item, "Name")
nbrRating.DataBindings.Add("Text", item, "Rating")
txtNumber.DataBindings.Add("Text", item, "Number")
txtDescription.DataBindings.Add("Text", item, "Description")
txtActor.DataBindings.Add("Text", item, "Actor")
nbrQuantity.DataBindings.Add("Value", item, "Quantity")
End Sub
StringRandomGenerator
The StringRandomGenerator
is actually a custom designed class
derived from an article found here on CodeProject.
Appended in the GetRandom
method is code to put a dash after
every fourth Char
.
Public Function GetRandom() As String
...
If i > 0 AndAlso (i Mod 4) = 0 Then sb.Append("-")
...
End Function
Summary
Ordermate can be used as a basic example of utilizing the XMLSerializer class
to automate the creation of invoice forms.
The purpose of this article was to give you a sense of the experience of
building an WindowsForm application based upon XMLSerialization techniques.
I hope that you discovered that building an XML flat file application is not difficult.
In this article, we examined only the most basic features of the .NET framework
and using component libraries.