The following shows a way one might use ComboBox
and ListBox
together for your CarWash project described here.
Create a new Windows Forms App (.NET Framework)
(name: CarWashDL)
We'll create a number of classes that contain properties that we'll use to hold the data that we're going to use.
Create a class (name: CarWashFragrance.cs)
public class CarWashFragrance
{
public string Name { get; set; }
public string Description { get; set; }
public decimal AlaCartePrice { get; set; }
public string Classification { get; set; }
public decimal UpCharge { get; set; }
public string DisplayName
{
get { return Name; }
}
public string DisplayNameClassificationOrUpcharge
{
//this property is used to change how the name appears in the ComboBox
get
{
if (Name == "None")
return Name;
else if (UpCharge == 0)
return $"{Name} ({Classification})";
else
return $"{Name} (+{UpCharge})";
}
}
public string DisplayNameCustom
{
//this property is used to change how the name appears in the ComboBox
get
{
if (Name == "None")
return Name;
else
return $"{Name} (+{AlaCartePrice})";
}
}
}
Create a class (name: CarWashService.cs)
public class CarWashService
{
public string Name { get; set; }
public string PackageName { get; set; }
public string Description { get; set; }
public decimal AlaCartePrice { get; set; }
public int Level { get; set; }
public bool AvailableInHigherLevel { get; set; } = true;
public string DisplayName
{
get { return $"{Name}";}
}
public string DisplayNameCustom
{
//this property is used to change how the name appears in the ListBox
get { return $"{Name} (+{AlaCartePrice})"; }
}
public override string ToString()
{
return $"Name: '{Name}' Description: '{Description}' PackageName: '{PackageName}' AlaCartePrice: {AlaCartePrice}";
}
}
Create a class (name: CarWashInterior.cs)
public class CarWashInterior
{
public CarWashFragrance Fragrance { get; set; } = null;
public List<CarWashService> InteriorServices { get; set; } = new List<CarWashService>();
}
Create a class (name: CarWashPackage.cs)
public class CarWashPackage
{
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public bool IncludesFragranceService { get; set; } = false;
public override string ToString()
{
return $"Name: '{Name}' IncludesFragranceService: {IncludesFragranceService}' Price: '{Price}'";
}
}
The following classes aren't really necessary - one can create methods in your Form
instead. I've used them to show an alternative. The data below is stored in a Property, but can be accessed via the Property or Method.
Create a class (name: DefaultCarWashFragrance.cs)
Add using directive
using System.Collections.Generic;
DefaultCarWashFragrance:
public static class DefaultCarWashFragrances
{
//create new instance
public static List<CarWashFragrance> Fragrances { get; private set; } = new List<CarWashFragrance>();
static DefaultCarWashFragrances()
{
//add fragrance information
Fragrances.Add(new CarWashFragrance() { Name = "None", AlaCartePrice = 0, Classification = "None" });
Fragrances.Add(new CarWashFragrance() { Name = "Cinnamon", AlaCartePrice = 2.99m, Classification = "Executive"});
Fragrances.Add(new CarWashFragrance() { Name = "Coconut", AlaCartePrice = 2.99m, Classification = "Executive" });
Fragrances.Add(new CarWashFragrance() { Name = "Ocean Breeze", AlaCartePrice = 3.99m, Classification = "Luxury", UpCharge = 1 });
Fragrances.Add(new CarWashFragrance() { Name = "Pepermint", AlaCartePrice = 2.99m, Classification = "Executive" });
Fragrances.Add(new CarWashFragrance() { Name = "Pine", AlaCartePrice = 2.99m, Classification = "Executive"});
Fragrances.Add(new CarWashFragrance() { Name = "Spermint", AlaCartePrice = 2.99m, Classification = "Executive"});
Fragrances.Add(new CarWashFragrance() { Name = "Strawberry", AlaCartePrice = 2.99m, Classification = "Executive" });
Fragrances.Add(new CarWashFragrance() { Name = "Vanilla", AlaCartePrice = 2.99m, Classification = "Executive"});
}
public static List<CarWashFragrance> GetFragrances()
{
return Fragrances;
}
}
Create a class (name: DefaultCarWashPackages.cs)
Add using directive
using System.Collections.Generic;
DefaultCarWashPackages:
public static class DefaultCarWashPackages
{
public static List<CarWashPackage> Packages { get; private set; } = new List<CarWashPackage> ();
static DefaultCarWashPackages()
{
Packages.Add(new CarWashPackage() { Name = "Standard", Description = "Standard", IncludesFragranceService = false, Price = 15.99m });
Packages.Add(new CarWashPackage() { Name = "Deluxe", Description = "Deluxe", IncludesFragranceService = false, Price = 25.99m });
Packages.Add(new CarWashPackage() { Name = "Executive", Description = "Executive", IncludesFragranceService = true, Price = 32.99m });
Packages.Add(new CarWashPackage() { Name = "Luxury", Description = "Luxury", IncludesFragranceService = true, Price = 49.99m });
Packages.Add(new CarWashPackage() { Name = "Custom", Description = "Custom", IncludesFragranceService = true, Price = 0 });
}
public static List<CarWashService> GetCarWashServicesExterior(string packageName)
{
//create new instance
List<CarWashService> availableCarWashServices = new List<CarWashService>();
availableCarWashServices.Add(new CarWashService() { PackageName = "Standard", Name = "Wash", AlaCartePrice = 9.99m, Level = 1, AvailableInHigherLevel = true });
availableCarWashServices.Add(new CarWashService() { PackageName = "Standard", Name = "Air Dry", AlaCartePrice = 0, Level = 1, AvailableInHigherLevel = false });
availableCarWashServices.Add(new CarWashService() { PackageName = "Deluxe", Name = "Blow Dry", AlaCartePrice = 2.99m, Level = 2, AvailableInHigherLevel = false });
availableCarWashServices.Add(new CarWashService() { PackageName = "Executive", Name = "Hand Dry", AlaCartePrice = 15.99m, Level = 3, AvailableInHigherLevel = true });
availableCarWashServices.Add(new CarWashService() { PackageName = "Executive", Name = "Hand Wax", AlaCartePrice = 20.99m, Level = 3, AvailableInHigherLevel = true });
availableCarWashServices.Add(new CarWashService() { PackageName = "Executive", Name = "Wheel Polish", AlaCartePrice = 9.99m, Level = 3, AvailableInHigherLevel = true });
availableCarWashServices.Add(new CarWashService() { PackageName = "Luxury", Name = "Detail Engine Compartment", AlaCartePrice = 19.99m, Level = 4, AvailableInHigherLevel = true });
int selectedLevel = 0;
if (packageName == "Custom")
{
selectedLevel = Int32.MaxValue;
}
else
{
//get level associated with selected package name
selectedLevel = availableCarWashServices.Where(x => x.PackageName == packageName).Select(x => x.Level).FirstOrDefault();
}
//return all services for selected package
//all services for lower packages are included if 'AvailableInHigherLevel' = true
return availableCarWashServices.Where(x => x.Level == selectedLevel || (x.Level < selectedLevel && x.AvailableInHigherLevel)).ToList();
}
public static CarWashInterior GetCarWashInterior(string packageName)
{
List<CarWashService> availableCarWashServices = new List<CarWashService>();
//Standard
availableCarWashServices.Add(new CarWashService() { PackageName = "Standard", Name = "Vacuum", AlaCartePrice = 9.99m, Level = 1, AvailableInHigherLevel = true });
//Deluxe - includes all lower-level services where 'AvailableInHigherLevel = true'
availableCarWashServices.Add(new CarWashService() { PackageName = "Deluxe", Name = "Shampoo Carpet", AlaCartePrice = 6.99m, Level = 2, AvailableInHigherLevel = true });
//Executive - includes all lower-level services where 'AvailableInHigherLevel = true'
availableCarWashServices.Add(new CarWashService() { PackageName = "Executive", Name = "Shampoo Upholstery", AlaCartePrice = 9.99m, Level = 3, AvailableInHigherLevel = true });
//Luxury - includes all lower-level services where 'AvailableInHigherLevel = true'
availableCarWashServices.Add(new CarWashService() { PackageName = "Luxury", Name = "Stain Protector", AlaCartePrice = 15.99m, Level = 4, AvailableInHigherLevel = true });
int selectedLevel = 0;
if (packageName == "Custom")
{
selectedLevel = Int32.MaxValue;
}
else
{
//get level associated with selected package name
selectedLevel = availableCarWashServices.Where(x => x.PackageName == packageName).Select(x => x.Level).FirstOrDefault();
}
//create new instance
CarWashInterior carWashInterior = new CarWashInterior();
//get all services for selected package
//all services for lower packages are included if 'AvailableInHigherLevel' = true
carWashInterior.InteriorServices = availableCarWashServices.Where(x => x.Level == selectedLevel || (x.Level < selectedLevel && x.AvailableInHigherLevel)).ToList<CarWashService>();
return carWashInterior;
}
public static List<CarWashPackage> GetCarWashPackages()
{
return Packages;
}
}
Now that all of the classes have been created, we'll work with the Form
(name: Form1.cs).
Add the following to the form
- Label (name: labelPackage; Text: "Package")
- ComboBox (name: comboBoxPackage; DropDownStyle: DropDownList)
- GroupBox (name: groupBoxInterior; Text: "Interior")
- GroupBox (name: groupBoxExterior; Text: "Exterior")
It should look similar to the following:

