Windows Develop Bookmark and Share   
 index > Windows Forms General > ComboBox Question
 

ComboBox Question

How do I populate a C# Forms ComboBox with one column (VendName) from a Vendors table, and then use that ComboBox to insert another column (VendID) from the same row of the Vendors table into the VendID column (foreign key) of a Products table?

My database has two simple tables -- Vendors and Products
The Vendors table has two columns -- VendID and VendName
The Products table has three columns -- ProdID, ProdName and VendID

Below is first, the code I successfully use to populate the ComboBox cmbxVendName, andsecond, the code I use (unseccessfully) to insert VendID into the Products table.

First -- code to fill the combobox

void ConnectVendorsCmbxFill() {
//connection string
string connString = @"server = .\sqlexpress; integrated security = true; database = vendprod; ";
//query
string qry = @"select VendID, VendName from Vendors order by VendName";
//create connection
SqlConnection conn = new SqlConnection(connString);
conn.Open();

//create datatable
try {
//create data adapter
SqlDataAdapter da = new SqlDataAdapter(qry, conn);
//create data table
DataTable dt = new DataTable();
da.Fill(dt);

foreach (DataRow row in dt.Rows) {
cmbxVendName.DisplayMember =
"VendName";
cmbxVendName.ValueMember =
"VendID";
cmbxVendName.Items.AddRange(
new object[] { row["VendName"] });
}
cmbxVendName.Text =
"Choose Vendor";
}
catch (Exception ex){MessageBox.Show("Error: " + ex);}
finally {

//close connection
conn.Close();
}
}

Second -- code to insert VendID into Products table

void

ConnectProductInsert() {
//create connection string
string connString = @"server = .\sqlexpress; integrated security = true; database = vendprod;";
//create select qry
string qry = @"select * from products";
//create insert SQL
string ins = @"insert products (ProdName, VendID) values (@ProdName, @VendID)";
//create connection
SqlConnection conn = new SqlConnection(connString);

try {
//create data adapter
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand =
new SqlCommand(qry, conn);

//create and fill data set
DataSet ds = new DataSet();
da.Fill(ds,
"Products");

//get table reference
DataTable dt = ds.Tables["Products"];

DataRow newRow = dt.NewRow();
newRow[
"ProdName"] = txtProdName.Text;
newRow[
"VendID"] = cmbxVendName.ValueMember;
dt.Rows.Add(newRow);

//create insert command
SqlCommand cmd = new SqlCommand(ins, conn);
//map parameters
cmd.Parameters.Add("@ProdName", SqlDbType.NVarChar, 50, "ProdName");
cmd.Parameters.Add(
"@VendID", SqlDbType.Int, 25, "VendID");
da.InsertCommand = cmd;
da.Update(ds,
"Products");
}
catch (Exception ex) {MessageBox.Show("Error: " + ex);}

finally{
//close connection
conn.Close();
}
}

Any help will be greatly appreciated. Thanks
Bill

  • Moved byTaylorMichaelLMVPFriday, September 04, 2009 1:56 PMWinforms related (From:Visual C# General)
  •  
byschaupp  Wednesday, September 02, 2009 5:50 PM

Bill,

What you really want to do is use DataBinding of the two DataTables, then use the DataAdapter stuff to put it back in the database. Something like this:

// After filling the DataTable with your DataAdapter,
// Here's how to "fill" the ComboBox
cmbxVendName.ValueMember = "VendID";
cmbxVendName.DisplayMember = "VendName";
cmbxVendName.DataSource = dt;

// Now databind to your Products table
cmbxVendName.DataBindings.Add("SelectedValue", ds.Tables["Products"], "VendID");


~~Bonnie Berent [C# MVP]
BonnieB  Friday, September 04, 2009 12:47 AM

Second -- code to insert VendID into Products table

void

ConnectProductInsert() {
//create connection string
string connString = @"server = .\sqlexpress; integrated security = true; database = vendprod;" ;
//create select qry
string qry = @"select * from products" ;
//create insert SQL
string ins = @"insert products (ProdName, VendID) values (@ProdName, @VendID)" ;
//create connection
SqlConnection conn = new SqlConnection (connString);

try {
//create data adapter
SqlDataAdapter da = new SqlDataAdapter ();
da.SelectCommand =
new SqlCommand (qry, conn);

//create and fill data set
DataSet ds = new DataSet ();
da.Fill(ds,
"Products" );

//get table reference
DataTable dt = ds.Tables[ "Products" ];

DataRow newRow = dt.NewRow();
newRow[
"ProdName" ] = txtProdName.Text;
newRow[
"VendID" ] = cmbxVendName.ValueMember;
dt.Rows.Add(newRow);

//create insert command
SqlCommand cmd = new SqlCommand (ins, conn);
//map parameters
cmd.Parameters.Add( "@ProdName" , SqlDbType .NVarChar, 50, "ProdName" );
cmd.Parameters.Add(
"@VendID" , SqlDbType .Int, 25, "VendID" );
da.InsertCommand = cmd;
da.Update(ds,
"Products" );
}
catch ( Exception ex) { MessageBox .Show( "Error: " + ex);}

finally {
//close connection
conn.Close();
}
}

Any help will be greatly appreciated. Thanks
Bill

I think you have problem to get the selected Vendor Id from the comboBox if yes then code is given below,

DataRowView dr;
dr = (DataRowView)cmbxVendName .SelectedItem;
string strVendId= Convert.ToString(dr["VendId"]);

AUmidh  Wednesday, September 02, 2009 11:37 PM
AUmidh,

Thank you for your response. Your suggestion does not solve the problem, but the Error message is a lot simpler.

"Error: System.InvalidCastException: Unable to cast object of type 'System.String' to type 'System.Data.DataRowView'.
at ... Form1.cs:Line 193

The offending line 193 is

dr = (DataRowView)cmbxVendName.SelectedItem;

Looks like some progress. Thanks again.

Bill
byschaupp  Thursday, September 03, 2009 1:11 PM

Bill,

What you really want to do is use DataBinding of the two DataTables, then use the DataAdapter stuff to put it back in the database. Something like this:

// After filling the DataTable with your DataAdapter,
// Here's how to "fill" the ComboBox
cmbxVendName.ValueMember = "VendID";
cmbxVendName.DisplayMember = "VendName";
cmbxVendName.DataSource = dt;

// Now databind to your Products table
cmbxVendName.DataBindings.Add("SelectedValue", ds.Tables["Products"], "VendID");


~~Bonnie Berent [C# MVP]
BonnieB  Friday, September 04, 2009 12:47 AM
Thanks Bonnie,

We went away for a couple of days. I'll try it out in the next day or so and mark it as answer if it works. Looks like a good answer. I need to learn a lot more about databindings.

Thanks again.

Bill
byschaupp  Saturday, September 05, 2009 7:14 PM

Good enough. Let me know if you still have problems andhave more questions.


~~Bonnie Berent [C# MVP]
BonnieB  Saturday, September 05, 2009 8:00 PM
@byschaupp
Sorry i was away. if you get solution to your problem?
AUmidh  Monday, September 07, 2009 1:00 PM
@byschaupp
Sorry i was away. Is your problem still exist?
AUmidh  Monday, September 07, 2009 1:04 PM
AUmidh,

After I asked my question, we went away on vacation. I'mnot connected to my regular computer, so I won't be able to find out for sure until about Sept. 14. I think Bonnie's recommendation to include the Databinding may solve the problem. I can't wait to try it as soon as I get back. I'll let you guys know the result, and mark it answered if it sorks.

Thanks again AUmidh and Bonnie for your help.

Bill
byschaupp  Monday, September 07, 2009 8:09 PM
Bonnie & AUmidh,

Back from vacation. I tried without success to get the combo box working. My understanding of C# and SQL is not yet adequate. I found a couple of books that should bring me up to speed. I'll post my code once I get it working. Thanks again.

Bill
byschaupp  Wednesday, September 16, 2009 12:37 PM
Bill,

Why don't you post what you have tried, even though it doesn't work. We may be able to spot the problem. Also, tell us specifically what is not working.
~~Bonnie Berent [C# MVP]
BonnieB  Friday, September 18, 2009 4:32 PM

Thanks Bonnie, I am still trying. I will respond with details of my lastest version tomorrow morning.

Bill

byschaupp  Friday, September 18, 2009 10:13 PM
Bonnie & Aumidh,

I got it working. Basically there were two problems that needed to be solved.
First, I had to get the combo box populated properly so it displayed VendName andincluded the VendID column so it's value could be inserted in the Product table.
Second, since I used an "int identifier" type for the VendID,I needed to convert the "selected value" from the combo box back to an int type. Somehow, I figured that out.

I used Bonnie's recommendation in 'void ConnectVendorsCmbxFill()'to solvethe first problem:

foreach (DataRow row in dt.Rows)
{
cmbxVendName.DisplayMember = "VendName";
cmbxVendName.ValueMember = "VendID";
//PROBLEM 1 FIXED -- puts both VendName and VendID into combobox
cmbxVendName.DataSource = dt;
}

I conjured up a couple of lines in 'void ConnectProductInsert() to solve the second problem:

string strSelVal = cmbxVendName.SelectedValue.ToString();
int intSelVal = int.Parse(strSelVal);

Complete code for both 'void ConnectVendorsCmbxFill()' and 'void ConnectProductInsert()' follow:

First --

void ConnectVendorsCmbxFill() {
//connection string
string connString = @"server = .\sqlexpress; integrated security = true; database = vendprod; ";
//query
string qry = @"select VendID, VendName
from Vendors
order by VendName"
;
//create connection
SqlConnection conn = new SqlConnection(connString);
conn.Open();

//create datatable
try {
//create data adapter
SqlDataAdapter da = new SqlDataAdapter(qry, conn);
//create data table
DataTable dt = new DataTable();
da.Fill(dt);

//fill combobox
foreach (DataRow row in dt.Rows){
cmbxVendName.DisplayMember =
"VendName";
cmbxVendName.ValueMember =
"VendID";
//PROBLEM 1 FIXED -- puts both VendName and VendID into combobox
cmbxVendName.DataSource = dt;
}
cmbxVendName.Text =
"Choose Vendor";
}
catch (Exception ex) { MessageBox.Show("Error: " + ex); }
finally {
//close connection
conn.Close();
}
}

Second --

void

ConnectProductInsert() {
//cast combobox selected value to integer (PROBLEM 2 FIXED -- PART 1)
string strSelVal = cmbxVendName.SelectedValue.ToString();
int intSelVal = int.Parse(strSelVal);
//create connection string
string connString = @"server = .\sqlexpress; integrated security = true; database = vendprod;";

//create select products qry for insert products table
string qry = @"select * from products";

//create insert SQL
string ins = @"insert products (ProdName, VendID)
values (@ProdName, @VendID)"
;

//create connection
SqlConnection conn = new SqlConnection(connString);
try {

//create data adapter
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand =
new SqlCommand(qry, conn);

//create and fill data set
DataSet ds = new DataSet();
da.Fill(ds,
"Products");

//get table reference
DataTable dt = ds.Tables["Products"];

//add new row
DataRow newRow = dt.NewRow();
newRow[
"ProdName"] = txtProdName.Text;
//insert converted integer from combo box -- (PART 2 OF PROBLEM 2)
newRow["VendID"] = intSelVal;
dt.Rows.Add(newRow);

//create insert command
SqlCommand cmd = new SqlCommand(ins, conn);
//
//map parameters
cmd.Parameters.Add("@ProdName", SqlDbType.NVarChar, 50, "ProdName");
cmd.Parameters.Add(
"@VendID", SqlDbType.Int, 25, "VendID");
da.InsertCommand = cmd;
da.Update(ds,
"Products");
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex);
}
finally
{
//close connection
conn.Close();
}
}

I can't believe it really works. Thanks for all the help.

Bill

byschaupp  Saturday, September 19, 2009 2:53 PM

I'm glad you got it working Bill, but you're still going about it the hard way. Sorry.

First, you don't need to spin through the rows of your DataTable when you "fill" your Combo. So this:

//fill combobox
foreach (DataRow row in dt.Rows){
cmbxVendName.DisplayMember =
"VendName";
cmbxVendName.ValueMember =
"VendID";
//PROBLEM 1 FIXED -- puts both VendName and VendID into combobox
cmbxVendName.DataSource = dt;
}
cmbxVendName.Text =
"Choose Vendor";
}
catch (Exception ex) { MessageBox.Show("Error: " + ex); }
finally {
//close connection
conn.Close();
}
}

Should be changed to this (note that I've simply taken out the iteration through the DataRow collection):

//fill combobox
cmbxVendName.DisplayMember = "VendName";
cmbxVendName.ValueMember =
"VendID";
//PROBLEM 1 FIXED -- puts both VendName and VendID into combobox
cmbxVendName.DataSource = dt;

cmbxVendName.Text =
"Choose Vendor";
}
catch (Exception ex) { MessageBox.Show("Error: " + ex); }
finally {
//close connection
conn.Close();
}
}

Now, secondly, it looks to me like you are retrieiving your entire Product DataTable simply to insert one row. While it's probably perfectly ok to have all your Products in one DataTable (depending on what you're going to do with them), retrieving them all each and every time you insert a new one isn't going to be very effective. Retrieve them all once, one time only. Update or insert data only when you need to. DataBind everything else to the Product table and you won't have to jump through quite so many hoops. This is kind of the point I've been trying to make ... databinding your DataTable to your various UI controls is the proper way to make this all happen.

