How To Fix My Class Structure?

Sn0wb0y
Sn0wb0y used Ask the Experts™
on
First time poster, hopefully it's okay.

I am developing a windows forms desktop application and am having some difficulty with my class design.  Based on the following background, I am seeking expert feedback and guidance on what I need to add to my shipment container class to allow for adding a container to a shipment?

As this is an ongoing project that will require further input, I thought it would be best to provide some background information to help articulate it's purpose.

Application structure and functional usage descriptions are defined follows:
 
Shipment
- Create a shipment.
- Update the attributes of a shipment.
- Delete a shipment.
- Provide a summary of the shipment details. i.e. Container count, Package count, Total weight, Total cube...

Shipment Container
- Create + Add a container to a shipment.
- Update the attributes of a container in a shipment.
- Delete a container from a shipment.
- List all containers in a shipment.
- List all packing quantities in a container.
- Track packing quantities and load utilization rates for each container.
- Provide a summary of load quantities and utilization rates for each container. i.e. Package count, Load weight, Load cube...

Shipment Container Package
- Create + Add a quantity of packages to a container.
- Update the attributes of a package container in a shipment.
- Delete a package from a container and/or all containers.
- List all packing quantities in a container.
- Track packing quantities and load utilization rates for each line item.
- Provide a summary of load quantities and utilization rates for each individual line item. i.e. Package count, Total weight, Total cube...

Inside my project, I have the following three classes:

Shipment - This class provides for a base shipment.

public class Shipment 
    {
        #region Properties

        public string ShipmentID { get; set; }
        public string ShipmentName { get; set; }
        public static double ShipmentWeight { get; set; }
        public static double ShipmentCube { get; set; }
        public static int PackageCount { get; set; }
        public static int ContainerCount { get; set; }

        #endregion

        #region Constructor

        public Shipment(string shipmentName)
        {
            ShipmentID = Extension.NewID();
            ShipmentName = shipmentName;
        }

        #endregion
    }

Open in new window


Shipment Container - This class provides for all containers in a shipment.

public class ShipmentContainer
    {
        #region Properties

        public string ShipmentID { get; set; }
        public string ContainerID { get; set; }
        public string ContainerDescription { get; set; }
        public double CapacityWeight { get; set; }
        public double ContainerWeight { get; set; }
        public double CapacityCube { get; set; }
        public double ContainerCube { get; set; }
        public static int ContainerPackageCount { get; set; }
        public static BindingList<ShipmentContainer> Containers { get; set; }
        public bool IsFull { get; set; }

        #endregion

        #region Constructor

        public ShipmentContainer(string shipmentID, string containerDescription, double capacityWeight, double capacityCube)
        {
            ShipmentID = shipmentID;
            ContainerID = Extension.NewID();
            ContainerDescription = containerDescription;
            CapacityWeight = capacityWeight;
            CapacityCube = capacityCube;
        }

        #endregion

        #region Helper Methods

        private double ContainerUtilizationWeight;
        public double GetContainerUtilizationWeight()
        {
            ContainerUtilizationWeight = (double)ContainerWeight / CapacityWeight * 100;
            return ContainerUtilizationWeight;
        }

        private double ContainerUtilizationCube;
        public double GetContainerUtilizationCube()
        {
            ContainerUtilizationCube = (double)ContainerCube / CapacityCube * 100;
            return ContainerUtilizationCube;
        }

        #endregion
    }

Open in new window


Shipment Container Package - This class provides for all packages in all containers in a shipment.

public class ContainerPackage
    {
        #region Properties

        public string ShipmentContainerID { get; set; }
        public string PackageID { get; set; }
        public string PackageDescription { get; set; }
        public int PackingQty { get; set; }
        public double PackageCube { get; set; }
        public double PackingCube { get; set; }
        public double PackageWeight { get; set; }
        public double PackingWeight { get; set; }
        public static List<ContainerPackage> Packages { get; set; }

        #endregion

        #region Constructor

        #endregion

        #region Helper Methods

        private double PackagePackingCube;
        public double GetPackingCube()
        {
            PackagePackingCube = (double)PackageCube * PackingQty;
            return PackagePackingCube;
        }

        private double PackagePackingWeight;
        public double GetPackingWeight()
        {
            PackagePackingWeight = (double)PackageWeight * PackingQty;
            return PackagePackingWeight;
        }

        #endregion
    }

