Dynamically adding a Button to a PlaceHolder in a DataGrid - asp.net

I have basically something like this:
<asp:datagrid id="DGrid" runat="server" AutoGenerateColumns="false">
<asp:TemplateColumn HeaderText="Stuff">
<ItemTemplate>
<asp:PlaceHolder id="PH" runat="server" />
</ItemTemplate>
</asp:TemplateColumn>
</asp:datagrid>
I need to add a Button to the PlaceHolder depending on the values of the data I am binding. At the moment I am adding the Button after the data is bound in Page_Load. The problem is that the data binding methods are not called in postbacks, so the Button disappears when I click on it.
Any suggestions on how to do this? Problem is that I need to know some attributes of the grid item to create the Button, so I cannot create it before the data has been bound.

How about subscribing to ItemCreated event?
Markup:
<asp:datagrid id="DGrid" runat="server" OnItemCreated="DGrid_ItemCreated" AutoGenerateColumns="false">...</asp:DataGrid>
Code-behind:
protected void DGrid_ItemCreated(object sender, DataGridItemEventArgs e)
{
var ph e.Item.FindControl("PH") as PlaceHolder;
// ...
}
UPDATE
Regarding the situation when the e.Item.DataItem is null on a postback: only the reliable information (e.g. databound control properties) is persisted across postbacks (if ViewState is enabled), the entire data items don't survive them. Therefore you have to manage the state by yourself. You can persist only the necessary data in a ViewState (and not the entire data items since it can blow it up).

Related

Why do my dynamically added controls loose their values after Postback?

To ask my question I have created an aspx file containing a Button and a DataList with an SqlDataSource:
<asp:Button ID="Button1" runat="server" Text="Button" onclick="Button1_Click" />
<asp:DataList ID="DataList1" runat="server" DataKeyField="a"
DataSourceID="SqlDataSource1" >
<ItemTemplate>
a:
<asp:Label ID="aLabel" runat="server" Text='<%# Eval("a") %>' />
<br />
b:
<asp:Label ID="bLabel" runat="server" Text='<%# Eval("b") %>' />
<br />
</ItemTemplate>
</asp:DataList>
<asp:SqlDataSource ID="SqlDataSource1" runat="server"
ConnectionString="<%$ ConnectionStrings:probaConnectionString %>"
SelectCommand="SELECT [a], [b] FROM [PROBA_TABLE]"></asp:SqlDataSource>
In my code behind I add TextBoxes to the Items of the DataList. I add to every Item a TextBox in the Page_Load, and another TextBox in the Button Click eventhandler as well.
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
foreach (DataListItem item in DataList1.Items)
{
item.Controls.Add(new TextBox());
}
}
}
protected void Button1_Click(object sender, EventArgs e)
{
foreach (DataListItem item in DataList1.Items)
{
item.Controls.Add(new TextBox());
}
}
}
}
This works fine except one thing. When I click the Button, the TextBoxes which were created in the Page_Load keep their Text value, but the TextBoxes which were created in the Button1_Click lose their Text values. My real problem is more complicated than this, but I think solving this would help me a lot.
Each control that should receive data from page ViewState should be instantiated in Init or Load event handlers, because ViewState is persisted to controls BEFORE Click, Change and the rest control events (those events are triggered when ViewState changes are detected, so ViewState must be read before Click event is fired).
So the process should look like:
OnInit (static controls get created)
Static control content is deserialized from ViewState
OnLoad (create dynamic controls, in your case textboxes that you created in last Postback)
Dynamic control content is deserialized from ViewState
Click, Change and other events are fired according to changes detected comparing POST data and ViewState data
Suggestions:
You can use hidden fields to save additional status information, and then in OnLoad you can read that info to recreate dynamically created controls.
Also, you should explicitly set ID property of your textboxes so that values can be properly persisted back, don't rely on ASP.Net.
the http by default is stateless that means after your request is processed the server keeps no data or info of the request
but the values in the form need to be persisted in special cases when there is an error
suppose you fill up a long form and then post it back to the server only to get an error message and all the filled up values are gone. wouldn't that be annoying
so what asp.net does behind the scenes that it keeps a string in the page hidden that has information about all the server controls and their ids
so when you post a form back the Page class is created and the values that are posted back and binded in the specific controls because the Page class is being created in every request the pageLoad event is run and controls created in the PageLoad are then present values corresponding to their ids are put into them unlike the controls that are being created on button click till the button_click event is run that viewstate has already been deseralized and values are filled into them

ASP.NET Gridview Templatefield with multiple items

I am creating a web application in ASP.net/VB.NET and I have an issue with the GridView control.
Currently, I have the GridView populated with data from the DB and I've also coded the update button to allow the user to edit any necessary information through a form that pops up.
What I'd like to do, if possible, is add a button to the two right columns(I already put one in the Dock Out Time column) which will be invisible if the column is set or will set the current time to the column. Setting the time for those two columns is already handled through the update form, but my supervisor asked me to try and see if this was possible.
Those two Time columns are TemplateFields(since I format the display time from what is actually in the DB) and I added an asp button in the ItemTemplate for that Set Button in the picture.
Is this even possible to do and if so, how would I access this button in the code behind so I can add functionality(setting the time and hiding it if the column is not null)If it's not really possible to have two items like this in a TemplateField I can just make 2 extra columns for these buttons but I think this would look much cleaner.
Any input would be greatly appreciated. thank you for your time.
Yes this is possible, check this answer:
https://stackoverflow.com/a/11077709/1268570
Basically you need to handle the RowCommand event from the grid and identify each button with a command, optionally you can add arguments to each button when you bind, for example:
<asp:GridView runat="server" OnRowCommand="grdProducts_RowCommand"
ID="grdProducts">
<Columns>
<asp:TemplateField ShowHeader="False">
<ItemTemplate>
<asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="false"
CommandName="myLink" CommandArgument='<%# DataBinder.Eval(Container, "RowIndex") %>' Text="Button"></asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
<asp:CommandField ShowEditButton="True" />
</Columns>
</asp:GridView>
In code behind:
protected void grdProducts_RowCommand(object sender, GridViewCommandEventArgs e)
{
switch (e.CommandName)
{
case "myLink":
this.lblMessage.Text = e.CommandName + " " + e.CommandArgument + " " + DateTime.Now.ToString();
// referenece to the current row
var row = this.grdProducts.Rows[int.Parse(e.CommandArgument.ToString())];
break;
default:
break;
}
}
After you update your grid in the RowCommand event, you should repopulate the grid data to render the changes

Reacting to Button commands inside a Web Usercontrol in a ASP.NET Repeater

I have an ASP.NET repeater, whose ItemTemplate is a WebUsercontrol named ProviderControl.
<asp:Repeater ID="rep" runat="server" OnItemDataBound="rep_ItemDataBound">
<ItemTemplate>
<custom:ProviderControl ID="row" runat="server" />
</ItemTemplate>
</asp:Repeater>
I am populating the custom control with data in the ItemDataBound event.
Inside the provider control I have two buttons that I want to be able to react to on the containing Page.
I know there are Commands, and Command arguments, but how would I do that?
Or is there an easier way than using Commands?
You have to handle ItemCommand event of "Repeater".
protected void Repeater1_ItemCommand(object source, RepeaterCommandEventArgs e)
{
Button btn = e.CommandSource as Button;
Response.Write(btn.ID);
}
The best way would be to provide custom events in your UserControl for each button-click event. The UserControl will raise and the page can handle them.
http://www.codeproject.com/Articles/8797/Mastering-Page-UserControl-Communication#4.3

How to check all check boxes are checked or not in a ASP.NET gridview

How do I find out if all the check boxes in a ASP.NET grid view are checked or not?
Depending on this I have to gray out a button. I have to enable the button depending on all check boxes being checked.
How do I do this and on which event do I have to place my code?
Per your comment on Nick's answer, I see that you prefer to do this on the server-side. My one word of warning here is that in order to accomplish this you would need to set the CheckBox's AutoPostBack property to True, which means each time a user checks or unchecks a checkbox there is going to be a postback. This can lead to a less than optimal user experience.
Server-Side Solution
With that out of the way, here's how you'd do it server-side. First, I presume you have a TemplateField in your GridView that contains a CheckBox? You need to set its AutoPostBack property to True and create a CheckChanged event handler for it. (You can create the event handler by going to the Designer and choosing Edit Templates from the GridView's smart tag. Then, select the Template and double-click the CheckBox.)
Here is my GridView markup for this example. Note the CheckBox's configuration - here, AutoPostBack is set to True and the server-side OnCheckChanged event is wired up to a server-side event handler, chkSelected_CheckChanged:
<asp:GridView ID="gvProducts" runat="server" AutoGenerateColumns="False"
DataKeyNames="ProductID" DataSourceID="dsProducts">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:CheckBox runat="server" ID="chkSelected" AutoPostBack="true"
oncheckedchanged="chkSelected_CheckedChanged" />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="ProductName" HeaderText="Product"
SortExpression="ProductName" />
...
</Columns>
</asp:GridView>
The chkSelected_CheckedChanged event handler my code-behind class, loops through the GridView's rows. For each row I reference the CheckBox (chkSelected) and see if it's checked. If it's not, then I can disable my button (btnDoSomething). If I loop through all GridView rows and none of them are not checked then I know I am to enable my button.
protected void chkSelected_CheckedChanged(object sender, EventArgs e)
{
// Iterate through all of the rows in the grid and see if there is any unchecked CheckBox
foreach (GridViewRow row in gvProducts.Rows)
{
var cb = row.FindControl("chkSelected") as CheckBox;
if (!cb.Checked)
{
btnDoSomething.Enabled = false;
return;
}
}
// If we reach here, all checkboxes are checked, so enable btnDoSomething
btnDoSomething.Enabled = true;
}
Client-Side Solution
The server-side solution is pretty straightforward, but has the drawback of requiring a postback each time a CheckBox is checked/unchecked. I'd highly suggest using a client-side approach. It's actually quite easy using a JavaScript library like jQuery.
Here, the GridView markup is the same as before except the CheckBox does not cause a postback (that is, AutoPostBack is False) and there's no server-side event handler:
<asp:GridView ID="gvProducts" runat="server" AutoGenerateColumns="False"
DataKeyNames="ProductID" DataSourceID="dsProducts">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:CheckBox runat="server" ID="chkSelected" />
</ItemTemplate>
</asp:TemplateField>
...
</Columns>
</asp:GridView>
All the magic happens on the client-side. Presuming you have referenced the jQuery library, your JavaScript code would just be the following:
<script type="text/javascript">
$(document).ready(function () {
$("#<%=gvProducts.ClientID%> input[id*='chkSelected']:checkbox").click(EnableDisableButtonAsNeeded);
EnableDisableButtonAsNeeded();
});
function EnableDisableButtonAsNeeded() {
var totalCheckboxes = $("#<%=gvProducts.ClientID%> input[id*='chkSelected']:checkbox").size();
var checkedCheckboxes = $("#<%=gvProducts.ClientID%> input[id*='chkSelected']:checkbox:checked").size();
if (totalCheckboxes == checkedCheckboxes)
$("#<%=btnDoSomething.ClientID %>").removeAttr('disabled');
else
$("#<%=btnDoSomething.ClientID %>").attr('disabled', 'disabled');
}
</script>
I created a client-side function named EnableDisableButtonAsNeeded that determines how many total checkboxes there are in the grid and how many are checked. If those two numbers are equal then it enabled the button, otherwise it disabled it. This function is called once when the page loads as well as every time a checkbox is checked or unchecked.
Happy Programming!
Use JavaScript. I suggest you read www.w3schools.com, specifcally Checkbox checked Property.
The main thing you want to do is get the "id" of all your check boxes and cycle through them.
It's difficult to tell you exactly where to put the code without knowing some more about your page and how the checkboxes get (un)checked, but the general pattern for doing this check would be something like this:
protected void checkCheckBoxes()
{
bool allCheckBoxesChecked = true;
// Go through each row in the gridview
foreach (GridViewRow row in Gridview1.Rows)
{
// Go through every control in the row
foreach (Control control in row.Controls)
{
// Control is the superclass so we can't tell what type it is
// Best we can do is examine the control ID...
if (control.ID.Contains("CheckBox"))
{
// Cast the control to a CheckBox so we can examine the Checked property
if (((CheckBox)control).Checked == false)
{
// If the CheckBox isn't checked, we set our flag
allCheckBoxesChecked = false;
// Would be nice here to be able to break out of both loops but there's no doublebreak statement in C# :-(
}
}
}
}
// Enable the button based on the value of the flag
Button1.Enabled = allCheckBoxesChecked;
}
This is a similar answer to Scott Mitchell's answer, but will work with multiple checkboxes in the row.

Switching a Repeater's ItemTemplate at runtime

Is it possible to define multiple templates for a Repeater's ItemTemplate and switch between them according to some condition?
I use a repeater to view a list of posts but want to have a different view for rows that belong to the current user (e.g. contains a LinkButton for deleting the post)
If this is not possible, then is it possible to use a Multiview control inside a Repeater's ItemTemplate?
I tried to use a MultiView control inside the ItemTemplate and it worked very well, hope this helps someone with the same problem:
<asp:Repeater ID="Repeater1" runat="server" OnItemCommand="Repeater1_ItemCommand">
<ItemTemplate>
<asp:MultiView ID="MultiView1" runat="server" ActiveViewIndex="<%# ((Post)Container.DataItem).Member.ID == CurrentMemberID ? 1 : 0 %>">
<asp:View ID="View1" runat="server"><!-- some links --></asp:View>
<asp:View ID="View2" runat="server"><asp:LinkButton CommandName="DeletePost" CommandArgument="<%# ((Post)Container.DataItem).Id %>" ID="LinkButton1" runat="server">Delete Post</asp:LinkButton></asp:View>
</asp:MultiView>
</ItemTemplate>
</asp:Repeater>
I don't know if it's possible to switch between templates, but I've found the the Repeater.OnItemDataBound event most useful for modifying the display of individual repeater items.
For example, to show a link button based on the current user...
protected void repeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
LinkButton = e.Item.FindControl("btnDelete");
LinkButton.Visible = (e.Item.DataItem as DataRow)["CreatedBy"] == getCurrentUser();
}
Generally I keep the layout of the data consistent for each repeater item and modify the visual appearance by altering the CssStyle and Visible properties of controls in the template. If there are more radical layout changes, I'll put each layout option inside a placeholder and use logic to determine which placeholder to show.

Resources