So, here's some code off the top of my head to show you what I mean:

// member declarations
DataTable dtVendors = new DataTable();
DataTable dtProducts = new DataTable();

private void FillDataAndDataBind()
{
	this.ConnectVendorsCmbxFill();
	this.FillProducts();
	this.DataBind();
}
private void DataBind()
{
	this.cmbxVendName.DataBindings.Add("SelectedValue", this.dtProducts, "VendID");
	this.txtProdName.DataBindings.Add("Text", this.dtProducts, "VendName");
}

private void Save()
{
	this.UpdateProducts();
}
private void FillProducts()
{
	//create connection string
	string connString = @"server = .\sqlexpress; integrated security = true; database = vendprod;"; 

	//create select products qry
	string qry = @"select * from products"; 

	//create connection
	SqlConnection conn = new SqlConnection(connString);
	try { 
		//create data adapter
		SqlDataAdapter da = new SqlDataAdapter();
		da.SelectCommand = new SqlCommand(qry, conn); 

		da.Fill(this.dtProducts); 
	}
	catch (Exception ex)
	{ 
		MessageBox.Show("Error: " + ex);
	}
	finally
	{
		//close connection
		conn.Close();
	}
}

// somewhere on your form, you'll either need a button to add a new row to your dtProducts table, 
// or you simply add it yourself programatically ( I'd probably do it in the DataBind method, but I
// really don't know the total flow of your app, so it may belong elsewhere ... button click sounds better to me).
private void AddNewProduct()
{
	this.dtProducts.Rows.Add(this.dtProducts.NewRow());
	// here's what you have to do to make databinding work nicely
	this.BindingContext[this.dtProducts].Position = this.dtProducts.Rows.Count - 1;
}