Add the following to groupBoxInterior
GroupBox
- ListBox (name: listBoxInterior)
- Label (name: labelFragrance; Text: "Fragrance")
- ComboBox (name: comboBoxFragrance; DropDownStyle: DropDownList)
It should look similar to the following:

Add the following to groupBoxExterior
GroupBox
- ListBox (name: listBoxExterior)
It should look similar to the following:

Add the following to the form (lower-right):
- TextBox (name: textBoxSubtotal)
- TextBox (name: textBoxPST)
- TextBox (name: textBoxGST)
- TextBox (name: textBoxTotal)
It should look similar to the following:

Open Solution Explorer:
- In VS menu, click View
- Select Solution Explorer
Open Properties Window:
- In VS menu, click View
- Select Properties Window
Now we'll subscribe to some events.
Form1 - subscribe to the Form.Load event
- In Solution Explorer, right-click Form1.cs and select View Designer
- In the Properties Window, click

- Double-click Load
ComboBox (name: comboBoxPackage) - subscribe to the ComboBox.SelectedIndexChanged event
- In the Properties Window, use the drop-down to select
comboBoxPackage
- Click

- Double-click SelectedIndexChanged
ListBox (name: listBoxInterior) - subscribe to the ListBox.SelectedIndexChanged event
- In the Properties Window, use the drop-down to select
listBoxInterior
- Click

- Double-click SelectedIndexChanged
ComboBox (name: comboBoxFragrance) - subscribe to the ComboBox.SelectedIndexChanged event
- In the Properties Window, use the drop-down to select
comboBoxPackage
- Click

- Double-click SelectedIndexChanged
ListBox (name: listBoxExterior) - subscribe to the ListBox.SelectedIndexChanged event
- In the Properties Window, use the drop-down to select
listBoxInterior
- Click

- Double-click SelectedIndexChanged
In the code below, you'll notice that I've used a BindingList which is set as the DataSource for each ListBox.
Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CarWashDL
{
public partial class Form1 : Form
{
//create new instance
private List<CarWashPackage> _carWashPackages = null;
private BindingList<CarWashService> _carWashExteriorServices = null;
private CarWashInterior _carWashInterior = null;
private BindingList<CarWashFragrance> _carWashFragrances = null;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//ToDo: if available fragrances are saved to file, then get available fragrances from file instead
_carWashFragrances = new BindingList<CarWashFragrance>(DefaultCarWashFragrances.Fragrances);
//set properties
comboBoxFragrance.DataSource = _carWashFragrances;
comboBoxFragrance.DisplayMember = "DisplayName";
comboBoxFragrance.ValueMember = "AlaCartePrice";
//ToDo: if available CarWashPackages are saved to file, then get available fragrances from file instead
_carWashPackages = DefaultCarWashPackages.Packages;
//set properties
comboBoxPackage.DropDownStyle = ComboBoxStyle.DropDownList;
//set value
comboBoxPackage.DataSource = _carWashPackages;
comboBoxPackage.DisplayMember = "Name";
comboBoxPackage.ValueMember = "Name";
//set properties
listBoxExterior.DisplayMember = "Name";
listBoxExterior.ValueMember = "AlaCartePrice";
listBoxExterior.SelectionMode = SelectionMode.None;
listBoxInterior.DisplayMember = "Name";
listBoxInterior.ValueMember = "AlaCartePrice";
listBoxInterior.SelectionMode = SelectionMode.None;
textBoxGST.TextAlign = HorizontalAlignment.Right;
textBoxPST.TextAlign = HorizontalAlignment.Right;
textBoxSubtotal.TextAlign = HorizontalAlignment.Right;
textBoxTotal.TextAlign = HorizontalAlignment.Right;
}
private void Calculate()
{
decimal subtotalExterior = 0;
decimal subtotalInterior = 0;
decimal subtotal = 0;
if (_carWashInterior == null || _carWashExteriorServices == null)
return;
//create reference
string packageName = _carWashPackages[comboBoxPackage.SelectedIndex].Name;
bool includesFragranceService = _carWashPackages[comboBoxPackage.SelectedIndex].IncludesFragranceService;
if (packageName == "Custom")
{
//Custom package is selected
//Exterior - get price for each selected service
foreach (CarWashService cwService in listBoxExterior.SelectedItems)
{
//add price for each selected service
subtotalExterior += cwService.AlaCartePrice;
}
//Interior - get price for each selected service
for (int i = 0; i < listBoxInterior.SelectedItems.Count; i++)
{
//create reference
CarWashService cwService = (CarWashService)listBoxInterior.SelectedItems[i];
//add price for each selected service
subtotalInterior += cwService.AlaCartePrice;
}
//add
subtotal = subtotalExterior + subtotalInterior;
}
else
{
//all packages except 'Custom'
//get price for package
subtotal = _carWashPackages[comboBoxPackage.SelectedIndex].Price;
}
//'Luxury' package includes all available fragrances,
//so if 'Luxury' package is selected, skip this
if (includesFragranceService && packageName != "Luxury")
{
decimal fragrancePrice = 0;
//convert to decimal
//'SelectedValue' is determined by the name set in the ComboBox's 'ValueMember' property
//(ie: whether its set to'AlaCartPrice' or 'UpCharge')
//If one doesn't use 'SelectedValue', then the value of the ComboBox's
//'ValueMember' property is unimportant.
Decimal.TryParse(comboBoxFragrance.SelectedValue.ToString(), out fragrancePrice);
//add
subtotal += fragrancePrice;
}
//set value
textBoxSubtotal.Text = Decimal.Round(subtotal, 2, MidpointRounding.AwayFromZero).ToString();
//ToDo: calculate other values and set each value in appropriate TextBox
}
private void comboBoxPackage_SelectedIndexChanged(object sender, EventArgs e)
{
ComboBox cBox = (ComboBox)sender;
//create reference
string packageName = _carWashPackages[cBox.SelectedIndex].Name;
bool includesFragranceService = _carWashPackages[cBox.SelectedIndex].IncludesFragranceService;
//get exterior services for package
_carWashExteriorServices = new BindingList<CarWashService>(DefaultCarWashPackages.GetCarWashServicesExterior(packageName));
//set new instance as DataSource
listBoxExterior.DataSource = _carWashExteriorServices;
//get interior services for package
_carWashInterior = DefaultCarWashPackages.GetCarWashInterior(packageName);
//set new instance of BindingList as DataSource
listBoxInterior.DataSource = _carWashInterior.InteriorServices;
if (packageName != "Custom")
{
//a non-custom package is selected (ie: Standard, Deluxed, Executive, Luxury, ...)
//Exterior - set properties
//'DisplayMember' affects how the names of the services are displayed in the ListBox
listBoxExterior.DisplayMember = "Name";
listBoxExterior.SelectionMode = SelectionMode.None;
//Interior - set properties
//'DisplayMember' affects how the names of the services are displayed in the ListBox
listBoxInterior.DisplayMember = "Name";
listBoxInterior.SelectionMode = SelectionMode.None;
//change 'ValueMember' property value
//the value of the ComboBox's 'SelectedValue' property
//is determined by what 'ValueMember' is set to.
//if 'ValueMember' = "AlaCarePrice", then 'SelectedValue'
//will return the value for 'AlaCartePrice.
//if 'ValueMember' = "UpCharge", then 'SelectedValue'
//will return the value for 'UpCharge'
if (packageName == "Luxury")
{
comboBoxFragrance.DisplayMember = "DisplayName";
comboBoxFragrance.ValueMember = "DisplayName";
}
else
{
comboBoxFragrance.DisplayMember = "DisplayNameClassificationOrUpCharge";
comboBoxFragrance.ValueMember = "UpCharge";
}
//the selected package name determines how the fragrance names are displayed
//update the display names in the ComboBox
comboBoxFragrance.Update();
}
else
{
//Custom package is selected
//set properties
listBoxExterior.SelectionMode = SelectionMode.MultiSimple;
listBoxInterior.SelectionMode = SelectionMode.MultiSimple;
//'DisplayMember' affects how the names of the services are displayed in the ListBox
listBoxExterior.DisplayMember = "DisplayNameCustom";
listBoxInterior.DisplayMember = "DisplayNameCustom";
//change 'DisplayMember' and 'ValueMember' property values
//the value of the ComboBox's 'SelectedValue' property
//is determined by what 'ValueMember' is set to.
//if 'ValueMember' = "AlaCarePrice", then 'SelectedValue'
//will return the value for 'AlaCartePrice.
//if 'ValueMember' = "UpCharge", then 'SelectedValue'
//will return the value for 'UpCharge'
comboBoxFragrance.DisplayMember = "DisplayNameCustom";
comboBoxFragrance.ValueMember = "AlaCartePrice";
//the selected package name determines how the fragrance names are displayed
//update the display names in the ComboBox
comboBoxFragrance.Update();
}
//determine if the package contains fragrance service
if (!includesFragranceService)
{
//set to 'None' which is index '0'
comboBoxFragrance.SelectedIndex = 0;
//disable
comboBoxFragrance.Enabled = false;
}
else
{
//enable
comboBoxFragrance.Enabled = true;
}
Calculate();
}
private void listBoxInterior_SelectedIndexChanged(object sender, EventArgs e)
{
bool shampooSelected = false;
//determine if one of the 'Shampoo' services have been selected
foreach (CarWashService interiorService in listBoxInterior.SelectedItems)
{
if (interiorService.Name.StartsWith("Shampoo"))
{
//set value
shampooSelected = true;
break;
}
}
if (shampooSelected)
{
//unsubscribe from 'SelectedIndexChanged' event to prevent recursive calling of it
//we'll re-subscribe to it below
//if we don't unsubscribe from the 'SelectedIndexChanged' event prior to selecting
//another item, this method will be recursively called until a stackoverflow exception occurs
listBoxInterior.SelectedIndexChanged -= listBoxInterior_SelectedIndexChanged;
//all shampoo services must include vacuum
//select 'Vacuum' service
for (int i = 0; i < _carWashInterior.InteriorServices.Count; i++)
{
if (_carWashInterior.InteriorServices[i].Name == "Vacuum")
{
listBoxInterior.SetSelected(i, true);
break;
}
}
//re-subscribe to event
listBoxInterior.SelectedIndexChanged += listBoxInterior_SelectedIndexChanged;
}
Calculate();
}
private void listBoxExterior_SelectedIndexChanged(object sender, EventArgs e)
{
Calculate();
}
private void comboBoxFragrance_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBoxPackage.SelectedIndex >= 0 && comboBoxFragrance.SelectedIndex >= 0)
{
//set value
_carWashInterior.Fragrance = (CarWashFragrance)comboBoxFragrance.SelectedItem;
Calculate();
}
}
}
}
Here's a demo:

Update:
To fix the flickering of the ListBox that occurs after selecting a value in one of the ListBoxes and then clicking comboBoxFragrance, add the following code to the form (Form1.cs):
Note: The following code is from here.
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED
return cp;
}
}
Additional Resources: