1. Original Entry + Comments2. Write a Comment3. Preview Comment
New comments for this entry are disabled.


February 08, 2006  |  DropDownList in a FormView control  |  73383 hit(s)

Another in an apparently relentless series of posts about trying to see how much can be done with the new data controls in ASP.NET 2.0 without writing code. It's pretty easy to take advantage of the code hooks we all know and love, like the ItemDataBound event in the DataGrid control (RowDataBound in the GridView control). which lets you do a whole lotta stuff. A more interesting challenge is to see what you can do with no code at all.

A question came up several times the other day about using a DropDownList control inside a (e.g.) FormView control. It's a pretty common scenario -- people want to be able to edit values by picking a value from a list:



In this example, the FormView control displays data from the Northwind Products table. One of the fields is CategoryID. The easy way to edit the CategoryID field is to pick from a list of categories. So the FormView control's templates contain a DropDownList control that displays records from the Category table. Make sense?

Once again the clever Polita (who owns the FormView control) was able to whip this thing out on her whiteboard in about 60 seconds. This, I should probably add, after I myself had, mmm, not been able to do that.

The first job is to display the category name in the read-only ItemTemplate. You can do that a couple of ways. One way would be to create a Select statement that joined Products to Categories and thereby gave you access to CategoryName. You could then display it using a Label control or something.

Here I practiced, so to speak, using the DropDownList control. The FormView and DropDownList controls, as I hope is clear, display data from two different tables. The easy way to set that up is to use two data source controls -- one to get the Products data, and a second one to get the Category data. The FormView control is bound to the first data souce control, no problem. In the ItemTemplate, you add a DropDownList and configure it something like this:
<asp:DropDownList ID="DropDownList1" runat="server" 
DataSourceID="AccessDataSource2"
DataTextField="CategoryName"
DataValueField="CategoryID"
SelectedValue='<%# Eval("CategoryID") %>'
Enabled="False" />
The two interesting things here are 1) we set the SelectedValue property by data binding it to the current CategoryID value. The DropDownList sorts out (haha) how to use that value to display the right category name. 2) We're using Eval, because the ItemTemplate is read only.

And it turns out -- this was the surprising part, maybe -- that is isn't any harder to use a DropDownList control in the EditItemTemplate, where it functions as a category picker. Here's the declaration:
<asp:DropDownList ID="DropDownList1" runat="server" 
DataSourceID="AccessDataSource2"
DataTextField="CategoryName"
DataValueField="CategoryID"
SelectedValue='<%# Bind("CategoryID") %>' />
Same deal, except that in this case, we use Bind for the data binding method in order to get two-way binding. The whole entire page is below, including the data source controls (Access, in this case). (It's not formatted at all to try to keep the listing to a reasonable size.)
<%@ Page Language="VB" %>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
' Look ma, no code!
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>FormView with DropDownList</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:AccessDataSource ID="AccessDataSource1" runat="server"
DataFile="~/App_Data/northwind.mdb"
SelectCommand="SELECT [ProductID], [ProductName],
[CategoryID] FROM [Products]"
UpdateCommand="UPDATE [Products] SET [ProductName] = ?,
[CategoryID] = ? WHERE [ProductID] = ?">
<UpdateParameters>
<asp:Parameter Name="ProductName" Type="String" />
<asp:Parameter Name="CategoryID" Type="Int32" />
<asp:Parameter Name="ProductID" Type="Int32" />
</UpdateParameters>
</asp:AccessDataSource>
<br />
<asp:AccessDataSource ID="AccessDataSource2"
runat="server"
DataFile="~/App_Data/Northwind.mdb"
SelectCommand="SELECT [CategoryID], [CategoryName]
FROM [Categories]">
</asp:AccessDataSource>
<br />
<br />
<asp:FormView ID="FormView1" runat="server"
DataKeyNames="ProductID"
DataSourceID="AccessDataSource1"
AllowPaging="True">
<EditItemTemplate>
ProductID:
<asp:Label ID="ProductIDLabel1" runat="server"
Text='<%# Eval("ProductID") %>'/>
<br />
ProductName:
<asp:TextBox ID="ProductNameTextBox" runat="server"
Text='<%# Bind("ProductName") %>' />
<br />
CategoryID:
<asp:DropDownList ID="DropDownList1" runat="server"
DataSourceID="AccessDataSource2"
DataTextField="CategoryName"
DataValueField="CategoryID"
SelectedValue='<%# Bind("CategoryID") %>' />
<br />
(<asp:Label ID="CategoryIDLabel"
runat="server"
Text='<%# Eval("CategoryID") %>' />)
<br />
<asp:LinkButton ID="UpdateButton" runat="server"
CausesValidation="True"
CommandName="Update"
Text="Update" />
<asp:LinkButton ID="UpdateCancelButton" runat="server"
CausesValidation="False"
CommandName="Cancel"
Text="Cancel" />
</EditItemTemplate>


<ItemTemplate>
ProductID:
<asp:Label ID="ProductIDLabel" runat="server"
Text='<%# Eval("ProductID") %>' />
<br />
ProductName:
<asp:Label ID="ProductNameLabel" runat="server"
Text='<%# Eval("ProductName") %>' />
<br />
CategoryID:
<asp:DropDownList ID="DropDownList1" runat="server"
DataSourceID="AccessDataSource2"
DataTextField="CategoryName"
DataValueField="CategoryID"
SelectedValue='<%# Eval("CategoryID") %>'
Enabled="False" />
(<asp:Label ID="CategoryIDLabel" runat="server"
Text='<%# Eval("CategoryID") %>'/>)
<br />
<br />
<asp:LinkButton ID="EditButton" runat="server"
CausesValidation="False"
CommandName="Edit"
Text="Edit" />
</ItemTemplate>
</asp:FormView>
</div>
</form>
</body>
</html>


Another in an apparently relentless series of posts about trying to see how much can be done with the new data controls in ASP.NET 2.0 without writing code. It's pretty easy to take advantage of the code hooks we all know and love, like the ItemDataBou ...



kazoo   20 Feb 06 - 7:04 PM

I am trying to do a similar thing. But, my Product gridview is bound to an ObjectDataSource (with properties as ProductId, ProductName, Category), and my Category dropdownlist in the formview is bound to another ObjectDataSource (with properties as CategoryId, CategoryName).

I am able to bind the formview fine .. both in ItemTemplate and EditItemTemplate.

<asp:DropDownList runat="server" ID="ddlEditCategory"
DataValueField="CategoryId" DataTextField="CategoryName" DataSourceID="CategoryDataSource" AppendDataBoundItems="True" SelectedValue='<%# Bind("CategoryId") %>'>
<asp:ListItem Text="-- Select Category --" Value="0"> </asp:ListItem>
</asp:DropDownList>

But I am having trouble with the UpdateMethod on the Product object. This method expects the ProductId, ProductName and CategoryId arguments, but the Category field is bound to tbe Category object in the FormView.

Any ideas???

Thanks in advance!


 
Pepi   06 Mar 06 - 3:50 PM

To think that this is one of the most common things that you would do in a DetailsView and yet there is nothing close to what I found in here.

You rock!!!


 
TheBorg   08 Mar 06 - 7:00 PM

Thank you sir! This has been a pesky problem for which there don't seem to be a lot of examples; either that, or my google-fu is weak.

Thanks again


 
Garry Lindsay   28 Mar 06 - 6:57 PM

All they need to do is make a label control that has a DataSourceID property i.e. like a dropdown list.

Has anybody written one yet?


 
Simone Busoli   02 Apr 06 - 11:37 PM

@ Garry

Yes, maybe.

In the meanwhile, if you don't want to get the Text property of a label control from the select statement of the DetailsView DataSourceControl you should write a custom databinding expression like

<asp:label ... Text="<%# GetLabelText() %>"

where GetLabelText is a method which returns the value you want, maybe retrieving it from a database.


 
Jason   02 May 06 - 3:53 AM

How would you select multiple items for a control like checkboxlist?

 
mike   15 May 06 - 9:48 AM

Jason -- tough one. The CheckBoxList control doesn't have a SelectedItems property, so the only way to pre-select items is to a) data-bind the CheckBoxList control to the data and then b) separately walk the data source manually and set check boxes individually. This latter might mean re-querying the data. In any event, it has to be done in code; there's no declarative way to do it, unfortunately.

 
lh   16 May 06 - 1:37 PM

you are the man! i am so surprised that so many asp.net 2 books, even the "pro" ones, use only the most simplistic example and don't even tell you this.

 
The Colonel   27 May 06 - 9:49 PM

Awesome! What about when the FK is null (or not in the list)? I get a "myddl has a SelectedValue which is invalid because it does not exist in the list of items". I've tried to intercept at SelectedIndexChanged, TextChanged and DataBinding (bombs before Databound) to insert a "blank" entry, but no good. Any ideas?



 
Eric Pincus   24 Sep 06 - 11:58 PM

Call me crazy but I get "The 'SelectedValue' property cannot be set declaratively.

What am I missing?


 
Hugo   20 Oct 06 - 6:52 AM

The process described here is quite straightforward, and works fine for InsertTemplate and EditTemplate. But for the ItemTemplate, it doesn’t look too good, having that big combo box hanging around with its useless select button on the side (even if disabled)… Isn’t there a better way to have something like Simone Busoli described with very little code?

 
Hugo   20 Oct 06 - 6:52 AM

The process described here is quite straightforward, and works fine for InsertTemplate and EditTemplate. But for the ItemTemplate, it doesn’t look too good, having that big combo box hanging around with its useless select button on the side (even if disabled)… Isn’t there a better way to have something like Simone Busoli described with very little code?

 
mike   22 Oct 06 - 12:15 PM

Hugo, I'm not 100% sure I understand your question, but I think that you can just do something like this:
<asp:Label ID="CategoryNameLabel" 
runat="server"
Text='<%# Eval("CategoryName") %>' />

And of course just delete the DropDownList from the ItemTemplate markup.

Is that what you mean?


 
Hugo   23 Oct 06 - 1:37 AM

Thanks mike that is of course the logic thing to do if the data source had any "CategoryName". But what you have is the "CategoryID" and you need to bind to a second data source to get "CategoryName" like the article explains.
The best solution I've found Is to create a GetCategoryName function that returns the CategoryName from the secondsource and use it like this:
<asp:label ... Text='<%# GetCategoryName(Bind("CategoryId")) %>' >


 
hugo   23 Oct 06 - 1:49 AM

I fergot that here we can't have the two-way bind, Eval has to be used instead of Bind:
<ItemTemplate>
CategoryID:
<asp:label ID='CategoryNameLabel' Text='<%# GetCategoryName(Eval("CategoryId")) %>' />
(<asp:Label ID="CategoryIDLabel" runat="server" Text='<%# Eval("CategoryID") %>'/>)
</ItemTemplate>


 
abuk   22 Nov 06 - 9:33 AM

and if i want tou use 2 dropdownlists? i get the error message - 'DropDownList2' has a SelectedValue which is invalid because it does not exist in the list of items.
Parameter name: value

it's possible to use 2 ddl in this case (with a formview)?

thanks


 
mike   22 Nov 06 - 11:00 AM

@ abuk -- see the follow-up here:

http://mikepope.com/blog/DisplayBlog.aspx?permalink=1629


 
Jordan Rieger   22 Nov 06 - 3:45 PM

Does any one know an easy (declarative, ASP.NET 2.0) way of achieving this functionality for a simple label in the ItemTemplate? I don't want the non-editable ItemTemplate to have a DropDownList, even a disabled one. I want it to have a simple label, the contents of which are the CategoryName from the 2nd DataSource corresponding to the CategoryID in the current DataSource. I only want the DropDownList to appear in the EditItemTemplate.

 
mike   22 Nov 06 - 6:06 PM

Well, one way is to use a join in the original query, which brings the CategoryName into the dataset for the first data source. The Select query might look like this:
SELECT Products.ProductID, 
Products.ProductName,
Products.CategoryID,
Categories.CategoryName
FROM (Products INNER JOIN Categories ON
Products.CategoryID = Categories.CategoryID)
ORDER BY ProductID
You can then have an ItemTemplate like this:


<ItemTemplate>

ProductID:
<asp:Label ID="ProductIDLabel" runat="server"
Text='<%# Eval("ProductID") %>' />
<br />

ProductName:
<asp:Label ID="ProductNameLabel" runat="server"
Text='<%# Bind("ProductName") %>' />
<br />

Category:
<asp:Label ID="Label1" runat="server"
Text='<%# Eval("CategoryName") %>' />


<br /><br />

<asp:LinkButton ID="EditButton" runat="server"
CausesValidation="False" CommandName="Edit"
Text="Edit" />

<asp:LinkButton ID="DeleteButton" runat="server"
CausesValidation="False" CommandName="Delete"
Text="Delete" />

<asp:LinkButton ID="NewButton" runat="server"
CausesValidation="False" CommandName="New"
Text="New" />

</ItemTemplate>


 
Merrill Callaway   29 Jan 07 - 10:56 AM

Your SelectedValue solution seemed such a good idea, until I noticed that if your dropdownlist data changes you get an error:
=================
Exception Details: System.ArgumentOutOfRangeException: 'approvalDropDown' has a SelectedValue which is invalid because it does not exist in the list of items.
Parameter name: value
=================
I was trying to display data for editing, when the 'approvalDropDown' is a list of approvers that changes. In other words, my data could contain people who no longer worked here. I would have hoped if the item were not found, it could default to the first item or something, but no, it bombs with an error.
How can I code to display the first item if there is not match? I don't think there is a 'no code writing way' to do this and I'd have to make a code behind to do this. Can you give an example?


 
Charlie Voss   11 Feb 07 - 11:34 AM

Can you do all of this in a single GridView control?

Charlie


 
mike   11 Feb 07 - 11:40 AM

@Charlie Voss -

The GridView control doesn't support insert mode, is that what you mean?


 
Charlie Voss   11 Feb 07 - 12:47 PM

@Mike

No, I meant a single GridView that pulls ProductID, ProductName, CategoryID and CategoryName, and allows you to use a drop-downlist in the CategoryName field in Edit mode to Edit (Update) CategoryID in the Product table.

Make sense?


 
Charlie Voss   11 Feb 07 - 1:07 PM

@ Mike -

For example, I have everything in one GridView as below, but when I try to insert a drop-down into the mix to handle the Edits everything blows up:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataSourceID="NorthwindDataSource1">
<Columns>
<asp:CommandField ShowEditButton="True" />
<asp:BoundField DataField="ProductID" HeaderText="ProductID" InsertVisible="False"
SortExpression="ProductID" />
<asp:BoundField DataField="ProductName" HeaderText="ProductName" SortExpression="ProductName" />
<asp:BoundField DataField="SupplierID" HeaderText="SupplierID" SortExpression="SupplierID" />
<asp:BoundField DataField="CategoryID" HeaderText="CategoryID" SortExpression="CategoryID" />
<asp:BoundField DataField="SupplierName" HeaderText="SupplierName" SortExpression="SupplierName" />
<asp:BoundField DataField="CategoryName" HeaderText="CategoryName" SortExpression="CategoryName" />
</Columns>
</asp:GridView>
<asp:AccessDataSource ID="NorthwindDataSource1" runat="server" DataFile="~/App_Data/Northwind.mdb"
SelectCommand="SELECT Products.ProductID, Products.ProductName, Products.SupplierID, Products.CategoryID,
Suppliers.CompanyName AS SupplierName, Categories.CategoryName
FROM ((Products INNER JOIN Categories ON Products.CategoryID = Categories.CategoryID)
INNER JOIN Suppliers ON Products.SupplierID = Suppliers.SupplierID)"
UpdateCommand="UPDATE Products SET ProductName = ?, SupplierID = ?, CategoryID = ? WHERE (ProductID = ?)">
</asp:AccessDataSource>

Charlie


 
gorag   25 Sep 07 - 12:41 PM

Hi, this is a neat and easy solution - should point out though that it is dependant upon the design of the database to work, you can't substitute any two tables you must have a common field, as I discovered!
Cheers,
Steve.


 
Mike Plant   10 Nov 07 - 11:54 AM

Nice job Mike! Thanks from another Mike.
I knew it had to be simple!


 
mback   19 Nov 07 - 7:29 AM

This is just what I needed, Thank You!!

Now I just have to figure out how to have the dropdown list include a blank that is not in my database table. :)