ASP.Net Databinding inside a bound control - asp.net

I'm having a littl fun with ASP.Net data binding today, basically I have two nested controls, and an collection of objects with their own internal collections which I wish to bind...
So, say I'm using two repeaters like this ->
<asp:Repeater ID="Repeater1">
<ItemTemplate>
<asp:Label runat="server" Text='<%#DataBinder.Eval(Container.DataItem, "HeaderText")%>'>
</asp:Label>
<asp:Repeater ID="Repeater2">
<asp:Label runat="server" Text='<%#DataBinder.Eval(Container.DataItem, "DetailText")%>'>
</asp:Label>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
And my objects look like this:
public class parent
{
public string HeaderText {get;set;}
public List<child> children {get;set;}
}
public class child
{
public string DetailText {get;set;}
}
How do I bind the inner repeater? I'm guessing that I need to set & bind the datasource of 'Repeater2' somewhere in the aspx to be the 'children' property of 'parent'?
Can someone point me in the right direction?
Thanks

Bind the nested repeater in the main repeater ItemDataBound event.
http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.repeater.itemdatabound.aspx
Here you can find the control (FindControl) and bind to it.
It would be something like:
<asp:Repeater ID="Repeater1" OnItemDataBound="Repeater1_ItemDataBound">
void Repeater1_ItemDataBound(Object Sender, RepeaterItemEventArgs e) {
Repeater Rep2 = e.Item.FindControl("Repeater2");
Rep2.DataSource = //datasource here
Rep2.DataBind();
}

You would bind the inner repeater in the ItemDatabound event of the outer repeater:
protected void Repeater1_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
Repeater innerRepeater = e.Item.FindControl("InnerRepeater1") as Repeater;
if (innerRepeater != null)
{
innerRepeater.DataSource = GetSomeData();
innerRepeater.DataBind();
}
}
It might be easier to use a ListView or DataList if you need to use data from the outer databound control to bind the inner databound control, because you can specify datakeys.
<asp:ListView ID="ListView1" runat="server" DataKeyNames="SomeColumn" ...>
Code-behind:
protected void ListView1_ItemDataBound(object sender, ListViewItemEventArgs e)
{
ListView innerList = e.Item.FindControl("InnerList1") as ListView;
if (innerList != null)
{
innerList.DataSource = GetSomeData((int)ListView1.DataKeys[ListView1.Items.IndexOf(e.Item)]["SomeColumn"]);
innerList.DataBind();
}
}

Related

Get value of gridview.selected.row[] with visible property = 'false'

Hi from this field in my gridview, I'd like to pass the id value when the select command field is clicked, but i don't want to have the id field visible so I have the visible property set to false; however, when I pass it on the SelectedIndexChanged event the value is "" yet when the visible property is set to "true" the text value passes fine. What is the correct way to do this?
<asp:BoundField DataField="Project_ID" Visible="false"/>
protected void GridView1_SelectedIndexChanged(object sender, EventArgs e)
{
String ProjID = GridView1.SelectedRow.Cells[10].Text;
}
Try this:
.aspx
<asp:Gridview id="GridView1" runat="server" DataKeyNames="Project_ID" />
.cs
protected void GridView1_SelectedIndexChanged(object sender, EventArgs e)
{
if(gridView.SelectedDataKey != null)
{
var selectedId = GridView1.SelectedDataKey.Value;
}
}
Here you'll find more info about DataKeys in Gridviews: http://msdn.microsoft.com/de-de/library/system.web.ui.webcontrols.gridview.selecteddatakey(v=vs.110).aspx
I have done something like this in winform maybe can help you. That is what i used
int rowindex = dataGridView1.CurrentRow.Index;
string ProjID= dataGridView1.Rows[rowindex].Cells[10].Value.ToString();
you could use HiddenField inside your gridView ItemTemplate to keep the ID and use it in onrowcommand event, like below:
.aspx
<asp:GridView ID="gridProject" runat="server"
onrowcommand="gridProject_RowCommand">
<Columns>
<ItemTemplate>
<asp:HiddenField ID="hidProjectID" runat="server"
Value='<%# ((DataRowView)Container.DataItem)["Project_ID"] %>' />
<asp:Button ID="btnProject" runat="server" Text="use pro id"
CommandArgument="<%# ((GridViewRow) Container).RowIndex %>"
CommandName="DoSomething"></asp:Button>
</ItemTemplate>
</Columns>
</asp:GridView>
aspx.cs
protected void gridProject_RowCommand(object sender, GridViewCommandEventArgs e)
{
int index = 0;
GridViewRow gridRow;
GridView grid = sender as GridView;
try
{
switch (e.CommandName)
{
case "DoSomething":
index = Convert.ToInt32(e.CommandArgument);
row= gridProject.Rows[index];
string Id = ((HiddenField)row.FindControl("hidProjectID")).Value;
//do whatever you want here
break;
// and you can have as many commands as you want here
}
}
catch { //display error }
}

View state on dropdown control

<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:Button ID="Button1" runat="server" UseSubmitBehavior="false"
OnClick="Button1_Click" OnClientClick="this.disabled = true; this.value = 'Submitting...'; "
Text="Click Me…" /></div>
<asp:DropDownList ID = "dListFruits" runat ="server" EnableViewState ="true"></asp:DropDownList>
protected void Page_Load(object sender, EventArgs e)
{
ArrayList aList = new ArrayList();
aList.Add("Apples");
aList.Add("Oranges");
if (!Page.IsPostBack)
{
dListFruits.DataSource = aList;
dListFruits.DataBind();
TextBox1.Text = "Hi";
}
}
protected void Button1_Click(object sender, EventArgs e)
{
}
When i had a break point in a button click event , this peice of statement "dListFruits.DataSource" shows null while debugging. I thought view state will be applied before load event triggered. But when i see the page items are appended into the dropdown list. So view state will be applied just before render?
Just check out the Items collection of the dropdownlist. Viewstate stores the items collection of dropdownlist control.

Asp.net Formview with a gridview inside the Edit/InsertTemplate working example

I'm using a FormView to edit my business objects. I don't have any problem to edit/insert single properties.
Some of the business objects have collection properties that I'd like edit/insert in the same way I do for the single properties: Text='<%# Bind("SinglePropertyName") %>'.
So I'd like to include a gridview inside of the edit/insert templates and bind (two-way) it Datasource to the collection property: Datasource='<%# Bind("CollectionPropertyName") %>'. Then I'd like to be able to edit the collection propties items with the gridview itself and get the changed values among the other sigleproperties' changes.
This works fine to show the template, the collection is rendered to the gridview. The problem is to get the changes on it.
I've tried to do so with no luck, I get the following exception when trying to Databind the gridview: Databinding methods such as Eval(), XPath(), and Bind() can only be used in the context of a databound control.
Beside that, the NewValues of the FormView for the CollectionProperty from the ItemUpdating event always return null.
So I'd like to see a working example of a similar scenario to see if I'm able to do it or if I need to use a different approach.
Thanks!
I already found a solution to this and it was to encapsulate the gridview in a user control (ObjectList) that exposes a Value property to bind to.
<uc:ObjectList ID="ucObjectList" runat="server" Value='<%#Bind("Items") %>' />
ObjectList.ascx:
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="ObjectList.ascx.cs" Inherits="TestBinding.ObjectList" %>
<asp:GridView runat="server" ID="grdItems" DataSource='<%#Datasource%>'
OnRowEditing="grdItems_RowEditing"
OnRowCancelingEdit="grdItems_RowCancelingEdit"
OnRowUpdating="grdItems_OnRowUpdating">
<Columns>
<asp:CommandField ShowEditButton="True"></asp:CommandField>
</Columns>
</asp:GridView>
ObjectList.ascx.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.UI.WebControls;
namespace TestBinding
{
public partial class ObjectList : UserControl
{
protected List Datasource
{
get
{
if (ViewState["ObjectList"] == null) ViewState["ObjectList"] = new Test();
return (List)ViewState["ObjectList"];
}
set { ViewState["ObjectList"] = value; }
}
[Bindable(true, BindingDirection.TwoWay)]
public List Value
{
get { return Datasource; }
set { Datasource = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
}
protected void grdItems_RowEditing(object sender, GridViewEditEventArgs e)
{
((GridView)sender).EditIndex = e.NewEditIndex;
((GridView)sender).DataSource = Datasource;
((GridView)sender).DataBind();
}
protected void grdItems_RowCancelingEdit(object sender, GridViewCancelEditEventArgs e)
{
((GridView)sender).EditIndex = -1;
((GridView)sender).DataSource = Datasource;
((GridView)sender).DataBind();
}
protected void grdItems_OnRowUpdating(object sender, GridViewUpdateEventArgs e)
{
Datasource[e.RowIndex].ID = int.Parse(e.NewValues["ID"].ToString());
Datasource[e.RowIndex].Last = (string)e.NewValues["Last"];
((GridView)sender).EditIndex = -1;
((GridView)sender).DataSource = Datasource;
((GridView)sender).DataBind();
}
}
}
I hope this help you if you deal with something like that.

Nested DataPagers problem

Sample code
<asp:Repeater>
<ItemTemplate>
<asp:ListView DataSource=<%# Container.DataItem.Items %> ... />
<asp:DataPager .... />
</ItemTemplate>
</asp:Repeater>
This does not work.
The repeater data source is not a datasource control
It is set like so
repeater.DataSource = datasource
repeater.DataBind()
It is possible, and I've done it many times before.
You may have to wire up the events yourself, and you do have to use FindControl() in the repeaters item databound event to grab the specific ListView in order to set the Data Source, and also to call DataBind on it.
You can use the data binding shortcut <%# ... %> within the nested Repeater / DataList, but not to set the DataSource as you have done.
Paste the following into a blank new project. Compiles and runs.
(Big disclaimer - the html is just for demonstration and is pretty poor.)
Web Form Code.
<asp:ListView ID="dlOuter" runat="server"
onitemdatabound="dlOuter_ItemDataBound">
<LayoutTemplate>
<div id="personGroupList">
<asp:PlaceHolder ID="itemPlaceHolder" runat="server" />
</div>
</LayoutTemplate>
<ItemTemplate>
<div class="groupHeading"><%# Eval("Key") %></div>
<asp:ListView ID="dlInner" runat="server"
ItemPlaceholderID="innerItemPlaceHolder"
onitemdatabound="dlInner_ItemDataBound"
>
<LayoutTemplate>
<asp:PlaceHolder ID="innerItemPlaceHolder" runat="server" />
</LayoutTemplate>
<ItemTemplate>
<div class="person">
Name: <%# Eval("Name") %>
Age: <%# Eval("Age") %>
</div>
</ItemTemplate>
</asp:ListView>
</ItemTemplate>
</asp:ListView>
Now in the code behind
protected void Page_Load(object sender, EventArgs e)
{
// takes a list of Person and group's by Person.City
// really this is just an outer grouping that's in use.
var query = from p in Person.GetPersons() select p;
dlOuter.DataSource = query.ToLookup(o => o.City);
dlOuter.DataBind();
}
// The outer List View is the groups.
// we bind the inner view to the list if Person in the group.
protected void dlOuter_ItemDataBound(object sender, ListViewItemEventArgs e)
{
if (e.Item.ItemType == ListViewItemType.DataItem)
{
ListViewDataItem di = (ListViewDataItem)e.Item;
ListView inner = (ListView)e.Item.FindControl("dlInner");
IGrouping<string, Person> lookup = (IGrouping<string, Person>)di.DataItem;
inner.DataSource = lookup.AsEnumerable();
inner.DataBind();
}
}
protected void dlInner_ItemDataBound(object sender, ListViewItemEventArgs e)
{
// included so you can see how it's wired up. Unused in this sample.
}
Person class is just for demonstration.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string City { get; set; }
public static List<Person> GetPersons()
{
List<Person> persons = new List<Person>
{
new Person { Name="Bob", Age=30, City="Chicago" },
new Person { Name="Mary", Age=20, City="NYC" },
new Person { Name="Marty", Age=12, City="LA" },
new Person { Name="Fred", Age=33, City="NYC" },
new Person { Name="Susan", Age=22, City="Chicago" }
};
return persons;
}
}
Note that in this example I'm grouping using ToLookup to create a grouping from a list. In the real code this is derived from, it's showing a page of data ordered by what happens on a particular day. E.g. the records are sorted by thing.SomeDate and the grouping is query.ToLookup( o => o.SomeDate.ToLongDateString() );
It's important to note that the ToLookup and use of IGrouping<T,X> is irrelevant except I need to get grouped data in there somehow for the purposes of example. You could just as easily have the canonical example of Order and OrderDetail where the outer ListView is a List<Order> and the inner ListView is the Order.OrderDetails

Why is the footer-item not included in Repeater.Items?

I need to get a value from a textbox inside a FooterTemplate in the OnClick event of a button. My first thought was to loop through the items-property on my repeater, but as you can see in this sample, it only includes the actual databound items, not the footer-item.
ASPX:
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
Item<br />
</ItemTemplate>
<FooterTemplate>
Footer<br />
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
</FooterTemplate>
</asp:Repeater>
<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
Code-behind.cs:
protected void Page_Load(object sender, EventArgs e)
{
ListItemCollection items = new ListItemCollection();
items.Add("value1");
items.Add("value2");
Repeater1.DataSource = items;
Repeater1.DataBind();
}
protected void Button1_Click(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine(Repeater1.Items.Count);
}
This code will only output "2" as the count, so how do I get to reference my textbox inside the footertemplate?
From the MSDN documentation, the Items is simply a set of RepeaterItems based off the DataSource that you are binding to and does not include items in the Header or FooterTemplates.
If you want to reference the textbox, you can get a reference on ItemDataBound event from the repeater where you can test for the footer.
E.g.
private void Repeater_ItemDataBound(Object Sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Footer)
{
TextBox textBox = e.Item.FindControl("TextBox1") as TextBox;
}
}
You can find controls in the repeater. That will give you all the controls in the repeater (RepeaterItems collection). Now you can do something like this:
RepeaterItem footerItem=null;
foreach(Control cnt in Repeater1.Controls)
{
if(cnt.GetType() == typeof(RepeaterItem) && ((RepeaterItem)cnt).ItemType == ListItemType.Footer)
{
footerItem = cnt;
break;
}
}
The footer should be the last child control of the repeater so you can do something like..
RepeaterItem riFooter = Repeater1.Controls[Repeater1.Controls.Count - 1] as RepeaterItem;
if (riFooter != null && riFooter.ItemType == ListItemType.Footer) {
TextBox TextBox1 = riFooter.FindControl("TextBox1") as TextBox;
if (TextBox1 != null) {
TextBox1.Text = "Test";
}
}

Resources