Open in new window


Below is my UI:

WinformUI
Future goals:
- Local JSON data storage.
- Migrate to WPF.

Rules:
- Only one shipment can be worked on at any given time.
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Kevin CrossChief Technology Officer
Most Valuable Expert 2011

Commented:
At first glance, I would expect to see composition in the Shipment class. i.e. a Shipment has packages or containers.  Container has packages.  If this List is at the Shipment level, then you do not need a setter for the property package or container count.  The count is the size of the list which you can remove or add packages or containers as you go.  I hope that makes sense.
Most Valuable Expert 2018
Distinguished Expert 2018
Commented:
Hi Snowboy,

Couple of quick observations based on your description.

Your Shipment class should contain a property for the ShipmentContainer. This should be a List if there can be more than one.

Your ShipmentContainer should include a property for the ContainerPackages. Again, should probably be a List<ContainerPackage>

When creating properties of Lists, it generally makes sense to set it to readonly (getter only) and instantiate an empty List in the contructor. This way, the consumer can't overwrite the List - only manage the List.

Properties such as ContainerCount and PackageCount should also be readonly. You don't want to allow the consumer to set these as they;ll be based on the count of the List properties. Your getter for those properties can then just return the count of the relevant property.

A quick example:

public class Shipment 
{
        public string ShipmentID { get; private set; }
        public string ShipmentName { get; private set; }
        public int PackageCount { get { return Container.Count; }
        public IList<ShipmentContainer> Containers { get; private set; }

        public Shipment(string shipmentName)
        {
            ShipmentID = Extension.NewID();
            ShipmentName = shipmentName;
            Containers = new List<ShipmentContainer>();
        }
}

Open in new window

Kevin CrossChief Technology Officer
Most Valuable Expert 2011

Commented:
Chris shows exactly the points I was making if it wasn't clear.  Good post, Chris!
Become a Microsoft Certified Solutions Expert

This course teaches how to install and configure Windows Server 2012 R2.  It is the first step on your path to becoming a Microsoft Certified Solutions Expert (MCSE).

Sn0wb0yLearner

Author

Commented:
Hi Kevin & Chris,

Thank you for your replies.

So Chris, I followed your guidance and added a Container list to my Shipment class and a Package list to my Container class. Also modified those properties for counts based on the lists to read-only as well. Thanks again.

Just wanted to clarify one thing with you before going further though - My UI is datagrid based, in my summary, I made it apparent that the adding and removal of containers and packages are dynamic. Is a binding list more appropriate in my case, keeping in mind I believe that I need to bind my datagrids to my class objects or would a normal list suffice?

Enclosed as follows is my UI code for completeness. I am still new so just putting it all together, learning as I go...

public partial class Main : Form
    {
              
        public Main()
        {
            InitializeComponent();

        }

        private void BtnAddContainerToShipment_Click(object sender, EventArgs e)
        {
            // Add selected container to shipment
            BindingList<ShipmentContainer> ShipmentContainers = new BindingList<ShipmentContainer>();

        }

        private void Main_Load(object sender, EventArgs e)
        {
            // Create new shipment and initialize shipment section
            Shipment Shipment = new Shipment("Danang Imports Vietnam");            
            lblShipmentID.Text = Shipment.ShipmentID;
            txtShipmentName.Text = Shipment.ShipmentName.ToUpper();
            lblShipmentPackages.Text = Shipment.PackageCount.ToString();
            lblShipmentContainers.Text = Shipment.ContainerCount.ToString();
            lblShipmentWeight.Text = Shipment.ShipmentWeight.ToString();
            lblShipmentCube.Text = Shipment.ShipmentCube.ToString();

            // Create a base list of containers for the user to choose from
            List<Container> Containers = new List<Container>();
            Containers.Add(new Container() { Description = "20ft General Purpose", CapacityWeight = 23400, CapacityCube = 30 });
            Containers.Add(new Container() { Description = "40ft General Purpose", CapacityWeight = 24500, CapacityCube = 60 });
            Containers.Add(new Container() { Description = "40ft General Purpose - High Cube", CapacityWeight = 25000, CapacityCube = 70 });

            // Setup datagrid properties
            dgvContainer.MultiSelect = false;
            dgvContainer.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
            dgvContainer.RowHeadersVisible = false;
            
            // Set datagrid rowsource
            dgvContainer.DataSource = Containers;

            // Inititialize datagrid 
            initDGV();            
        }

