MDI Application Case Study - Part I - Introduction
MDI Application Case Study - Part 1 - Introduction
Introduction
This is Part I of an ongoing case study for the development of an MDI application. The goal is to touch on many aspects of the process, in parts, to be a guide for handling different aspects of the general needs of such applications. The example program will be a inventory purchasing environment, and will be an MDI application for producing purchase orders, including the use of object data sources. This guide will assume you have a working knowledge of object oriented coding in general, as well as C# syntax. As we progress, we will cover most if not all of the familiar Multiple Document Interface type functions, and how to employ them in your application. Other notes of interest will be custom events, Win32 dllimports, Object Data Sources, XML and Binary serialization, Emailing, GDI Printing, and PDF incorporation using MigraDoc and PDFSharp libraries, and tool bar docking.
Our First Object Class - Item
Our first object class, Item
, will contain information about each individual item in our sample company's inventory. Below is the start of our class, but as this case study progresses, we will be expanding this class to feature more properties, methods, and events. We will use a few conventions throughout this study. First, all private
properties will be defined with names starting with the underscore character, to help differentiate scope as we start to incorporate more advanced code. Secondly, although you can easily just empty public
properties with empty getters and setters, this habit for our case study will prove to be problematic as we progress, as there will be times when getting or setting, we will want to also run other lines of code. An empty getter or setter will not allow this, so we want to use private
properties exposed by public
definitions so that we can better control what happens on each get and set call.
using System;
namespace MDICaseStudyPurchasing.ObjectBase
{
public class Item
{
private String _productNumber;
private String _description;
private double _cost;
private int _purchaseQty;
public Item()
{
_productNumber = String.Empty;
_description = String.Empty;
_cost = 0;
_purchaseQty = 0;
}
public String ProductNumber
{
get { return _productNumber; }
set { _productNumber = value; }
}
public String Description
{
get { return _description; }
set { _description = value; }
}
public double Cost
{
get { return _cost; }
set { _cost = value; }
}
public int PurchaseQuantity
{
get { return _purchaseQty; }
set { _purchaseQty = value; }
}
}
}
As you can see, so far this is pretty basic, a part number, description, cost, and quantity we are buying. Now let's touch on object inheritance as we create a collection class to handle groups of our Item
objects. We create a collection class that inherits a typed list, List<T>
. If you are unfamiliar with typed classes, they are somewhat advantageous. They will by default pass the objects it holds as the correct type, and will reject attempts to add objects of the incorrect type. Typed lists, List<T>
can be found in the System.Collections.Generic
library.
using System;
using System.Collections.Generic;
namespace MDICaseStudyPurchasing.ObjectBase
{
public class ItemCollection : List<Item>
{
public ItemCollection() { }
}
}
The collection above is already extremely robust and ready to go, by inheriting the List<T>
class, we already have an Add(Item)
, Remove(Item)
, and Contains(Item)
methods, as well as this[int]
getter/setter which will return the Item
at the specified index. However, let's add a couple more interesting methods. In our case study, the item's product number will be a unique identifier, no two items will ever have the same product number. These 2 methods will help to facilitate this functionality for us. When you have multiple methods by the same name, but with different arguments, that's known as overloading a method.
public bool Contains(String productNumber)
{
foreach (Item i in this)
{
if (i.ProductNumber == productNumber) return true;
}
return false;
}
public Item this[String productNumber]
{
get
{
foreach (Item i in this)
{
if (i.ProductNumber == productNumber) return i;
}
return null;
}
}
As we add items into this collection, we can first call Contains(String)
to be sure the item isn't already in the collection, or more accurately, to be sure another item with the same product number isn't already there. Then we can get the requested item using the this[String]
getter. You could also put a set
clause into the custom this[String]
method if you wish, but we won't need it for this case study. The foreach
clause is very nice, the only caveat with it is that the collection you are stepping through cannot be modified while the foreach
clause is in effect. If you need to modify as you step through, then you'll need to use the for(;;)
clause.
Object Inheritance - A Further Look
Next, let's create a few object classes we will need to handle addresses used throughout our application. We will create a base Address
class, as well as 3 extended classes, ShippingAddress
, VendorAddress
, and BillingAddress
. Fist, each of our three addresses we will use will all share a large number of properties, so let's create a base Address
class that will include these shared properties.
using System;
namespace MDICaseStudyPurchasing.ObjectBase
{
public class Address
{
private String _attention;
private String _companyName;
private String _addressLine1;
private String _addressLine2;
private String _city;
private String _state;
private String _zipCode;
public Address()
{
_attention = _companyName = _addressLine1 = _addressLine2 =
_city = _state = _zipCode = String.Empty;
}
public String Attention
{
get { return _attention; }
set { _attention = value; }
}
public String CompanyName
{
get { return _companyName; }
set { _companyName = value; }
}
public String AddressLine1
{
get { return _addressLine1; }
set { _addressLine1 = value; }
}
public String AddressLine2
{
get { return _addressLine2; }
set { _addressLine2 = value; }
}
public String City
{
get { return _city; }
set { _city = value; }
}
public String State
{
get { return _state; }
set { _state = value; }
}
public String ZipCode
{
get { return _zipCode; }
set { _zipCode = value; }
}
}
}
Who wants to write all that code 3 times? Again object orientation and inheritance helps us greatly. Now we can extend this base class in each of our extension address classes. First our BillingAddress
:
using System;
namespace MDICaseStudyPurchasing.ObjectBase
{
public class BillingAddress : Address
{
private String _phoneNumber;
private String _faxNumber;
public BillingAddress()
{
_phoneNumber = _faxNumber = String.Empty;
}
}
public String PhoneNumber
{
get { return _phoneNumber; }
set { _phoneNumber = value; }
}
public String FaxNumber
{
get { return _faxNumber; }
set { _faxNumber = value; }
}
}
Again, due to inheritance, we automatically get all the properties contained in Address
, so we just have to add the properties that make BillingAddress
unique, PhoneNumber
and FaxNumber
. Next the VendorAddress
:
using System;
namespace MDICaseStudyPurchasing.ObjectBase
{
public class VendorAddress : Address
{
private String _phoneNumber;
private String _faxNumber;
public VendorAddress()
{
_phoneNumber = _faxNumber = String.Empty;
}
public String PhoneNumber
{
get { return _phoneNumber; }
set { _phoneNumber = value; }
}
public String FaxNumber
{
get { return _faxNumber; }
set { _faxNumber = value; }
}
}
}
And finally, the ShippingAddress
:
using System;
namespace MDICaseStudyPurchasing.ObjectBase
{
public class ShippingAddress : Address
{
private String _reference;
public ShippingAddress()
{
_reference = String.Empty;
}
public String Reference
{
get { return _reference; }
set { _reference = value; }
}
}
}
As you can see, inheritance saves us time, but it also provides a sense of hierarchy and organization. Our next class is the Vendor
class. The vendor
object class will hold all of the information for each source of products our sample company uses.
using System;
namespace MDICaseStudyPurchasing.ObjectBase
{
public class Vendor
{
private String _vendorID;
private VendorAddress _address;
public Vendor()
{
_vendorID = String.Empty;
_address = new VendorAddress();
}
public String VendorID
{
get { return _vendorID; }
set { _vendorID = value; }
}
public VendorAddress Address
{
get { return _address; }
set { _address = value; }
}
}
}
And again, we want a collection to keep up with all of the vendors for our company, so we will create an extended List<T> VendorCollection
. And the VendorID
will be the unique identifier for vendors, so we will also overload the Contains
and this
methods to use the VendorID
, similar to what we did in the ItemCollection
above.
using System;
using System.Collections.Generic;
namespace MDICaseStudyPurchasing.ObjectBase
{
public class VendorCollection : List<Vendor>
{
public VendorCollection() { }
public bool Contains(String vendorID)
{
foreach (Vendor v in this)
{
if (v.VendorID == vendorID) return true;
}
return false;
}
public Vendor this[String vendorID]
{
get
{
foreach (Vendor v in this)
{
if (v.VendorID == vendorID) return v;
}
return null;
}
}
}
}
The final class for this section, and the last of our main object classes is the PurchaseOrder
. This will keep up with all of the information about each specific order created, the addresses, the items purchased, the vendor, and all other pertinent information.
using System;
namespace MDICaseStudyPurchasing.ObjectBase
{
public class PurchaseOrder
{
private String _purchaseOrderNumber;
private Vendor _vendor;
private BillingAddress _billingAddress;
private ShippingAddress _shippingAddress;
private VendorAddress _vendorAddress;
private ItemCollection _items;
public PurchaseOrder()
{
_purchaseOrderNumber = String.Empty;
_vendor = new Vendor();
_billingAddress = new BillingAddress();
_shippingAddress = new ShippingAddress();
_vendorAddress = _vendor.Address;
_items = new ItemCollection();
}
public String PurchaseOrderNumber
{
get { return _purchaseOrderNumber; }
set { _purchaseOrderNumber = value; }
}
public Vendor Vendor
{
get { return _vendor; }
set { _vendor = value; }
}
public BillingAddress BillingAddress
{
get { return _billingAddress; }
set { _billingAddress = value; }
}
public ShippingAddress ShippingAddress
{
get { return _shippingAddress; }
set { _shippingAddress = value; }
}
public VendorAddress VendorAddress
{
get { return _vendorAddress; }
set { _vendorAddress = value; }
}
}
}
Just a note concerning handling of document data. It's tempting to try and consolidate data storage but referencing, but this is froth with potential mistakes. For example, you can see in our PurchaseOrder
class, I could have easily not included a VendorAddress
property since it could be retrieved from the Vendor
, however as you will see as we progress, the vendors will be stored in an application wide collection, which can change over time, addresses, phone numbers, all these can change, and if you try to reference current data onto an old document, you can have problems. Only reference data when you know that the referenced data is not subject to future change.
Moving Forward
On the next installment, we will begin delving into the UI aspects of our application. Creating an MDI platform, using a TaskBar icon, and starting up with a loading splash screen. If this interests you, stick with it, because things will start to get even more interesting as we progress.
Points of Interest
- Basic object inheritance
- Overloaded methods in inherited classes
History
- Original - 10/28/2015