// bb - I've left this as you had it (with the exception of removing the iteration through the Rows
//        and using this.dtVendors).
private void ConnectVendorsCmbxFill() {
	//connection string
	string connString = @"server = .\sqlexpress; integrated security = true; database = vendprod; ";
	//query
	string qry = @"select VendID, VendName
	from Vendors
	order by VendName";
	//create connection
	SqlConnection conn = new SqlConnection(connString);
	conn.Open(); 

	//create datatable
	try {
		//create data adapter
		SqlDataAdapter da = new SqlDataAdapter(qry, conn);
		//create data table
		da.Fill(this.dtVendors); 

		//fill combobox
		cmbxVendName.DisplayMember = "VendName";
		cmbxVendName.ValueMember = "VendID";
		//PROBLEM 1 FIXED -- puts both VendName and VendID into combobox
		cmbxVendName.DataSource = this.dtVendors;

		cmbxVendName.Text = "Choose Vendor";
	}
	catch (Exception ex) { MessageBox.Show("Error: " + ex); }
	finally {
		//close connection
		conn.Close();
	}
}

// Change the name of this method to reflect the fact that you're updating the Product table
// Even though I'm not sure your app is updating anything, looks like you just want to add new Products
// private void ConnectProductInsert() {
private void UpdateProducts() {
	// bb - you won't need this anymore because you're databinding
	//cast combobox selected value to integer (PROBLEM 2 FIXED -- PART 1)
	//string strSelVal = cmbxVendName.SelectedValue.ToString();
	//int intSelVal = int.Parse(strSelVal);
	
	// bb - likewise, for the same reason, you won't need this:
	//add new row
	//DataRow newRow = dt.NewRow();
	//newRow["ProdName"] = txtProdName.Text;
	//insert converted integer from combo box -- (PART 2 OF PROBLEM 2)
	//newRow["VendID"] = intSelVal;
	//dt.Rows.Add(newRow); 
	
	//create connection string
	string connString = @"server = .\sqlexpress; integrated security = true; database = vendprod;"; 

	//create insert SQL
	string ins = @"insert products (ProdName, VendID)
	values (@ProdName, @VendID)"; 

	//create update SQL
	string upd = @"update products SET ProdName = @ProdName, 
	                               SET VendID = @VendID";

	//create connection
	SqlConnection conn = new SqlConnection(connString);
	try { 
		//create data adapter
		SqlDataAdapter da = new SqlDataAdapter();
		da.InsertCommand = new SqlCommand(ins, conn); 
		da.UpdateCommand = new SqlCommand(upd, conn); 

		//map parameters
		cmd.Parameters.Add("@ProdName", SqlDbType.NVarChar, 50, "ProdName");
		cmd.Parameters.Add("@VendID", SqlDbType.Int, 25, "VendID");
		da.Update(this.dtProducts);
	}
	catch (Exception ex)
	{ 
		MessageBox.Show("Error: " + ex);
	}
	finally
	{
		//close connection
		conn.Close();
	}
}



I hope this helps you see the bigger picture. =0)


~~Bonnie Berent [C# MVP]
BonnieB  Saturday, September 19, 2009 5:15 PM
Thanks Bonnie,

This is very informative. Much more efficient than my primative attempt. Never got into databinding before.

My simple approach uses two buttons. First button "Add Product" on "main form" when clicked "shows" the Add Product Form which is populated with the cmbxVendName combox, and txtProdName text box. Once a vendor is selected with cmbxVendName and text is input to txtProdName, an "Insert Product" button "shows", and a click of the "Insert Product" button inserts VendID and ProdName into the SQL Express Products table. My effort so far seems to work because a simple query "joining" Vendors table fields and Products table fields on VendID produces the result I expect.

I understand yourConnectVendorsCmbxFill() revision. It works fine. Let me tinker with databinding a bit and I'll let you know how it works out (if my brain doesn't explode absorbingit allin the meantime). How much do I owe you?

Bill
byschaupp  Sunday, September 20, 2009 1:46 PM

Hi Bill,

Yep, your waywill work ... but wouldn't it be nice to have it all displayed on one form? ;0)

>>Let me tinker with databinding a bit and I'll let you know how it works out (if my brain doesn't explode absorbingit allin the meantime).<<

Sure ... and let me know if you have any other questions about it.

>>How much do I owe you?<<

hahahahaha --- actually, I have a new blog thatI just started last week. I have a PayPal "donate" button on it, if you're so inclined. ;0) Actually, I'm not really being serious here (and I'm sure you weren't either)...no oneexpects any payment for forum participation ...but I *am* unemployed at the moment. ;0)


~~Bonnie Berent [C# MVP]

(new blog --- not many posts yet --- be patient)
geek-goddess-bonnie.blogspot.com
BonnieB  Sunday, September 20, 2009 3:03 PM
Hi Bonnie,

Still trying to get the hang of this Databinding thing. It seems to make sense, but I don't yethave a real clear idea of what is happening.

In your private void UpdateProducts() method, I hang up at the "da.Update(this.dtProducts);" line. I get the error message:
Error: System.Data.SQLClient.SQLException: cannot insert the value NULL into 'ProdName', table 'VendProd.dbo.Products': column does not allow nulls. INSERT fails...

Since there is no declaration for 'cmd' in the two lines preceding da.Update..., I changed "cmd.Parameters.Add..." to "da.InsertCommand.Parameters.Add..."

Not sure how to handle this.

Bill

PS. Guess we're both without paying jobs. I'll chek out your blog from time to time.
byschaupp  Monday, September 21, 2009 4:25 PM
Hey Bill,

Yeah, sorry about the cmd.Parameters ... changing it to da.InsertCommand.Parameters is good.

About the error message ... cannot insert NULL, did you set a breakpoint there and look at the value of ProdName? It's probably cuz I screwed up and didn't show the correct databinding for the txtProdName TextBox in the DataBind() methodand you probably didn't catch my mistake. I had:

this.txtProdName.DataBindings.Add("Text", this.dtProducts, "VendName");

And it should be this:

this.txtProdName.DataBindings.Add("Text", this.dtProducts, "ProdName");

Does that help?
~~Bonnie Berent [C# MVP]

(new blog --- not many posts yet --- be patient)
geek-goddess-bonnie.blogspot.com
BonnieB  Monday, September 21, 2009 4:43 PM
Hmmm... I fixed that and still get the same error message.

A little background. My simple UI starts with a form that has two buttons -- btnAddVend andbtnAddProd.

btnAddVend click shows a groupbox with a blank textbox txtVendName. When text (Vendor Name) is entered in txtVendName a button btnInsertVend appears. btnInsertVend click updates SQL table Vendors and UI goes back to start with two buttons -- btn AddVend and btnAddProd.

btnAddProd click shows a groupbox with a combo box cmbxVendName filled with vendor names per 'ConnectVendorsCmbxFill()' and a blank text box 'txtProdName'. Once a vendor is selected and text entered in txtProdName, btnInsertProd appears. btnInsertProd click should update SQL table Products and send UI back to start with two buttons -- btnAddVend and btnAddProd.

I'm using three of yourmain methods ro run btnInsertProd as follow:

private void btnInsertProd_Click(object sender, EventArgs e) {
//ConnectProductInsert();
//clear textbox and hide button
FillDataAndDataBind();
AddNewProduct();
UpdateProducts();
gpbxProdInsert.Hide();
btnAddVend.Show();
btnAddProd.Show();
btnCancel.Hide();
}

Thanks again.

Bill
byschaupp  Monday, September 21, 2009 5:41 PM

The FillDataAndDataBind() method is intended to be run only one time, not with every button click.

But, oh wait ... I know what the problem probably is. Sorry I forgot about mentioning it, because it's a question thatcomes up frequently and I have a standard canned response for it. Here it is:

Data in a DataRow has several different versions. First, there's the original version. Then, when it's being edited, it has a Proposed version and once it's done being edited, that becomes the Current version. Sometimes when editing, the row is left in the Proposed state and the Edit needs to be ended programmatically.

Here's a method I *always* call before I attempt to check for .HasChanges() before saving data:

protected virtual void CommitProposedChanges(DataSet ds)
{
	if (ds == null)
		return;

	for (int nTable = 0; nTable < ds.Tables.Count; nTable++)
	{
		for (int nRow = 0; nRow < ds.Tables[nTable].Rows.Count; nRow++)
		{
			if (ds.Tables[nTable].Rows[nRow].HasVersion(DataRowVersion.Proposed))
			{
				ds.Tables[nTable].Rows[nRow].EndEdit();
			}
		}
	}
}


So, add the above method, and in theSave() method, call it before you call the UpdateProducts() method.See if that takes care of it.


~~Bonnie Berent [C# MVP]

(new blog --- not many posts yet --- be patient)
geek-goddess-bonnie.blogspot.com
BonnieB  Monday, September 21, 2009 6:01 PM
Bonnie,

I think I get it about the FillDataAndDataBind() method. I put it in the form load event.

This discussion is really expanding my understanding of how things work together. I need to get back to the books and work some more basic examples so I have a better understanding of what's happening in the background as Itry out your recommendations. I think you are about a light year or so ahead of me.

If you don't mind, I'll continue this thread from time to time as I try to catch up.

Thanks again for the help and guidance.

Bill
byschaupp  Tuesday, September 22, 2009 12:59 PM

You can use google to search for other answers

Custom Search

More Threads

• Printing a designed layoutform
• Function as Arugment?
• ListView ColumnHeader.TextAlign can not be set other than Left for Col 1
• Color change of text in a label
• Event Delegation
• Using CheckBoxColumn in DataGrid
• help with bound combobox and selectedindexchanged.
• SplashScreen in my application
• Using Resrouce file in runtime
• How to handle button click