So I have this checkbox inside a gridview, and it is working properly for the most part. However, I am trying to implement column-sorting to the grid, and it is causing an issue with the checkbox. When I do my initial fetch from the database, it populates the checkboxes properly, but when I click on a column to sort by it, all of my checkboxes get cleared out.
It seems that this is a problem with the Databind being done by the gridview, but I'm not sure what I'm doing wrong. My research into the issue makes me feel like I have it right, but I don't know for sure (legacy code: I hate it).
<asp:GridView ID="UserListGrid" runat="server"
OnSorting="UserListGrid_Sort" AllowSorting = "True" AutoGenerateColumns="False"
AllowPaging="True" PageSize="25" OnRowDataBound="UserListGrid_RowDataBound"
OnPageIndexChanging="UserListGrid_PageIndexChanging">
.
.
.
<ItemTemplate>
<asp:CheckBox ID="ActiveCheck" runat="server" SortExpression="ActiveCheck"/>
</ItemTemplate>
.
.
.
protected void UserListGrid_Sort(object sender, GridViewSortEventArgs e)
{
// ViewState["CurTab"] = 0;
DataTable Data = myData.GetSessionRoster(TeamID);
DataView UserListView = new DataView(Data);
ViewState["SortDirection"] = myData.ConvertSortDirectionToSql(ViewState["SortDirection"] == null ? "" : ViewState["SortDirection"].ToString());
UserListView.Sort = e.SortExpression + " " + ViewState["SortDirection"];
UserListGrid.DataSource = UserListView;
UserListGrid.DataBind();
}
Is there anything obviously wrong with my sorting? I can provide more code if needed.
As requested, here is UserListGrid_RowDataBound
protected void UserListGrid_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType != DataControlRowType.DataRow)
{
return;
}
// See which users are active
CheckBox ActiveCheck = (CheckBox)e.Row.Cells[1].FindControl("ActiveCheck");
if (ActiveCheck != null)
{
ActiveCheck.Enabled = true;
if (e.Row.Cells[11].Text.Equals("1") && !Page.IsPostBack)
{
ActiveCheck.Checked = true;
ActiveCheck.DataBind();
}
}
}
It looks like you're populating checkboxes not from the data base, but corresponding to the contents of your cell 11 (whatever it is).
I appeal to this code line:
if (e.Row.Cells[11].Text.Equals("1") && !Page.IsPostBack)
While sorting you rebind your gridview, but you restrict populating and rebinding of your checkboxes on postback. This could be a reason for checkboxes loosing values.
I do not see where you are saving data of your checkBoxes. However, the good approach is to save all changes made by the end-user at every request to the server. As far as I see, the data stored in the DataTable. So, generally, you should browse through all grid rows, find CheckBoxes in them and save their values in this table. This should be done in the Page_Load method. This is necessary for the code in the UserListGrid_RowDataBound event handler to work properly.
Related
Apparently something is lost when a change is made to my RadioButtonList and the GridView is sorted. I guess this is a post back.
<asp:GridView ID="GridView1"
datasourceid="CoffeeDS"
runat="server"
autogeneratecolumns="False"
allowsorting="True">
<Columns>
<asp:BoundField DataField="Name" HeaderText="Name" SortExpression="Name" />
<asp:BoundField DataField="Brand" HeaderText="Brand" SortExpression="Brand" />
<asp:BoundField DataField="Roast" HeaderText="Roast" SortExpression="Roast" />
<asp:BoundField DataField="Type" HeaderText="Type" SortExpression="Type" />
<asp:BoundField DataField="Description" HeaderText="Description" SortExpression="Description" />
<asp:BoundField DataField="RowColor" HeaderText="RowColor" Visible="false" />
</Columns>
</asp:GridView>
protected void Page_Load(object sender, EventArgs e)
{
AddLineColor();
}
private void AddLineColor()
{
GridView1.DataKeyNames = new string[] { "RowColor" };
for (int i = 0; i< GridView1.Rows.Count; i++)
{
GridView1.Rows[i].BackColor = Color.FromName(GridView1.DataKeys[i]["RowColor"].ToString());
}
}
protected void RadioButtonList1_SelectedIndexChanged(object sender, EventArgs e)
{
GridView1.Sort(RadioButtonList1.SelectedValue, SortDirection.Ascending);
AddLineColor();
}
When the page loads the colored rows appear as expected, however when SelectedIndexChanged runs the data sorts correctly but no colors appear. I thought that resetting DataKeyNames would do the trick but no joy. It seems the hidden field just evaporates.
I am coming from vb.net and WinForms so this is doubly frustrating.
This is plain old asp.net as I chose to host on GoDaddy for practice. They do a bit of core but I haven't figured out what version yet.
Ok, first up?
In Vb6, vb.net, ms-access, even FoxPro - and just about any desktop enviroment?
We setup the grid, the controls etc. on FIRST page load. (we always did - on load event).
Now, in asp.net? Yes, we quite much do the same. But there is some ugly grinds in the coffee that we have to be aware of:
Every button, every event, every click? The page load event fires EACH time again!!!
So, that means our page load event is "sort of" what we were used to in the page, but it fires EVERY time!!!
So, that means to load up the grid? Well, ok, we put our code in the page load event. But we want to think, to play, to code like we always did? And this desire is not only due to how we coded in the past, but that we need/want/should have the same concept on page load - the FIRST page load.
So, you want to always - without thought - ALWAYS code a REAL page load event - one that only runs the first time.
And you do that with this on the page load event:
If Not IsPostBack Then
End if
or if we going with c#, then:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPost Back)
{
AddLineColor();
}
}
that way, our grid load fires one time. Remember, you touch + click ANY button on that page with a post-back? The page load event fires. So it not really our lovable page load like you used since your first day of coding! But, by a simple test for IsPostBack = False, then the code inside of that REALLY does become our true page load event like our minds have coded for "years" without thinking.
So, put in that real page load code stub.
And I would consider writing code to fill out that GridView in place of the wizards and it then inserting a "data source" into the markup. It not a huge deal.
So, if the RadioButton has a auto post back? then on-load will fire, your custom format routine will fire, and THEN teh code stub event for your radio button will fire.
So, if you want the formatting to trigger/run when you change/set a raadio button (assuming auto post-back), then you need to call the color formatting routine AFTER again (in the RadioButtion index changed event.
However, given the typical flow of code in a asp.net web page?
I would move your formatter to the row data bound event.
I OFTEN will drop in a grid view. Often will use the wizard to setup and create the grid view. I then delete the datasource on the page, remove the gridview setting for that data source (DataSourceID = "sqlDataSource1"), and then code out the data load myself.
I do this for several reasons - and one is to gain control over the data loading process, only have it load ONE time on now what we call our REAL page load event.
DataKeys:
I not 100% sure - but it looks like (seems like) you using DataKeys for somthing other then what they are for. DataKeyes are for the PK row ID. This allows you to toss up a nice grid, but NOT have to expose/have the PK row id in the markup for the grid control. In other words, I would not re-purpose it for some other use.
Of course the common issue then crops up: How do I deal with and have columns from the database in my code that I do NOT want to dispay on the grid, but I need for foramtting, maybe some calulation. Or maybe just that you click on a row and then want to jump to a page to edit/show the details for that one record. So the DataKeys list is for holding the PK value - but not having it displayed in the markup, or the PK having to be a column of data for display.
So, it not quite clear your use of Datakeys here?
But, if you looking to format/change color or whatever in that grid? I would move your color format routine you have to the row data bound event. This means a code change, but that event is some what better, since during the row data bound event, you have FULL USE of the actual data row used to bind - and that includes columns that you don't display in the markup (grid), but is part of the data source you used.
So, my typical code to bind/load a grid looks like this:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack) {
LoadGrid();
}
}
void LoadGrid()
{
using (SqlCommand cmdSQL = new SqlCommand("SELECT Animal, ImageB from tblAnimals",
new SqlConnection(Properties.Settings.Default.TEST4)))
{
cmdSQL.Connection.Open();
GridView1.DataSource = cmdSQL.ExecuteReader();
GridView1.DataBind();
}
}
I can expand on that issue - but lets just be all warm and happy that we are now coding like we did in the past with a REAL page load event (inside of that "if IsPostBack = False).
In fact, the FIRST thing I do for any web page on page load? I write in that !IsPostback stub - and I can't think of out of 100's of web pages that is missing that code stub - they all have them!
Edit: ----------------------
Ok, so we adopted a data table as per above. Now, lets format each row of the grid. As noted, we will/should/near always/ use the on data bound event. And there are MANY reasons, but one BIG bonus reason is that we have FULL use of the actual data row columns during that bind process. And that INCLUDES columns in the data source that we not going to display in the grid. (but, we sure might want to use some of those non displayed columns for things like formatting the grid).
So, with above code we load the grid. But now lets add the formatting code.
First up? We can't JUST stuff cmdSQL.Execute reader into the grid anymore (becuase I need the full row during biding).
So, the code to load the grid in above now becomes this:
void LoadGrid()
{
using (SqlCommand cmdSQL = new SqlCommand("SELECT * from tblhotels ORDER BY HotelName",
new SqlConnection(Properties.Settings.Default.TEST4)))
{
cmdSQL.Connection.Open();
DataTable rst = New DataTable()
rst.Load(cmdSQL.ExecuteReader());
GridView1.DataSource = rst;
GridView1.DataBind();
}
}
So, yes, we NOW need to fill the grid via table - we can't use reader,, since during data bind I want/need that data.
So, now our formatting code goes in the row data bind event. Lets say we want a blue color for the row for City = "Edmonton". NOTE BEYOND great is that I don't even have to show/have that city in the grid - we have FULL use of the FULL data row. The code in row bind now looks like this:
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
// get the full data row - all columns
DataRowView OneRow = (DataRowView)e.Row.DataItem;
// if the city is Edmonton, format row to blue
// note that CITY IS NOT in the grid view display!!!!
if ((string)OneRow["City"] == "Edmonton")
{
e.Row.BackColor = System.Drawing.Color.AliceBlue;
}
}
}
So note how we are now able to get FULL use of the SAME row used during the bind process.
so, say our markup does NOT have the city, say like this:
<asp:GridView ID="GridView1" runat="server" CssClass="table table-hover"
AutoGenerateColumns="False" OnRowDataBound="GridView1_RowDataBound" >
<Columns>
<asp:BoundField DataField="FirstName" HeaderText="FirstName" SortExpression="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="LastName" SortExpression="LastName" />
<asp:BoundField DataField="HotelName" HeaderText="HotelName" SortExpression="HotelName" />
<asp:BoundField DataField="Province" HeaderText="Province" SortExpression="Province" />
</Columns>
</asp:GridView>
When we run the above code, we now get this:
So, we have full use of all columns during that databind process. Just remember, once data bind has completed, then we DO NOT have use of the GridView1.DataSource, but we certainly do during the bind process, and thus I was able to get "OneRow" as per above.
Ok, as stated. We can't hide a control in the grid with Visible = False. Now for 20+ years using a rather wide varity of desktop tools? We often did that. But, in web land? Nope! - if you set a control.Visible = False, it not only hides the control but the asp.net redering engine will ALSO skip that control! It does not exist in the markup. In fact, even during itemDatabound, you note that the cell that points to that column is blank (empty). Was surpised that you could even use that control. So as a general rule, you can't use Visible = False for anything more then hiding a control - but if you hide, then as a "general" rule, then you can't use that control and get/grab values from it. It just the way the web works.
So, to hide a control, then Style="display:none" is the general workaround. However, (I did not realize) that boundfield controls do NOT support Style - so, no suggesting to put it else where - it just don't work (my bad, my fault - bad suggestion here).
next up:
As stated, as a general rule, you can after you bind up the whole data grid loop again and do formatting against the grid, however, it MUCH better to do this on the item data bound event. So, to setup extra values, hide/show, set values, and of course setup colors for a single text box, or the whole row? That code belongs in row data bind event. And better yet, it ALSO solves the whole problem of getting at the WHOLE data row set of values. Including often things like the PK, or say your color color - they do NOT have to be part of the markup - but ONLY part of the data source you use to bind the grid.
So, quite much a "good" guess and "good" idea? Do grid formatting in the row data bound event - it again like the visbile sugesting is just a good idea - and this approch means you don't fight the event model, but leverage it to your advantage.
I also don't know what type or thing DataAccess is - but again, not a huge deal, since you do eventually move/get/have a data table to work with.
As a small FYI: A .net datatable has ALWAYS had a built in data view object. This suggests that we really don't need to create a whole new DataView object/instance. But, lets just go along, and create that dataView anyway.
So, we need one routine - it loads the grid.
Say like this:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
LoadGrid();
}
}
void LoadGrid(string MySort = "")
{
// load up our grid
DataTable rst = new DataTable();
rst = DA.DT(); // ????
DataView MyView = new DataView(rst);
MyView.Sort = MySort;
GridView1.DataSource = MyView;
GridView1.DataBind();
}
And, our data bind even to foramt:
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
// get the full data row - all columns
DataRowView OneRow = (DataRowView)e.Row.DataItem;
// fomrat the row based on color
e.Row.BackColor = (string)OneRow["Color"]
}
}
(check the type for color - I don't know if that is a string in your case???)
And the sorting code?
private void SortGrid(int Index)
{
GridView1.DataSource = null;
switch (Index)
{
case 0: //Alphabetical
break;
case 1: //Type
LoadGrid("Type ASC");
break;
case 2: //Brand
LoadGrid("Brand ASC");
break;
case 3: //Roast
LoadGrid("Roast ASC");
etc.
So, remove the color bound field from the grid markup - we don't need it.
As Albert D. Kallal suggested, I added the check for IsPostback. I go rid of the SqlDataSource, datasourceid, and went back to my good old DataTable retrieved from a DataAccess class. I tried the DataKey because it seemed to preserve my hidden RowColor column. It worked for page load but not after. The DataSourceID made the code to sort easy but I lost access to RowColor.
Finally saw the light and got the RowDataBound working. I didn't realize that you could access the underlying datasource in this event but that is exactly what DataItem does. This greatly simplified the code and is more efficient to format in RowDataBound rather than looping through all the rows.
Thanks to Albert D. Kallal for his patience and great explanations.
<asp:GridView ID="GridView1"
runat="server"
autogeneratecolumns="False"
allowsorting="True"
OnRowDataBound="CustomersGridView_RowDataBound">
<Columns>
<asp:BoundField DataField="Name" HeaderText="Name" SortExpression="Name" />
<asp:BoundField DataField="Brand" HeaderText="Brand" SortExpression="Brand" />
<asp:BoundField DataField="Roast" HeaderText="Roast" SortExpression="Roast" />
<asp:BoundField DataField="Type" HeaderText="Type" SortExpression="Type" />
<asp:BoundField DataField="Description" HeaderText="Description" SortExpression="Description" />
</Columns>
</asp:GridView>
public partial class _Default : Page
{
private DataAccess DA = new DataAccess();
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
LoadGrid();
}
}
protected void RadioButtonList1_SelectedIndexChanged(object sender, EventArgs e)
{
SortGrid(RadioButtonList1.SelectedIndex);
}
private void SortGrid(int Index)
{
GridView1.DataSource = null;
switch (Index)
{
case 0: //Alphabetical
LoadGrid();
break;
case 1: //Type
LoadGrid("Type ASC");
break;
case 2: //Brand
LoadGrid("Brand ASC");
break;
case 3: //Roast
LoadGrid("Roast ASC");
break;
default: //Alphabetical
LoadGrid();
break;
}
}
protected void GridView1_RowDataBound(Object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
//DataItem gets underlying data object
DataRowView OneRow = (DataRowView)e.Row.DataItem;
e.Row.BackColor = Color.FromName(OneRow["RowColor"].ToString());
}
}
void LoadGrid(string MySort = "")
{
DataTable dt = new DataTable();
dt = DA.DT;
DataView MyView = new DataView(dt);
MyView.Sort = MySort;
GridView1.DataSource = MyView;
GridView1.DataBind();
}
}
The DataAccess class...
public class DataAccess
{
public DataAccess()
{
FillCoffeeData();
}
public DataTable DT { get; set; }
private string ConStr = "*****"
private void FillCoffeeData()
{ //0 1 2 3 4 5
string sql = #"Select Coffees.[Name], Roasters.[Name] As Brand, Coffees.Roast, Coffees.[Type], Coffees.[Description], RowColor
FROM Coffees
INNER JOIN Roasters ON Coffees.RoasterID = Roasters.ID
Order By Coffees.[Name], Roasters.Name";
DT = new DataTable();
using (SqlConnection cn = new SqlConnection(ConStr))
using (SqlCommand cmd = new SqlCommand(sql,cn))
{
cn.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
DT.Load(reader);
}
}
}
}
i am using entity frame work and want to bind data on Grid View but facing problem i have code that i am pasting as well as attaching screen shot i also saw answer regarding this problem but not beneficial for me so any one have experience with this error must be appreciated.
aspx.cs Code
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
BindGrid();
}
lblMessage.Text = "";
}
void BindGrid()
{
using (GapEntities1 context = new GapEntities1())
{
if (context.Organizations.Count() > 0)
{
// GdvOrganization is a gridview ID name
GdvOrganization.DataSource = context.Organizations;
GdvOrganization.DataBind();
}
}
}
Seems like you are trying to bind the GridView from Markup side (.aspx ) as well as using code behind.( .aspx.cs )
Choose any one way only to bind the grid.
1.) If you bind gridview from code behind then remove the DataSourceId property from grid view from markup. Change below code:
<asp:gridview id="GdvOrganization" runat="server" autogeneratecolumns="False"
DataSourceID="MyDataSource">
to
<asp:gridview id="GdvOrganization" runat="server" autogeneratecolumns="False">
2.) if you prefer to bind from markup side then you have to remove the c# code to bind the grid.
Still if above 2 steps doesn't interest you, try below trick ( Recommended ?? )
GdvOrganization.DataSource = ds;
GdvOrganization.DataSourceID = String.Empty;
GdvOrganization.DataBind();
I´ve read most posts here but i can´t figure out why the "CheckedChanged" Event is not firing. Here is my situation.
I´m using a Repeater to generate Items out of a Database. Each ReapeaterItem should include an UpdatePanel, because i have to Update the Controls inside the UpdatePanel and do not want to reload the complete page. Inside these dynamically generated UpdatePanels (each RepeaterItem has one) i´m adding up to three Checkboxes dynamically (based on the Database). These Checkboxes need to fire the "CheckedChanged" event, because on some conditions i want to enable/disable/check/uncheck Checkbox1, 2 or 3 based on business logic. ... Hope you got this so far. I´m adding all Controls and have the EventHandler Added. But the generated Code does not reflect the Event Handler. I tried OnItemDataBound, OnItemCreated, PreRender, ... Events to add the Eventhandler too, but i was not able to find the CheckBox-Control with the ID.
I´m totally lost with this and on the way to use Buttons instead of Checkboxes. From what i read so far is that with Buttons i can use the CommandName from the Button and the ItemCommand-Event from the Repeater to get a workaround, but then i need to reflect the "Check" on the Page in some way.
btw, every Repeater (8) sits inside an ajaxtoolkit-accordion control.
Here i give you some Code:
aspx-Page
<asp:Repeater ID="RepeaterAccordionPane2" runat="server">
<ItemTemplate>
HTML Stuff<%# DataBinder.Eval(Container.DataItem, "Header")%>HTML Stuff<%# DataBinder.Eval(Container.DataItem, "Beschreibung")%></td>
<asp:UpdatePanel ID="UpdatePanel2" runat="server" UpdateMode=Conditional>
<ContentTemplate>
</ContentTemplate>
</asp:UpdatePanel>
HTML Stuff
</ItemTemplate>
</asp:Repeater>
Here is the Page_Load Part
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
dvAlleArtikel = new System.Data.DataView(...Database...);
[... some other code here ...]
RepeaterAccordionPane2.DataSource = dvAlleArtikel;
//RepeaterAccordionPane2.ItemCreated +=new RepeaterItemEventHandler(RepeaterAccordionPane2_ItemCreated);
//RepeaterAccordionPane2.PreRender +=new EventHandler(RepeaterAccordionPane2_PreRender);
RepeaterAccordionPane2.DataBind();
int nUpdatePanelIndex = 0;
foreach (Control crInRepeater in RepeaterAccordionPane2.Controls)
{
if (crInRepeater.GetType() == typeof(RepeaterItem))
{
foreach (Control crInRepeaterItem in crInRepeater.Controls)
{
if (crInRepeaterItem.GetType() == typeof(UpdatePanel))
{
LiteralControl litTabelleBeginn = new LiteralControl("<table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"2\">");
((UpdatePanel)crInRepeaterItem).ContentTemplateContainer.Controls.Add(litTabelleBeginn);
if (dvAlleArtikel[nUpdatePanelIndex]["ArtNr1"].ToString() != "0")
{
CheckBox CheckBox1 = new CheckBox();
CheckBox1.ID = dvAlleArtikel[nUpdatePanelIndex]["ArtNr1"].ToString();
CheckBox1.Text = (dvAlleArtikel[nUpdatePanelIndex]["CheckBoxLbl1"].ToString() == "" ? "leer" : dvAlleArtikel[nUpdatePanelIndex]["CheckBoxLbl1"].ToString());
CheckBox1.AutoPostBack = true;
CheckBox1.CheckedChanged +=new EventHandler(CheckBox1_CheckedChanged);
LiteralControl litNeueTabellenZeileBeginn = new LiteralControl("<tr><td width=10><img src=\"images/helper/spacer.gif\" width=\"10\"></td><td height=\"20\">");
LiteralControl litNeueTabellenZeileEnde = new LiteralControl("</td><td width=\"100\" height=\"20\">" + dvAlleArtikel[nUpdatePanelIndex]["ArtPrice1"].ToString() + " € </td></tr>");
((UpdatePanel)crInRepeaterItem).ContentTemplateContainer.Controls.Add(litNeueTabellenZeileBeginn);
((UpdatePanel)crInRepeaterItem).ContentTemplateContainer.Controls.Add(CheckBox1);
((UpdatePanel)crInRepeaterItem).ContentTemplateContainer.Controls.Add(litNeueTabellenZeileEnde);
}
[... some other code here...]
LiteralControl litTabelleEnde = new LiteralControl("</table>");
((UpdatePanel)crInRepeaterItem).ContentTemplateContainer.Controls.Add(litTabelleEnde);
nUpdatePanelIndex++;
}
}
}
}
This code is never reached:
protected void CheckBox1_CheckedChanged(object sender, EventArgs e)
{
int foo = 0;
}
This is the CheckBox-Code generated:
<input id="AccordionPane2_content_RepeaterAccordionPane2_ctl00_6200" type="checkbox" name="AccordionPane2_content$RepeaterAccordionPane2$ctl00$6200" onclick="javascript:setTimeout('__doPostBack(\'AccordionPane2_content$RepeaterAccordionPane2$ctl00$6200\',\'\')', 0)" />
The Event is generated, but when i click the Checkbox all Content in the UpdatePanel is gone and the CheckedChanged-EventHandler is not fired.
What am i doing wrong?
Thanks to all advice, i´m really stuck.
mk
The first time the page loads you are adding all the checkboxes to the Controls collection, and they get rendered. When you do a postback (through the CheckBox's AutoPostBack) you have a check if(!IsPostBack) that doesn't allow the checkboxes to be added to the Controls collection on the postback. Because of that, you won't see the controls and the page, and when the page lifecycle tries to call the events (which occurs AFTER Page_Load), the controls that created the events are no longer there.
You will need to refactor your Page_Load method so it does two things - 1, regardless of the value of IsPostBack bind the repeaters and create the dynamic controls. 2, if IsPostBack==false, i.e., an initial load, then set the values of the dynamic controls. you don't want to set the values of the dynamic controls when IsPostBack==true because then you will lose the values the user entered.
also, just a note:
if (crInRepeater.GetType() == typeof(RepeaterItem))
can be rewritten as:
if (crInRepeater is RepeaterItem)
I have a page with a table of stuff and I need to allow the user to select rows to process. I've figured out how to add a column of check boxes to the table but I can't seem to figure out how to test if they are checked when the form is submitted. If they were static elements, I'd be able to just check do this.theCheckBox but they are programaticly generated.
Also I'm not very happy with how I'm attaching my data to them (by stuffing it in there ID property).
I'm not sure if it's relevant but I'm looking at a bit of a catch-22 as I need to known which of the checkboxes that were created last time around were checked before I can re-run the code that created them.
Edit:
I've found an almost solution. By setting the AutoPostBack property and the CheckedChanged event:
checkbox.AutoPostBack = false;
checkbox.CheckedChanged += new EventHandler(checkbox_CheckedChanged);
I can get code to be called on a post back for any check box that has changed. However this has two problems:
The call back is processed after (or during, I'm not sure) Page_Load where I need to use this information
The call back is not called for check boxes that were checked when the page loaded and still are.
Edit 2:
What I ended up doing was tagging all my ID's with a know prefix and stuffing this at the top of Form_Load:
foreach (string v in this.Request.Form.AllKeys)
{
if (v.StartsWith(Prefix))
{
var data = v.Substring(Prefix.Length);
}
}
everything else seems to run to late.
I'm going to assume you're using a DataList but this should work with and Control that can be templated. I'm also going to assume you're using DataBinding.
Code Front:
<asp:DataList ID="List" OnItemDataBound="List_ItemDataBound" runat="server">
<ItemTemplate>
<asp:CheckBox ID="DeleteMe" runat="server"/>
<a href="<%# DataBinder.Eval(Container, "DataItem.Url")%>" target="_blank">
<%# DataBinder.Eval(Container, "DataItem.Title")%></a>
</ItemTemplate>
</asp:DataList>
<asp:Button ID="DeleteListItem" runat="server" OnClick="DeleteListItem_Click" ></asp:Button>
Code Behind:
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
LoadList();
}
protected void DeleteListItem_Click(object sender, EventArgs e)
{
foreach (DataListItem li in List.Items)
{
CheckBox delMe = (CheckBox)li.FindControl("DeleteMe");
if (delMe != null && delMe.Checked)
//Do Something
}
}
LoadList();
}
protected void LoadList()
{
DataTable dt = //Something...
List.DataSource = dt;
List.DataBind();
}
protected void List_ItemDataBound(object sender, DataListItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item)
{
string id = DataBinder.Eval(e.Item.DataItem, "ID").ToString();
CheckBox delMe = (CheckBox)e.Item.FindControl("DeleteMe");
if (delMe != null)
delMe.Attributes.Add("value", id);
}
}
}
First, make sure that each Checkbox has an ID and that it's got the 'runat="server"' in the tag.
then use the FindControl() function to find it.
For example, if you're looping through all rows in a GridView..
foreach(GridViewRow r in Gridview1.Rows)
{
object cb = r.FindControl("MyCheckBoxId");
if(r != null)
{
CheckBox chk = (CheckBox)cb;
bool IsChecked = chk.Checked;
}
}
Postback data is restored between the InitComplete event and the PreLoad event. If your checkboxes are not created until later then the checkboxes will play "catch up" with their events and the data will be loaded into the control shortly after it is created.
If this is to late for you then you will have to do something like what you are already doing. That is you will have to access the post data before it is given to the control.
If you can save the UniqueId of each CheckBox that you create then can directly access the post data without having to given them a special prefix. You could do this by creating a list of strings which you save the ids in as you generate them and then saving them in the view state. Of course that requires the view state to be enabled and takes up more space in the viewstate.
foreach (string uniqueId in UniqueIds)
{
bool data = Convert.ToBoolean(Request.Form[uniqueId]);
//...
}
Your post is a little vague. It would help to see how you're adding controls to the table. Is it an ASP:Table or a regular HTML table (presumably with a runat="server" attribute since you've successfully added items to it)?
If you intend to let the user make a bunch of selections, then hit a "Submit" button, whereupon you'll process each row based on which row is checked, then you should not be handling the CheckChanged event. Otherwise, as you've noticed, you'll be causing a postback each time and it won't process any of the other checkboxes. So when you create the CheckBox do not set the eventhandler so it doesn't cause a postback.
In your submit button's eventhandler you would loop through each table row, cell, then determine whether the cell's children control contained a checkbox.
I would suggest not using a table. From what you're describing perhaps a GridView or DataList is a better option.
EDIT: here's a simple example to demonstrate. You should be able to get this working in a new project to test out.
Markup
<form id="form1" runat="server">
<div>
<table id="tbl" runat="server"></table>
<asp:Button ID="btnSubmit" runat="server" Text="Submit"
onclick="btnSubmit_Click" />
</div>
</form>
Code-behind
protected void Page_Load(object sender, EventArgs e)
{
for (int i = 0; i < 10; i++)
{
var row = new HtmlTableRow();
var cell = new HtmlTableCell();
cell.InnerText = "Row: " + i.ToString();
row.Cells.Add(cell);
cell = new HtmlTableCell();
CheckBox chk = new CheckBox() { ID = "chk" + i.ToString() };
cell.Controls.Add(chk);
row.Cells.Add(cell);
tbl.Rows.Add(row);
}
}
protected void btnSubmit_Click(object sender, EventArgs e)
{
foreach (HtmlTableRow row in tbl.Rows)
{
foreach (HtmlTableCell cell in row.Cells)
{
foreach (Control c in cell.Controls)
{
if (c is CheckBox)
{
// do your processing here
CheckBox chk = c as CheckBox;
if (chk.Checked)
{
Response.Write(chk.ID + " was checked <br />");
}
}
}
}
}
}
What about using the CheckBoxList control? I have no Visual Studio open now, but as far as I remember it is a DataBound control, providing DataSource and DataBind() where you can provide a list at runtime. When the page does a postback you can traverse the list by calling something like myCheckBoxList.Items and check whether the current item is selected by calling ListItem.Selected method. This should work.
Add them in an override of the CreateChildControls method of the Page. Be sure to give them an ID! This way they get added to the control tree at the correct time.
IMHO The best way would be to use DataBound Templated Control though, i.e. something like a ListView (in .NET 3.5). then in pageload after postback traverse all items in the databound control and use item.FindControl to get at the actual checkbox.
What I ended up doing was tagging all my ID's with a know prefix and stuffing this at the top of Form_Load:
foreach (string v in this.Request.Form.AllKeys)
{
if (v.StartsWith(Prefix))
{
var data = v.Substring(Prefix.Length);
}
}
everything else seems to run to late.
I'm having a problem dynamically adding columns to a GridView. I need to change the layout -- i.e. the included columns -- based on the value in a DropDownList. When the user changes the selection in this list, I need to remove all but the first column and dynamically add additional columns based on the selection.
I have only one column defined in my markup -- column 0, a template column, in which I declare a Select link and another application specific LinkButton. That column needs to always be there. When the ListBoxSelection is made, I remove all but the first column and then re-add the desired columns (in this sample, I've simplified it to always add a "Title" column). Here is a portion of the code:
RemoveVariableColumnsFromGrid();
BoundField b = new BoundField();
b.DataField = "Title";
this.gvPrimaryListView.Columns.Add(b);
this.gvPrimaryListView.DataBind();
private void RemoveVariableColumnsFromGrid() {
int ColCount = this.gvPrimaryListView.Columns.Count;
//Leave column 0 -- our select and view template column
while (ColCount > 1) {
this.gvPrimaryListView.Columns.RemoveAt(ColCount - 1);
--ColCount;
}
}
The first time this code runs through, I see both the static column and the dynamically added "Title" column. However, the next time a selection is made, the first column is generated empty (nothing in it). I see the title column, and I see the first column to its left -- but there's nothing generated within it. In the debugger, I can see that gvPrimaryListView does indeed still have two columns and the first one (index 0) is indeed a template column. In fact, the column even retains it's width which is set as 165px in the markup below (for debugging purposes).
Any ideas?
<asp:GridView ID="gvPrimaryListView" runat="server" Width="100%" AutoGenerateColumns="false"
DataKeyNames="Document_ID" EnableViewState="true" DataSourceID="odsPrimaryDataSource"
AllowPaging="true" AllowSorting="true" PageSize="10" OnPageIndexChanging="activeListView_PageIndexChanging"
AutoGenerateSelectButton="False" OnSelectedIndexChanged="activeListView_SelectedIndexChanged"
Visible="true" OnRowDataBound="CtlDocList_RowDataBound" Font-Size="8pt" Font-Names="Helvetica">
<Columns>
<asp:TemplateField ShowHeader="false">
<ItemTemplate>
<asp:LinkButton EnableTheming="false" ID="CtlSelectDocRowBtn" runat="server" Text="Select"
CommandName="Select" CssClass="gridbutton" OnClick="RowSelectBtn_Click" />
<asp:ImageButton EnableTheming="false" ID="DocViewBtn" runat="server" ImageUrl="../../images/ViewDoc3.png"
CssClass="gridbutton" CommandName="Select" OnClick="DocViewBtn_Click" />
</ItemTemplate>
<ItemStyle Width="165px" />
</asp:TemplateField>
</Columns>
<EmptyDataTemplate>
<asp:Label ID="Label6" runat="server" Text="No rows found." SkinID="LabelHeader"></asp:Label>
</EmptyDataTemplate>
</asp:GridView>
Just some additional information.
It has nothing to do with the fact that it is the first column but everything to do with the fact that it is a TemplateField. If I put a normal column to the left (in the markup) and shift the TemplateField column to the right, the first column renders fine, and the (now second) TemplateField column disappears.
Another strange thing -- the problem doesn't happen the first postback -- OR THE SECOND -- but it starts on the third postback and then continues for subsequent postbacks. I'm stumped.
I recently conquered silmilar issues with dynamic columns in gridviews, perhaps this will help.
First turn the viewstate off
Second add the columns programatically in a function fired in the oninit event
Lastly I used the following helper class to cause the checkboxes to instantiate when the RowDataBound event kicked off. Yes some of it is hard coded.
Heck here is all the code. Have at it :) Warrenty as is, blah blah blah...
Finally since I am just getting my feet wet DotNet any tips would be appreciated [IE don't rip me too much :) ]. And yes 'borrowed' the initial code from the web somewhere, sorry I cant remember off the top of my head :(
-- Fire this off in protected override void OnInit
private void GridViewProject_AddColumns()
{
DataSet dsDataSet = new DataSet();
TemplateField templateField = null;
try
{
StoredProcedure sp = new StoredProcedure("ExpenseReportItemType_GetList", "INTRANETWEBDB", Context.User.Identity.Name);
dsDataSet = sp.GetDataSet();
if (sp.RC != 0 && sp.RC != 3000)
{
labelMessage.Text = sp.ErrorMessage;
}
int iIndex = 0;
int iCount = dsDataSet.Tables[0].Rows.Count;
string strCategoryID = "";
string strCategoryName = "";
iStaticColumnCount = GridViewProject.Columns.Count;
// Insert all columns immediatly to the left of the LAST column
while (iIndex < iCount)
{
strCategoryName = dsDataSet.Tables[0].Rows[iIndex]["CategoryName"].ToString();
strCategoryID = dsDataSet.Tables[0].Rows[iIndex]["CategoryID"].ToString();
templateField = new TemplateField();
templateField.HeaderTemplate = new GridViewTemplateExternal(DataControlRowType.Header, strCategoryName, strCategoryID);
templateField.ItemTemplate = new GridViewTemplateExternal(DataControlRowType.DataRow, strCategoryName, strCategoryID);
templateField.FooterTemplate = new GridViewTemplateExternal(DataControlRowType.Footer, strCategoryName, strCategoryID);
// Have to decriment iStaticColumnCount to insert dynamic columns BEFORE the edit row
GridViewProject.Columns.Insert((iIndex + (iStaticColumnCount-1)), templateField);
iIndex++;
}
iFinalColumnCount = GridViewProject.Columns.Count;
iERPEditColumnIndex = (iFinalColumnCount - 1); // iIndex is zero based, Count is not
}
catch (Exception exception)
{
labelMessage.Text = exception.Message;
}
}
-- Helper Class
public class GridViewTemplateExternal : System.Web.UI.ITemplate
{
#region Fields
public DataControlRowType DataRowType;
private string strCategoryID;
private string strColumnName;
#endregion
#region Constructor
public GridViewTemplateExternal(DataControlRowType type, string ColumnName, string CategoryID)
{
DataRowType = type; // Header, DataRow,
strColumnName = ColumnName; // Header name
strCategoryID = CategoryID;
}
#endregion
#region Methods
public void InstantiateIn(System.Web.UI.Control container)
{
switch (DataRowType)
{
case DataControlRowType.Header:
// build the header for this column
Label labelHeader = new Label();
labelHeader.Text = "<b>" + strColumnName + "</b>";
// All CheckBoxes "Look Up" to the header row for this information
labelHeader.Attributes["ERICategoryID"] = strCategoryID;
labelHeader.Style["writing-mode"] = "tb-rl";
labelHeader.Style["filter"] = "flipv fliph";
container.Controls.Add(labelHeader);
break;
case DataControlRowType.DataRow:
CheckBox checkboxAllowedRow = new CheckBox();
checkboxAllowedRow.Enabled = false;
checkboxAllowedRow.DataBinding += new EventHandler(this.CheckBox_DataBinding);
container.Controls.Add(checkboxAllowedRow);
break;
case DataControlRowType.Footer:
// No data handling for the footer addition row
CheckBox checkboxAllowedFooter = new CheckBox();
container.Controls.Add(checkboxAllowedFooter);
break;
default:
break;
}
}
public void CheckBox_DataBinding(Object sender, EventArgs e)
{
CheckBox checkboxAllowed = (CheckBox)sender;// get the control that raised this event
GridViewRow row = (GridViewRow)checkboxAllowed.NamingContainer;// get the containing row
string RawValue = DataBinder.Eval(row.DataItem, strColumnName).ToString();
if (RawValue.ToUpper() == "TRUE")
{
checkboxAllowed.Checked = true;
}
else
{
checkboxAllowed.Checked = false;
}
}
#endregion
}
best solution for add dynamic column to grid view (ASP) placed on code project by below address :
please check it out :
http://www.codeproject.com/Articles/13461/how-to-create-columns-dynamically-in-a-grid-view
diningphilanderer.myopenid.com has a similar approach to what I would recommend.
The problem is that you have to rebind the grid each time a postback occurs and consequently you have to rebuild the columns. I like to have a method called BindGrid() that first clears the Columns GridView1.Columns.Clear(); then adds them programatically, then sets the datasource and calls databind. Make sure you have viewstate disabled for the grid and you have autogeneratecolumns = false;
I found this earlier today: TemplateField in a GridView doesn't have its ViewState restored when BoundFields are inserted.
Looks like a bug that Microsoft doesn't plan on fixing, so you'll have to try one of the solutions above. I'm having the same problem -- I have some DataBoundFields and some TemplateFields, and after a postback, the TemplateField based columns lose their controls and data.
I have written a short article on the similar topic that deal with dynamically populating GridView column based on the columns selected by the user in the CheckBoxList control. Hope this will help to those looking for simple demonstration How to generate GridView columns dynamically based on user selection?.
void Page_PreRenderComplete(object sender, EventArgs e)
{
// TemplateField reorder bug: if there is a TemplateField based column (or derived therefrom), GridView may blank out
// the column (plus possibly others) during any postback, if the user has moved it from its original markup position.
// This is probably a viewstate bug, as it happens only if a TemplateField based column has been moved. The workaround is
// to force a databind before each response. See https://connect.microsoft.com/VisualStudio/feedback/details/104994/templatefield-in-a-gridview-doesnt-have-its-viewstate-restored-when-boundfields-are-inserted
//
// This problem is also happening for grid views inside a TabPanel, even if the TemplateField based columns have not
// been moved. Also do a databind in that case.
//
// We also force a databind right after the user has submitted the column chooser dialog.
// (This is because the user could have moved TemplateField based column(s) but ColChooserHasMovedTemplateFields()
// returns false -- ie when the user has moved all TemplateField based columns back to their original positions.
if ((!_DataBindingDone && (ColChooserHasMovedTemplateFields() || _InTabPanel)) || _ColChooserPanelSubmitted || _ColChooserPanelCancelled)
DataBind();
// There is a problem with the GridView in case of custom paging (which is true here) that if we are on the last page,
// and we delete all row(s) of that page, GridView is not aware of the deletion during the subsequent data binding,
// will ask the ODS for the last page of data, and will display a blank. By PreRenderComplete, it will somehow have
// realized that its PageIndex, PageCount, etc. are too big and updated them properly, but this is too late
// as the data binding has already occurred with oudated page variables. So, if we were on the last page just before
// the last data binding (_LastPageIndex == _LastPageCount - 1) and PageIndex was decremented after the data binding,
// we know this scenario has happened and we redo the data binding. See http://scottonwriting.net/sowblog/archive/2006/05/30/163173.aspx
// for a discussion of the problem when the GridView uses the ODS to delete data. The discussion also applies when we
// delete data directly through ClassBuilder objects.
if (_LastPageIndex == _LastPageCount - 1 && PageIndex < _LastPageIndex)
DataBind();
if (EnableColChooser)
{
if (!_IsColChooserApplied)
ApplyColChooser(null, false, false);
else
{
// The purpose of calling ApplyColChooser() here is to order the column headers properly. The GridView
// at this point will have reverted the column headers to their original order regardless of ViewState,
// so we need to apply our own ordering. (This is not true of data cells, so we don't have to apply
// ordering to them, as reflected by the parameters of the call.)
// If we have already processed column reordering upon the column chooser panel being submitted,
// don't repeat the operation.
if (!_ColChooserPanelSubmitted)
ApplyColChooser(null, false, true);
}
}
}
I found this little nugget in the documentation, under the DataControlFieldCollection Class.
If you are using the GridView or DetailsView control, the DataControlField objects that are automatically created (for example, when the AutoGenerateColumns property is true) are not stored in the publicly accessible fields collection. You can only access and manipulate DataControlField objects that are not automatically generated.
I guess the answer is to do all of your column manipulation in code, and then your approach should work fine.
Instead of dynamically adding columns, could you define them at the outset and hide/show them as needed (either with Visible="false" or setting the CssClass of the control/header/footer to a class with "display: none;")? I use this method in some of my code, including template columns, with no issues.
Sorry, Decker. I missed a few key points obviously.. :)
If this is still a problem for you, I wonder if it makes a difference what you have in your item template? If you just put some text in there, then refresh the page a few times, does the text appear on the first load, then not on the second?
Also, when the problem arises is there any html markup at all in the cells or are they completely empty?