I have the following nested listview...
<asp:ListView ID="lvwRiskQuestions" runat="server" ItemPlaceholderID="QuestionItemPlaceholder">
<LayoutTemplate>
<asp:PlaceHolder ID="QuestionItemPlaceholder" runat="server" />
</LayoutTemplate>
<ItemTemplate>
<%# Eval("DESCRIPTION")%>
<asp:ListView ID="lvwAnswers" runat="server" ItemPlaceholderID="AnswerItemPlaceholder" DataSource='<%# Eval("Answers")%>'>
<LayoutTemplate>
<asp:PlaceHolder ID="AnswerItemPlaceholder" runat="server" />
</LayoutTemplate>
<ItemTemplate>
<asp:RadioButton ID="rdbSelect" runat="server" AutoPostBack="true" OnCheckedChanged="rdbSelectChanged"/>
<%# Eval("Description")%>
</ItemTemplate>
</asp:ListView>
</ItemTemplate>
</asp:ListView>
I get hold of the radio buttons OnCheckedChanged like so...
Protected Sub rdbSelectChanged(ByVal sender As Object, ByVal e As System.EventArgs)
Dim rb1 As RadioButton = CType(sender, RadioButton)
Dim lvwAnswers = DirectCast(lvwRiskQuestions.FindControl("lvwAnswers"), ListView)
For Each row As ListViewItem In lvwAnswers.Items
Dim rb As RadioButton = row.FindControl("rdbSelect")
If rb IsNot Nothing AndAlso rb.Checked Then
rb.Checked = False
End If
Next
rb1.Checked = True
End Sub
The problem i have is 'lvwAnswers' is Nothing. I'm guessing im not doing my findcontrol correctly.
Any help greatly appreciated.
If you're just generating a list of radio-buttons for the answers, you could use the RadioButtonList control. This would generate the correct HTML so that only one answer could be selected per question without having to post-back to de-select the other options.
If your answer template contains more than a single RadioButton, things get more complicated. When it's not hosted in a RadioButtonList, the RadioButton uses the UniqueID of the parent NamingContainer to build its unique group name. Unfortunately, in your example, the NamingContainer will be the ListViewDataItem from the lvwAnswers list, and each answer will have a different ID.
What you need is a RadioButton which will look at the NamingContainer's NamingContainer to generate its group name. You could either re-implement the RadioButton control, or use a little bit of reflection to update the private _uniqueGroupName field:
[ToolboxData("<{0}:ListRadioButton runat=\"server\" />")]
public class ListRadioButton : RadioButton
{
private static readonly FieldInfo UniqueGroupNameField = FindUniqueGroupNameField();
private string _uniqueGroupName;
private static FieldInfo FindUniqueGroupNameField()
{
return typeof(RadioButton).GetField("_uniqueGroupName",
BindingFlags.NonPublic | BindingFlags.Instance);
}
protected virtual string CreateUniqueGroupName()
{
string result = GroupName;
if (string.IsNullOrEmpty(result))
{
result = ID;
}
if (string.IsNullOrEmpty(result))
{
result = UniqueID;
}
else
{
Control container = NamingContainer;
if (container != null)
{
if (container is IDataItemContainer)
{
container = container.NamingContainer ?? container;
}
result = container.UniqueID + base.IdSeparator + result;
}
else
{
string uniqueID = UniqueID;
if (!string.IsNullOrEmpty(uniqueID))
{
int index = uniqueID.LastIndexOf(base.IdSeparator);
if (index != -1)
{
result = uniqueID.Substring(0, 1 + index) + result;
}
}
}
}
return result;
}
private void EnsureUniqueGroupName()
{
if (_uniqueGroupName == null)
{
string value = CreateUniqueGroupName();
if (UniqueGroupNameField != null) UniqueGroupNameField.SetValue(this, value);
_uniqueGroupName = value;
value = base.Attributes["value"];
if (string.IsNullOrEmpty(value))
{
base.Attributes["value"] = UniqueID;
}
}
}
protected override bool LoadPostData(string postDataKey, NameValueCollection postCollection)
{
EnsureUniqueGroupName();
return base.LoadPostData(postDataKey, postCollection);
}
protected override void Render(HtmlTextWriter writer)
{
EnsureUniqueGroupName();
base.Render(writer);
}
}
With that control in place and registered using the site prefix, you can change your code to:
<asp:ListView ID="lvwRiskQuestions" runat="server" ItemPlaceholderID="QuestionItemPlaceholder">
<LayoutTemplate>
<asp:PlaceHolder ID="QuestionItemPlaceholder" runat="server" />
</LayoutTemplate>
<ItemTemplate>
<%# Eval("DESCRIPTION") %>
<asp:ListView ID="lvwAnswers" runat="server" ItemPlaceholderID="AnswerItemPlaceholder" DataSource='<%# Eval("Answers")%>'>
<LayoutTemplate>
<asp:PlaceHolder ID="AnswerItemPlaceholder" runat="server" />
</LayoutTemplate>
<ItemTemplate>
<site:ListRadioButton ID="rdbSelect" runat="server"
Text='<%# Eval("Description") %>'
/>
</ItemTemplate>
</asp:ListView>
</ItemTemplate>
</asp:ListView>
In the rendered HTML, the radio-buttons for each question will then have the same name, and you will only be able to select a single answer per question, without having to post the entire page on each selection.
I'd like to point out that this "copy/paste" code doesn't work and was taken from a comment on codeproject (Comment titled Another Option). The original code does work.
Here it is :
using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Reflection;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
[AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
public class SimpleRadioButton : RadioButton
{
private static readonly FieldInfo UniqueGroupNameField = FindUniqueGroupNameField();
private string _uniqueGroupName;
private static FieldInfo FindUniqueGroupNameField()
{
return typeof(RadioButton).GetField("_uniqueGroupName",
BindingFlags.NonPublic | BindingFlags.Instance);
}
protected virtual string CreateUniqueGroupName()
{
string result = this.GroupName;
if (string.IsNullOrEmpty(result))
{
result = this.ID;
}
if (string.IsNullOrEmpty(result))
{
result = this.UniqueID;
}
else
{
Control container = this.NamingContainer;
if (null != container)
{
if (container is IDataItemContainer)
{
container = container.NamingContainer ?? container;
}
result = container.UniqueID + base.IdSeparator + result;
}
else
{
string uniqueID = this.UniqueID;
if (!string.IsNullOrEmpty(uniqueID))
{
int index = uniqueID.LastIndexOf(base.IdSeparator);
if (-1 != index)
{
result = uniqueID.Substring(0, 1 + index) + result;
}
}
}
}
return result;
}
private void EnsureUniqueGroupName()
{
if (null == _uniqueGroupName)
{
string value = this.CreateUniqueGroupName();
if (null != UniqueGroupNameField) UniqueGroupNameField.SetValue(this, value);
_uniqueGroupName = value;
// Make sure we have a value attribute:
value = base.Attributes["value"];
if (string.IsNullOrEmpty(value))
{
base.Attributes["value"] = this.UniqueID;
}
}
}
protected override bool LoadPostData(string postDataKey, NameValueCollection postCollection)
{
this.EnsureUniqueGroupName();
return base.LoadPostData(postDataKey, postCollection);
}
protected override void Render(HtmlTextWriter writer)
{
this.EnsureUniqueGroupName();
base.Render(writer);
}
}
Related
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();
}
}
}
Depending on whichh button is pressed I have to take the excursion_date_id which I will need in the next form!
SO what is best to use - buttonfield property maybe!
here is my code
First it's the Dates.class
public class Dates
{
private int excursionID;
private DateTime startdates;
private double prices;
public int ExcursionID
{
get { return excursionID; }
set { excursionID = value; }
}
public DateTime StartDates
{
get { return startdates; }
set { startdates = value; }
}
public double Prices
{
get { return prices; }
set { prices = value; }
}
public Dates()
{ }
}
}
Then I populate the gridview with the dates from my database
I want to have a button with text property "reserve" at the end of each row of the gridview!
protected void Page_Load(object sender, EventArgs e)
{
string excursionnId = Request.QueryString["ExcursionId"];
Dates date = new Dates();
List<Dates> listDate = new List<Dates>();
listDate = GetDates();
gvDates.DataSource = listDate;
gvDates.DataBind();
}
public List<Dates> GetDates()
{
List<Dates> datesList = new List<Dates>();
string connectionString = "Server=localhost\\SQLEXPRESS;Database=EXCURSIONSDATABASE;Trusted_Connection=true";
string excursionnID = Request.QueryString["ExcursionID"];
string query =
"SELECT Excursion_date_ID, Excursion_ID, Start_date, Price FROM EXCURSION_DATES WHERE EXCURSION_ID='" + excursionnID + "'";
SqlConnection conn = new SqlConnection(connectionString);
SqlCommand cmd = new SqlCommand(query, conn);
try
{
conn.Open();
SqlDataReader rd = cmd.ExecuteReader();
int s=0;
while (rd.Read())
{
Dates dates = new Dates();
dates.ExcursionID = Convert.ToInt32(rd["Excursion_ID"]);
dates.StartDates = Convert.ToDateTime(rd["Start_date"]);
dates.Prices = Convert.ToDouble(rd["Price"]);
datesList.Add(dates);
}
}
catch (Exception EX)
{
}
return datesList;
}
}
<asp:GridView ID="gvDates" runat="server" Width="100%" AutoGenerateColumns="false"
OnRowCommand="grd_RowCommand" >
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:Button ID="btn" CausesValidation="false" CommandName="YourCommandName"
CommandArgument='<%#Eval("Excursion_ID") %>' runat="server" Text="Text" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
protected void grd_RowCommand(object sender, GridViewCommandEventArgs e)
{
string number = (string)e.CommandArgument;
if (number==null) return;
switch (e.CommandName)
{
case "YourCommandName":
Load(number);
break;
// some others command
}
}
OR without rowcommand
<asp:GridView ID="gvDates" runat="server" Width="100%" AutoGenerateColumns="false" >
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:Button ID="btn" CausesValidation="false" OnClick="btnClick"
CommandArgument='<%#Eval("Excursion_ID") %>' runat="server" Text="Text" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
protected void btnClick(object sender, EventArgs e)
{
Button btn=(Button)sender;
if(btn==null) return;
string number = (string)btn.CommandArgument;
if (number==null) return;
Load(number);
}
In your .aspx page, specify the id of the GridView button to the ExcursionID. You can use buttonfield or place a button in a template column for that.
I have a image datatype which on click of a linkbutton needs to be displayed on a seperate page
aspx page
detail view:
<asp:TemplateField HeaderText="Evidence (if any)">
<ItemTemplate>
<asp:LinkButton ID="lbEvidence" runat="server"
Text='<%# DataBinder.Eval(Container.DataItem, "Evidence").ToString() == String.Empty ? "None" : DataBinder.Eval(Container.DataItem, "Evidence")%>'
CommandName="Select" CommandArgument = '<%# DataBinder.Eval(Container.DataItem, "Complaint_Id") %>'> </asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
ImageHandler.ashx
<%# WebHandler Language="C#" Class="FMMadminModule.imageHandler" %>
using System;
using System.Web;
using System.Data;
using System.Web.SessionState;
namespace FMMadminModule
{
public class imageHandler : IHttpHandler, IReadOnlySessionState
{
DataTable dt;
int key;
byte[] imageOut;
public void ProcessRequest(HttpContext context)
{
HttpResponse response = context.Response;
HttpRequest request = context.Request;
context.Response.ContentType = "image/jpeg";
response.BufferOutput = false;
// get the key, the index into the DataTable
key = Convert.ToInt32(request.QueryString["Complaint_ID"]);
// Prepare the datatable to hold the SNo key and the jpeg image, which will be written out
dt = new DataTable();
dt = (DataTable)context.Session["dt"];
if (!dt.Rows[key]["Evidence"].Equals(null))
{
imageOut = (byte[])dt.Rows[key]["Evidence"];
response.OutputStream.Write(imageOut, 0, imageOut.Length);
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
}
How would I display the image on a separate page?
This is how aspx.cs looks like
protected void dvResolveComplaint_ItemCommand(object sender, DetailsViewCommandEventArgs e)
{
if (e.CommandName == "Select")
{
DetailsViewRow row = dvResolveComplaint.Rows[5];
//String RowId = (e.NewSelectedIndex).ToString();
Type csType = this.GetType();
String strScript = "<script> ";
strScript += #"var newWindow = window.open('imageHandler.ashx?Complaint_ID=" + e.CommandArgument + #"', 'Evidence', ' height=450, center:yes, width=600, status=no, resizable= yes, menubar=no, toolbar=no, location=yes, scrollbars=no, status=no')";
strScript += "</script>";
ClientScript.RegisterClientScriptBlock(csType, "ViewEvidence", strScript);
dvResolveComplaint.Attributes.Add("OnClick", strScript);
}
}
this is way overcomplicated. You don't need the linkbutton at all, just write an anchor-wrapped image in your item template and call it a day, e.g.:
<img src='url_to_your_handler' alt='complaint image' />
I am having trouble displaying my object in a datalist.
My object loks like this:
public class Course
{
private string courseName;
private DateTime[] courseDates;
public string CourseName
{
get { return courseName; }
set { courseName = value; }
}
public DateTime[] CourseDates
{
get { return courseDates; }
set {
foreach (DateTime dt in courseDates)
{
if (dt < DateTime.Now)
throw new Exception("The date of the course has to be after todays date.");
}
courseDates = value; }
}
public Course(string _courseName, DateTime[] _courseDates)
{
courseName = _courseName;
courseDates = _courseDates;
}
}
When I try to display it in a datalist the name of the course looks ok. But The dates are not shown.
So I was thinking I needed a nested datalist but then I don't know how to fill the second datalist with the dates in the object.
I've show two solutions:
<asp:DataList ID="DataList1" runat="server"
onitemdatabound="DataList1_ItemDataBound">
<ItemTemplate>
<td><%# DataBinder.Eval(Container.DataItem, "CourseName")%></td>
<td><%# RenderDates( DataBinder.Eval(Container.DataItem, "CourseDates"))%></td>
<td>
<asp:DataList ID="DateList" runat="server">
<ItemTemplate>
<td><%# Container.DataItem%></td>
</ItemTemplate>
</asp:DataList>
</td>
</ItemTemplate>
</asp:DataList>
and
public string RenderDates(object dates)
{
DateTime[] ds = dates as DateTime[];
StringBuilder sb = new StringBuilder();
if( ds != null && ds.Length > 0)
{
sb.Append(ds[0]);
for (int i = 1; i < ds.Length; i++)
{
sb.AppendFormat(" - {0}", ds[i]);
}
}
return sb.ToString();
}
protected void DataList1_ItemDataBound(object sender, DataListItemEventArgs e)
{
DataListItem item = e.Item;
if ((item.ItemType == ListItemType.Item) ||
(item.ItemType == ListItemType.AlternatingItem))
{
var innerList= (DataList)item.FindControl("DateList");
innerList.DataSource = ((Course) item.DataItem).CourseDates;
innerList.DataBind();
}
}
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.