        private void BtnQuit_Click(object sender, EventArgs e)
        {
            // Close form
            Application.Exit();
        }

        private void DgvContainer_CellClick(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex == dgvContainer.Columns["edit_column"].Index)
            {
                txtContainerDescription.Text = dgvContainer.SelectedCells[1].Value.ToString();
                txtContainerCapacityWeight.Text = dgvContainer.SelectedCells[2].Value.ToString();
                txtContainerCapacityCube.Text = dgvContainer.SelectedCells[3].Value.ToString();
                btnCreateNewContainer.Text = "Save Changes";                
            }
        }

        private void initDGV()
        {
            dgvContainer.Columns[0].HeaderText = "ID";
            dgvContainer.Columns[0].MinimumWidth = 5;
            dgvContainer.Columns[0].Width = 50;
            dgvContainer.Columns[1].HeaderText = "Description";
            dgvContainer.Columns[1].MinimumWidth = 130;
            dgvContainer.Columns[1].Width = 190;
            dgvContainer.Columns[2].HeaderText = "Weight Capacity (MT)";
            dgvContainer.Columns[2].MinimumWidth = 50;
            dgvContainer.Columns[2].Width = 100;
            dgvContainer.Columns[3].HeaderText = "Cube Capacity (M3)";
            dgvContainer.Columns[3].MinimumWidth = 50;
            dgvContainer.Columns[3].Width = 100;

            DataGridViewButtonColumn editButtonColumn = new DataGridViewButtonColumn();
            editButtonColumn.Name = "edit_column";
            editButtonColumn.HeaderText = "Edit";
            editButtonColumn.Text = "Edit";
            editButtonColumn.UseColumnTextForButtonValue = true;
            int columnIndex = 4;
            if (dgvContainer.Columns["edit_column"] == null)
            {
                dgvContainer.Columns.Insert(columnIndex, editButtonColumn);
            }
        }
    }

Open in new window

Most Valuable Expert 2018
Distinguished Expert 2018

Commented:
Hi,

Yeah - a binding list makes it easier to bind your Datagrids to.

However, in your code, you seem to be creating new lists to bind to the Datagrids. What I think you should be doing is binding to the properties of your Object, so for example, in your Form Load event, something like this:

private void Main_Load(object sender, EventArgs e)
{
    var Shipment = new Shipment("Danang Imports Vietnam");

    Shipment.Containers.Add(new Container() { Description = "20ft General Purpose", CapacityWeight = 23400, CapacityCube = 30 });
    Shipment.Containers.Add(new Container() { Description = "40ft General Purpose", CapacityWeight = 24500, CapacityCube = 60 });
    Shipment.Containers.Add(new Container() { Description = "40ft General Purpose - High Cube", CapacityWeight = 25000, CapacityCube = 70 });

    dgvContainer.DataSource = Shipment.Containers;

Open in new window

It's been a while since I've tried Binding with WinForms (WPF is MUCH easier!), so you might need to use a BindingSource. You set the DataSource of that to your object, and then bind the DataGrid to the Binding source.
Sn0wb0yLearner

Author

Commented:
Yeah, the container list you saw in my Main_Load event is there to populate a list of simple containers to add to a shipment. I have a separate class for  container as well which I deliberately left out because I didn't want to overload my post.

I found a post about datagrid object binding lists on SO I will look into that when I get back to it.  I do plan on migrating my project to WPF later on when I am more experienced with C#. It's a lot to cram in WPF as well so I thought it would be best to start with winforms.

To this end, I do have a further question about initialization of my classes and the constructors. I will post up a new one when I get back to it after work tonight.

Thanks again.
Sn0wb0yLearner

Author

Commented:
Thanks guys.
Most Valuable Expert 2018
Distinguished Expert 2018

Commented:
No worries :)

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial