Nested DataPagers problem - asp.net

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

Related

How can I Implement N level nested Repeater control in asp.net?

I want to achieve n level data hierarchy in using repeater control in asp.net.
Is there any solution to achieve that hierarchy ?
For this answer I'm going to suggest creating your template programmatically - see here: https://msdn.microsoft.com/en-us/library/aa289501 .
There is probably some way to use templates that have been created in markup, but this seems easier, and definitely more flexible.
I start out with a page with just a repeater (not template)
<body>
<form id="form1" runat="server">
<div>
<asp:Repeater runat="server" ID="TestRepeater">
</asp:Repeater>
</div>
</form>
</body>
and a data class
public class DataClass
{
public string Name { get; set; }
public List<DataClass> Children { get; set; }
}
For the template we use the following class:
public class DataTemplate : ITemplate
{
public void InstantiateIn(Control container)
{
var name = new Literal();
var repeater = new Repeater();
name.DataBinding += BindingLiteral;
repeater.DataBinding += BindingRepeater;
// this here makes it recursive
repeater.ItemTemplate = new DataTemplate();
container.Controls.Add(name);
container.Controls.Add(repeater);
}
private void BindingLiteral(object sender, System.EventArgs e)
{
var name = (Literal)sender;
var container = (RepeaterItem)name.NamingContainer;
name.Text = String.Concat("<h2>", DataBinder.Eval(container.DataItem, "Name").ToString(), "</h2>");
}
private void BindingRepeater(object sender, System.EventArgs e)
{
var name = (Repeater)sender;
var container = (RepeaterItem)name.NamingContainer;
name.DataSource = DataBinder.Eval(container.DataItem, "Children");
}
}
Obviously you'll want to use a more sophisticated template. Notice that if you currently have a template in markup, you could simply take the code that has been generated by the markup parser, and adapt it to your needs.
Now in the code behind of the page we simple assign the ItemTemplate and DataSource:
public partial class Test : System.Web.UI.Page
{
protected void Page_Init(object sender, EventArgs e)
{
TestRepeater.DataSource = GetTestData();
TestRepeater.ItemTemplate = new DataTemplate();
TestRepeater.DataBind();
}
}
Nice thing about this is your template is just a class, so you could add a public Int32 Depth { get; set; } to it, and change the generated controls based on your depth.
Another solution, without creating the template programmatically :
Using a simple data class :
public class DataClass
{
public string Name { get; set; }
public List<DataClass> Children { get; set; }
}
In the ASPX markup create your parent repeater, put your item display code in the ItemTemplate, and add a second "empty" repeater :
<body>
<form id="form1" runat="server">
<div>
<asp:Repeater runat="server" ID="ParentRepeater" OnItemDataBound="Repeater_ItemDataBound">
<ItemTemplate>
<asp:Literal runat="server" Text="<%# Eval("Name") %>"></asp:Literal>
<asp:Repeater runat="server" ID="ChildRepeater" OnItemDataBound="Repeater_ItemDataBound" Visible="false">
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
</div>
</form>
</body>
And in the code-behind :
protected void Repeater_ItemDataBound(object sender, RepeaterItemEventArgs e) {
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem) {
DataClass currentItem = (DataClass)e.Item.DataItem;
if (currentItem.Children.Count > 0) {
Repeater ChildRepeater = (Repeater)e.Item.FindControl("ChildRepeater");
ChildRepeater.DataSource = currentItem.Children;
ChildRepeater.ItemTemplate = ParentRepeater.ItemTemplate;
ChildRepeater.Visible = true;
ChildRepeater.DataBind();
}
}
}

Dynamically accessing array variables from Code Behind in ASPX

Due to the nature of the current file system I am working with I have to dynamically access array variables in ASPX page from the code-behind page. I got it to the point where I am able to pull any one explicitly stated variable, but cannot seem to pull them dynamically.
Code Behind:
public partial class some_class : System.Web.UI.Page {
public string[] array123 = new string[100];
....
protected void Button1_Click(object sender, EventArgs e) {
someFunction();
}
protected void someFunction() {
int i = 1;
_TempDt = Locator._New_Locator(value)
foreach (DataRow _TempDR in _TempDt.Rows) {
array123[i] = Server.UrlEncode(address);
i++;
}
}
}
ASPX:
....
<asp:Repeater ID="DataList" runat="server">
<ItemTemplate>
<label onClick="javascript:popup('page.aspx?key=<%= array123[1] %>')">Get link</label>
</ItemTemplate>
</asp:Repeater>
This only pulls the stated (2nd) value in the array and it needs to be dynamic with the repeater.
Thanks.
I think this is what you are looking for:
<p><%# array123[Container.ItemIndex] %></p>
Try this:
< %#DataBinder.Eval(Container, "ItemIndex", "")%>

ASP.Net Databinding inside a bound control

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();
}
}

How to Combine TemplateFields in ASP.Net GridView?

Everyone.
I have a question when combining cells in GridView. I konw how to combine BoundField cells, but I do not know how to combine TemplateField cells in asp.net GridView.
EDIT:
Perhaps I did not make my question clearly and I am sorry about that. My question is that I use GridView to bind data from db and There is a field named UserName, one user has several records in the db, so I want to combine UserName in one cell(i can combine it correctly). In the same way, I want to do some operation to this user such as Add, Delete. So i put these operations into TemplateField, but i do not konw how to combine TemplateField like BoundField. I have a low reputation, so i can not post images
Any good ideas?
Sorry for my poor english! Thanks in advance.
There isn't a way to merge/combine fields. I think you misunderstood the BoundFields/TemplateField. However you can use Eval() and Bind() to show/bind one or more expression into single cell//column..
Read MSDN articles:
BoundField
TemplateField
EDIT:
#loren : There is a field named UserName, one user has several records
in the db.
I guess you need to use "nested" grid or you may also use any data control (formview, detail view or ListView).
Here is demo that shows how to bind nested data controls.
I've define two classes - Contact, Info
public class Contact
{
public string Address{get;set;}
public string Phone {get;set;}
}
public class Info
{
public string UserName {get;set;}
private List<Contact> _addr=new List<Contact>();
public List<Contact> Address
{
get { return _addr; }
set { _addr = value; }
}
}
In .aspx page (Markup),
<asp:GridView
ID="GridView1"
runat="server"
AutoGenerateColumns="false"
onrowdatabound="GridView1_RowDataBound"
>
<Columns>
<asp:TemplateField>
<ItemTemplate>
<p>
Username :
<asp:Literal
ID="UserName"
runat="server"
Text='<%#Eval("UserName") %>'
>
</asp:Literal>
</p>
<asp:GridView
ID="GridView2"
runat="server"
AutoGenerateColumns="false"
>
<Columns>
<asp:TemplateField>
<ItemTemplate>
<p>Phone :
<asp:TextBox
runat="server"
ID="txtDetail"
Text='<%#Bind("Phone") %>'
></asp:TextBox>
</p>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
and in code-behind (aspx.cs),
List<Info> info;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
info = new List<Info>()
{
new Info()
{
UserName="User1",
Address =
{
new Contact() { Phone="2929927", Address="Address1"},
new Contact() { Phone="2929928", Address="Address2"},
}
},
new Info()
{
UserName="User2",
Address =
{
new Contact() { Phone="2929929", Address="Address3"},
new Contact() { Phone="2929930", Address="Address4"},
}
},
};
GridView1.DataSource = info;
GridView1.DataBind();
}
}
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
Literal username=(Literal)e.Row.FindControl("UserName");
GridView view=(GridView)e.Row.FindControl("GridView2");
if (view != null)
{
var result = from ele in info
from add in ele.Address
where ele.UserName == username.Text
select add;
view.DataSource = result;
view.DataBind();
}
}
If possible, you can update the source type.
For example, if you an object data source and a model like this :
[DataObject]
public class MyOds{
[DataObjectMethod(DataObjectMethodType.Select)]
public ICollection<MyModel> GetMyModels()
{
var result = new ListMyModel();
Populate(result); // load model with any method of you choice
return result;
}
}
public class MyModel{
public string Address { get; set; }
public string City { get; set; }
public string Country { get; set; }
public string FullAddress {
get
{
return string.Format("{0} - {1} - {2}", Address, City, Country);
}
}
}
Note the FullAddress property. The idea is to build, for the view, properties easy to exploit. The ASPX can looks like this :
<asp:TemplateField HeaderText="Header">
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text="Label">
<%# Eval("FullAddress")%>
</asp:Label>
</ItemTemplate>
</asp:TemplateField>
you can use Eval as bellow
<asp:TemplateField HeaderText="Header">
<ItemTemplate>
<asp:TextBox runat="server" ID="txt1" Text='<%#Bind("Phone") %>'></asp:TextBox>
<asp:TextBox runat="server" ID="txt2" Text='<%#Bind("Address") %>'></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>

Getting data from a checkbox inside a template column of asp.net gridview

This seems like something simple, but I can't seem to figure it out! I'm trying to get 2-way data-binding to work on an ASP.net page with a check box as one of the columns. How do I get the updated values (from check boxes) back from the gridview ?????
Here is my data type:
[Serializable]
public class UserRequirements
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string UserId { get; set; }
public string Email { get; set; }
public bool ThingRequired { get; set; }
}
My markup looks something like this:
<form id="form1" method="post" runat="server" >
<asp:GridView ID="UserTable" runat="server" AutoGenerateColumns="false" >
<Columns>
...
<asp:TemplateField HeaderText="Required ?">
<ItemTemplate>
<asp:CheckBox id="chkBox1" runat="server" on
Text ="Required"
checked='<%# DataBinder.Eval(Container.DataItem,"ThingRequired") %>'>
</asp:CheckBox>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<asp:Button id="thebutton" Text="Save Changes" OnClick="UpdateRequirements" runat="server" CausesValidation=false />
</form>
My code behind looks something like this:
List<UserRequirements > _userList = new List<UserRequirements >();
protected void Page_Load(object sender, EventArgs e)
{
_userList = data_layer.GetUserRequirments();
this.UserTable.DataSource = _userList;
this.UserTable.DataBind();
}
Eventually, I will call something like this, but I don't know where this should go or how to get the values back from the gridview:
void UpdateRequirements(object sender, EventArgs e)
{
_userList = ???????????? // How do I get the data?
data_layer.UpdateUserRequirements( _userList );
}
foreach (GridViewRow di in GridView1.Rows)
{
HtmlInputCheckBox chkBx = (HtmlInputCheckBox)di.FindControl("chkBox1");
if (chkBx != null && chkBx.Checked)
{
/// put your code here
}
}
try something like this to get the value on change:
protected void OnCheckedChanged(object sender, EventArgs e)
{
CheckBox c = (CheckBox)sender as CheckBox;
string checkBoxId = c.ID;
bool checkBoxValue = c.Checked;
//update database
}
[EDIT]
If you want to get all the values from the rows in the grid in one go, you will need to bind the checkboxes using the Id for the row or item in your list of UserRequirements, so in your grid do something like this:
<asp:CheckBox ID="<%# Eval('Id') %>" />
then on postback, iterate through the items in the UserRequirements list matching the object/item Id with the Ids of the checkboxes in the grid .. something like this:
foreach (UserRequirement item in Requirements)
{
Control c = grid.FindControl(item.Id);
CheckBox cbx = c as CheckBox;
if (cbx != null)
{
bool value = cbx.Checked;
//update db
}
}
Note: you may need to use FindControl recursively to search child controls, or do a foreach on each GridViewRow object in the grid to pickup the checkbox you are looking for.

Resources