Link to home
Start Free TrialLog in
Avatar of rwheeler23
rwheeler23Flag for United States of America

asked on

C# populate multiple values from combo box?

I am trying to populate three text fields:

txtID, txtCodes and TxtSuffix

I have created a class and the combo box.

How do I finish this so all three text fields will populate?

==============================================================

private class Harmonization
{
    public string Id { get; set; }
    public string Code { get; set; }
    public string Suffix { get; set; }
}

==============================================================

var dtharmon = new DataTable();
    _cmdText = "SELECT [ID]" +
                         ",[Codes]" +
                         ",[Suffix] " +
                         "FROM[MERCH].[dbo].[AAA_Harmonization_Codes] " +
                         "ORDER BY [Suffix]";

    using (var daharmon = new SqlDataAdapter(_cmdText, _gpconn))
    {
            daharmon.Fill(dtharmon);
            cboHarmCode.DataSource = dtharmon;
            cboHarmCode.DisplayMember = "Suffix";
            cboHarmCode.ValueMember   = "Suffix";
            cboHarmCode.SelectedIndex = 0;
      }

Avatar of gr8gonzo
gr8gonzo
Flag of United States of America image

There should be a property called SelectedItem on the combo box. It is intended to be able to hold many different types of objects, so it's typed as "object" but in your example, you're binding to a DataTable, so when you select an item in the ComboBox, then the SelectedItem will point to the corresponding DataRow object.


So when you need to fill your Harmonization object, just cast SelectedItem back to the DataRow type, and then access the fields in that row:


var selectedDataRow = (DataRow)cboHarmCode.SelectedItem;
var x = new Harmonization()
{
  Id = (string)selectedDataRow["ID"],
  Code = (string)selectedDataRow["Codes"],
  Suffix = (string)selectedDataRow["Suffix"]
};

Open in new window

Avatar of rwheeler23

ASKER

I tried using this but it does not work. It errors on line
 var selectedDataRow = (DataRow)cboHarmCode.SelectedItem;

"Unable to obtain harmonization code?

