0

I'm trying to access and change the value of a an object within a bindinglist of objects.

I have a bindinglist of Packages that contain these Package objects:

private BindingList<Package> packages;

Package standardPackage = new Package("Standard",7.5m,standardInterior,standardExterior);
Package deluxePackage = new Package("Deluxe",15,deluxeInterior,deluxeExterior);
Package executivePackage = new Package("Executive", 35, executiveInterior, executiveInterior);
Package luxuryPackage = new Package("Luxury",55,luxuryInterior,luxuryExterior);

this.packages.Add(standardPackage);
this.packages.Add(deluxePackage);
this.packages.Add(executivePackage);
this.packages.Add(luxuryPackage);

Each interior element of the package has a fragrance associated with said package. I have created a method with a switch case to identify the fragrance and return the price of the fragrance.

private decimal Update_Price_With_Fragrance()
        {
            decimal price = 0;
            string fragrance = this.fragrancesSource.Current.ToString();

            switch(fragrance)
            {
                case "Hawaiian Mist":
                    price = 2.75m;
                    break;

                case "Baby Powder":
                    price = 1.5m;
                    break;

                case "Country Floral":
                    price = 2.25m;
                    break;

                case "Pina Colada":
                    price = .75m;
                    break;

                case "Vanilla":
                    price = 2.0m;
                    break;
            }
            return price;
        }

I also have an event handler for when a combobox selected index changes to update the price of the package based on which fragrance is selected, but how do I access the price element of each Package within the bindinglist of packages?

  • 2
    Please add the definition of the _Package_ class. And the method _Update_Price_With_Fragrance_ is a method of which class? To update the BindingList inside that method you need to have access to the BindingList. Needs details – Steve Apr 01 '23 at 17:21
  • The following may be of interest: https://stackoverflow.com/a/2244039/10024425 – Tu deschizi eu inchid Apr 02 '23 at 00:11

1 Answers1

0

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:

enter image description here

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:

enter image description here

Add the following to groupBoxExterior GroupBox

  • ListBox (name: listBoxExterior)

It should look similar to the following:

enter image description here

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:

enter image description here


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 enter image description here
  • Double-click Load

ComboBox (name: comboBoxPackage) - subscribe to the ComboBox.SelectedIndexChanged event

  • In the Properties Window, use the drop-down to select comboBoxPackage
  • Click enter image description here
  • Double-click SelectedIndexChanged

ListBox (name: listBoxInterior) - subscribe to the ListBox.SelectedIndexChanged event

  • In the Properties Window, use the drop-down to select listBoxInterior
  • Click enter image description here
  • Double-click SelectedIndexChanged

ComboBox (name: comboBoxFragrance) - subscribe to the ComboBox.SelectedIndexChanged event

  • In the Properties Window, use the drop-down to select comboBoxPackage
  • Click enter image description here
  • Double-click SelectedIndexChanged

ListBox (name: listBoxExterior) - subscribe to the ListBox.SelectedIndexChanged event

  • In the Properties Window, use the drop-down to select listBoxInterior
  • Click enter image description here
  • 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:

enter image description here


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:

Tu deschizi eu inchid
  • 4,117
  • 3
  • 13
  • 24
  • You'll need to add code to calculate the values for `PST`, `GST`, and `Total`. Then set the values in the appropriate TextBox. You'll notice in the demo, that when `Custom` is selected as the package, if one selects one of the `Shampoo` services, then `Vacuum` is selected as well because I assume that one would vacuum prior to shampooing either the carpets or upholstery. – Tu deschizi eu inchid Apr 02 '23 at 02:20