========================================================================================================
        private void cboHarmCode_SelectedIndexChanged(object sender, EventArgs e)
        {
            var selectedDataRow = (DataRow)cboHarmCode.SelectedItem;
            var x = new Harmonization()
            {
                Id = (string)selectedDataRow["ID"],
                Code = (string)selectedDataRow["Codes"],
                Suffix = (string)selectedDataRow["Suffix"]
            };

            txtID.Text = x.Id;
            txtCode.Text = x.Code;
            txtSuffix.Text = x.Suffix;
       

Is "Unable to obtain harmonization code" the error you're getting?


If so, that is a custom error message, so can you provide the base error / exception?

Here is the complete code and error message.

User generated imagefrmHarmonization.zip
I even tried including the column names and capitalizing them.

                    if (_gpconn.State == ConnectionState.Open)
                    {
                        var dtharmon = new DataTable();
                        _cmdText = "SELECT [ID],[CODES],[SUFFIX] " +
                                   "FROM [dbo].[AAA_Harmonization_Codes] " +
                                   "ORDER BY [SUFFIX]";

                        using (var daharmon = new SqlDataAdapter(_cmdText, _gpconn))
                        {
                            daharmon.Fill(dtharmon);
                            cboHarmCode.DataSource = dtharmon;
                            cboHarmCode.DisplayMember = "SUFFIX";
                            cboHarmCode.ValueMember = "SUFFIX";
                            cboHarmCode.SelectedIndex = 0;
                        }
                    }
don't set the displayMember and the valueMember.

Sorry I made a mistake.


It should be (DataRowView), not (DataRow). There is an intermediate view object there.


var selectedDataRow = (DataRowView)cboHarmCode.SelectedItem; 


@Kyle - there is no problem setting the DisplayMember or ValueMember (although the ValueMember isn't really used here).

Yeah, looked at it too quickly.  Usually take the Display/Value path myself.  

Time for bed, lol.

Also, while there's no particular problem with using a DataTable as the data source, it can -sometimes- give you a little more flexibility to use a List<> of an object like List<Harmonization> as the data source.


For what it's worth, data binding would work well here.


The way I would approach it overall:


First, add a method to convert the DataTable into a List<Harmonization>:


        public class Harmonization
        {
            public string Id { get; set; }
            public string Code { get; set; }
            public string Suffix { get; set; }


            public static List<Harmonization> FromDataTable(DataTable dt)
            {
                var list = new List<Harmonization>();
                foreach(DataRow dr in dt.Rows)
                {
                    list.Add(new Harmonization()
                    {
                        Id = (string)dr["ID"],
                        Code = (string)dr["Codes"],
                        Suffix = (string)dr["Suffix"]
                    });
                }
                return list;
            }
        }

Open in new window


Next, add a property to your class to hold the selected Harmonization object.


public Harmonization selectedHarmonization { get; set; }

Open in new window


Next, call this method to convert the DataTable to the List<Harmonization> and use that List as your data source, and also add the binding so it updates selectedHarmonization automatically.


                        using (var daharmon = new SqlDataAdapter(_cmdText, _gpconn))
                        {
                            daharmon.Fill(dtharmon);                             var harmonizations = Harmonization.FromDataTable(dtharmon);
                            cboHarmCode.DataSource = harmonizations;
                            cboHarmCode.DisplayMember = "Suffix";
                            cboHarmCode.DataBindings.Add(new Binding("SelectedItem", this, "selectedHarmonization"));                             cboHarmCode.SelectedIndexChanged += (object sender, EventArgs e) => { cboHarmCode.DataBindings["SelectedItem"].WriteValue(); };
                            cboHarmCode.SelectedIndex = 0;
                        }

Open in new window


Now from here, you can just add the 3 data bindings to your text fields:

txtID.DataBindings.Add(new Binding("Text", cboHarmCode, "SelectedItem.Id"));
txtCode.DataBindings.Add(new Binding("Text", cboHarmCode, "SelectedItem.Code"));
txtSuffix.DataBindings.Add(new Binding("Text", cboHarmCode, "SelectedItem.Suffix"));

Open in new window


With those changes, as soon as you change the combo box selection, it should:

1. Update the 3 textbox values automatically.

2. Update the "selectedHarmonization" object, in case you want to reference it later.

I must have not positioned things properly. Now I get this message.
User generated image
I have attached the updated code.
frmHarmonization.zip
Changing DataRow to DataRowView results in this error message.

User generated image

Your error message screenshots are coming through really small and can't be read. Can you share the text?

If I attach the screenshot is is any better?
Another error
I complains about this line.

                            var harmonizations = Harmonization.FromDataTable(dtharmon);
Error-Harmonization3.png
Running debug appears to show that harmonizations is null. See attachment.
Debug.png

1. Move the "selectedHarmonization" property into your frmHarmonization class. I usually put properties above the constructor:

User generated image


2. Move the 3 textbox bindings into LoadComboBox so they come right after this line:


cboHarmCode.SelectedIndex = 0;

Open in new window

User generated image


On a side note, Data Binding is 2-way by default. This means that if someone selected Suffix "abc" and the 3 textboxes populate and then someone changes a value in that textbox, then the new value from the texbox will update the Harmonization object in memory.


This is often a desired effect with binding because it allows you to essentially set up a data entry form that can update the backend without having to write tons of repetitive "ok now go and update this field again" code. But if you just want the data binding to be one-way so that the combobox changes the textboxes but never the other way around, you can do it in one of three ways:


1. If the users should never change the textboxes at all, then just make the textboxes read-only.


2. If the users should be able to -temporarily- change the textboxes but the new value doesn't go back to the original Harmonization object, then just change the Harmonization object properties to { get; private set; } like this:


    public class Harmonization

    {

        public string Id { get; private set; }

        public string Code { get; private set; }

        public string Suffix { get; private set; }


3. Or you can add an extra statement to each of the textbox data bindings to specify that the data source (the original Harmonization object that was selected in the combo box) should never be updated by the textboxes:


            txtID.DataBindings.Add(new Binding("Text", cboHarmCode, "SelectedItem.Id")

            {

                DataSourceUpdateMode = DataSourceUpdateMode.Never

            });

            txtCode.DataBindings.Add(new Binding("Text", cboHarmCode, "SelectedItem.Code")

            {

                DataSourceUpdateMode = DataSourceUpdateMode.Never

            });

            txtSuffix.DataBindings.Add(new Binding("Text", cboHarmCode, "SelectedItem.Suffix")

            {

                DataSourceUpdateMode = DataSourceUpdateMode.Never

            });


Usually #1 is easier AND it's a better user experience unless you WANT the user to be able to temporarily modify the textboxes.

OK, the error message has stayed the same on the same line. Here is recap of what I am trying to do.
In the ERP solution there is a screen for items. I am grabbing the item number and then opening a window that will allow the user to assign these other values and then commit these values to a table in SQL. This error is occurring on a sub form that would allow the user to change values on any of these three values. Changing these values would be rare but there is also a possibility that may want to add a new harmonization code.

I have attached the latest code.
frmHarmonization.zip

Can you adjust the exception so it shows the actual error message? Right now you have:


                    string eMsg = "frmHarmonization - 001 - Error : Unable to obtain harmonization codes";

                    eMsg += "\n" + ex.StackTrace;


But that doesn't tell you what the error is. You need to include ex.Message in there somewhere. 


Also, are the 3 fields in the database all strings? I had assumed so, since your properties were strings. But if your ID was an integer, for example, that would result in a casting error.


You'd have to adjust the property type and the cast. For example, if the Id property WAS an integer, you'd change:

public string Id { get; set; }

to:

public int Id { get; set; }


and you'd change the cast from:

Id = (string)dr["ID"],

to:

Id = (int)dr["ID"],

All three fields are strings. I have attached the expanded error message.


Error-Harmonization4.png
ASKER CERTIFIED SOLUTION
Avatar of gr8gonzo
gr8gonzo
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial