Gridview Button in TemplateField only fires on second click - c#

I've done some searching prior to asking, and although this post is close, it doesn't work for my scenario.
What I find is that the "delete" button in my template field seems to fire, but the click event does not trigger. Yet the second time you click the button it works as expected.
So, breaking the code down, I am binding my data to a GridView, using a `SqlDataSource.
My page load event starts as follows:
if (!Page.IsPostBack)
{
externalUserDataSource.ConnectionString = "some connection string";
}
My data source is as follows:
<asp:SqlDataSource ID="externalUserDataSource" runat="server"
ConflictDetection="CompareAllValues" SelectCommand="uspGetExternalUsersByTeam"
SelectCommandType="StoredProcedure" ProviderName="System.Data.SqlClient">
<SelectParameters>
<asp:SessionParameter Name="TeamID" SessionField="TeamID" Type="Int32" />
</SelectParameters>
</asp:SqlDataSource>
And this is my GridView markup:
<asp:GridView ID="gridView" runat="server" AutoGenerateColumns="False"
BackColor="White" BorderColor="#3366CC" BorderStyle="None" BorderWidth="1px"
CellPadding="4" DataKeyNames="LoginID" DataSourceID="externalUserDataSource"
EnableModelValidation="True" OnRowDataBound="GridViewRowDataBound" TabIndex="3">
<HeaderStyle BackColor="#003399" Font-Bold="True" ForeColor="White" />
<FooterStyle BackColor="#99CCCC" ForeColor="#003399" />
<PagerStyle BackColor="#99CCCC" ForeColor="#003399" HorizontalAlign="Left" />
<RowStyle BackColor="LightGoldenrodYellow" />
<SelectedRowStyle BackColor="#009999" Font-Bold="True" ForeColor="#CCFF99" />
<Columns>
<asp:BoundField DataField="RowID" HeaderText="Row ID" ReadOnly="True"
SortExpression="RowID" Visible="False" />
<asp:BoundField DataField="LoginID" HeaderText="Login ID" ReadOnly="True"
SortExpression="LoginID" Visible="False" />
<asp:BoundField DataField="EmailAddress" HeaderText="Email Address"
ItemStyle-VerticalAlign="Bottom" ReadOnly="True" SortExpression="AssociateName"/>
<asp:BoundField DataField="TeamID" HeaderText="Team ID" ReadOnly="True"
SortExpression="TeamID" Visible="False" />
<asp:CheckBoxField DataField="HasFIAccess"
HeaderText="Has Access to<br />Funding<br/>Illustrator"
ItemStyle-HorizontalAlign="Center" ItemStyle-VerticalAlign="Bottom"
ReadOnly="True"/>
<asp:CheckBoxField DataField="HasALTAccess"
HeaderText="Has Access to<br />Asset Liability<br/>Tracker"
ItemStyle-HorizontalAlign="Center" ItemStyle-VerticalAlign="Bottom"
ReadOnly="True"/>
<asp:CheckBoxField DataField="HasFIAAccess"
HeaderText="Has Access to<br />Funding<br/>Illustrator App"
ItemStyle-HorizontalAlign="Center" ItemStyle-VerticalAlign="Bottom"
ReadOnly="True"/>
<asp:TemplateField>
<ItemTemplate>
<asp:Button runat="server" CssClass="additionsRow" ID="btnDeleteExternalUser" OnClick="DeleteExtUserButtonClick"
CausesValidation="False" Text="Delete"
CommandArgument='<%#Eval("TeamID") + "," + Eval("LoginID") + "," + Eval("EmailAddress") + "," + Eval("HasALTAccess")%>'/>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
So, you can see that I am passing across some information in the button that is used in the event to ensure the correct data is deleted (which is why I cannot use the ButtonField, as suggested in the link above).
The last part to the puzzle is the GridView's databound event:
protected void GridViewRowDataBound(object sender,
GridViewRowEventArgs e)
{
// if rowtype is not data row...
if (e.Row.RowType != DataControlRowType.DataRow)
{
// exit with no further processing...
return;
}
// get the ID for the selected record...
var selectedId = DataBinder.Eval(e.Row.DataItem, "RowID").ToString();
// create unique row ID...
e.Row.ID = string.Format("ExtUserRow{0}", selectedId);
// find the button delete for the selected row...
var deleteButton = (Button)e.Row.FindControl("btnDeleteExtUser");
// get the email address for the selected record...
var selectedUser = DataBinder.Eval(e.Row.DataItem, "EmailAddress").ToString();
// define the message text...
var messageText = string.Format("OK to delete {0}?",
selectedUser.Replace("'", "\\'"));
// add attribute to row delete action...
this.AddConfirmMessage(deleteButton, messageText);
}
Where AddConfirmMessage simply assigns an onclick attribute to the control to ensure the user has to confirm the deletion.
Now, in every case the message pops up 'OK to delete abc#xyz.com?', but as stated earlier, the event assigned to the "delete" button does not trigger until the button is clicked a second time.
Strangely enough, I took this code from another page and modified accordingly, though it doesn't have this issue there:
protected void DeleteExtUserButtonClick(object sender,
EventArgs e)
{
// get the buton which was clicked...
var button = (Button)sender;
// break the delimited array up...
string[] argumentArray = button.CommandArgument.Split(',');
// store the items from the array...
string teamId = argumentArray[0];
string loginId = argumentArray[1];
string emailAddress = argumentArray[2];
string hasAltAccess = argumentArray[3];
using (var conn = new SqlConnection(Utils.GetConnectionString()))
{
// create database command...
using (var cmd = new SqlCommand())
{
// set the command type...
cmd.CommandType = CommandType.StoredProcedure;
// set the name of the stored procedure to call...
cmd.CommandText = "uspDeleteExternalUser";
// create and add parameter to the collection...
cmd.Parameters.Add(new SqlParameter("#TeamId", SqlDbType.Int));
// assign the search value to the parameter...
cmd.Parameters["#TeamId"].Value = teamId;
// create and add parameter to the collection...
cmd.Parameters.Add(new SqlParameter("#LoginId", SqlDbType.VarChar, 50));
// assign the search value to the parameter...
cmd.Parameters["#LoginId"].Value = loginId;
// set the command connection...
cmd.Connection = conn;
// open the connection...
conn.Open();
// perform deletion of user...
cmd.ExecuteNonQuery();
}
}
// bind control to refresh content...
ExtUsersGrid.DataBind();
}
Have I missed something obvious? I am happy to modify if there are better ways to do this.
Edit 1: Following on from the discussions below, I have modified the following:
Removed the Onclick event property of the ButtonItem;
Set the CommandName and CommandArgument as suggested below, and updated the DataKeyNames to use RowID which is a unique ID from the data;
Assigned a RowCommand event for the GridView;
Assigned the delete code to the RowCommand event.
Following these changes, it still fires the row event code on the second click.
Edit 2: FYI - I've now removed the SqlDataSource and the associated code/references, and created a procedure to fill the dataset, which is called on Page_Load (inside the !Page.IsPostBack brackets). I started making the changes below to use the RowCommand event, but they still caused the same issue (i.e. the button will only fire on the second click). As using RowCommand meant converting the BoundFields to ItemTemplates, I reverted back to the button click event as it seemed pointless making all those changes for no gain. If anyone else can help me understand why it only triggers on the second click, would appreciate your input.

OK, frustratingly this was due to some code that for reasons still unknown, works elsewhere.
In the DataBound event, there was two lines of code:
// get the associate name for the selected record...
var selectedId = DataBinder.Eval(e.Row.DataItem, "RowID").ToString();
// create unique row ID...
e.Row.ID = string.Format("ExtUserRow{0}", selectedId);
The process of applying an ID to the rows programatically seems to break the connection between the data and the events.
By removing these two lines of code, it works as expected.

Well instead you can do something like this.
Add a CommandName property to your GridView like this. Also note the changes in the CommandArgument property:
<asp:TemplateField>
<ItemTemplate>
<asp:Button runat="server" CssClass="additionsRow" ID="btnDeleteExtUser"
CausesValidation="False" Text="Delete"
CommandName="OnDelete"
CommandArgument='<%# Container.DisplayIndex %>" '
</ItemTemplate>
</asp:TemplateField>
Code behind will look something like this. Note that I am using the RowCommand event of Gridview.
protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
{
if(e.CommandName == "OnDelete")
{
int rowIndex = Convert.ToInt32(e.CommandArgument);
// now you got the rowindex, write your deleting logic here.
int ID = Convert.ToInt32(myGrid.DataKeys[rowIndex]["ID"].Value);
// id will return you the id of the row you want to delete
TextBox tb = (TextBox)GridView1.Rows[rowIndex].FindControl("textboxid");
string text = tb.Text;
// This way you can find and fetch the properties of controls inside a `GridView`. Just that it should be within `ItemTemplate`.
}
}
Note: mention DataKeyNames="ID" inside your GridView. The "ID" is the primary key column of your table.
Are you binding the GridView on pageload? If so, then move it to !IsPostBack block as show below:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
GridView1.DataSource = yourDataSource;
GridView1.DataBind();
}
}

Related

How can i get the textbox value in selected row in gridview

I am working on a project in c# asp.net.
I created a gridview and connected it to a database. My code is like that;
<asp:GridView ID="GridView1" runat="server"
class="table table-bordered table table-hover " AutoGenerateColumns="false" HeaderStyle-Height="40px" OnRowCommand="GridView1_RowCommand1" >
<Columns>
<asp:TemplateField HeaderText="Numune Parçası" >
<ItemTemplate>
<asp:Literal ID="Literal22" runat="server" Text='<%# Eval("Açıklama") %>'></asp:Literal>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Analiz">
<ItemTemplate>
<asp:Literal ID="Literal112" runat="server" Text='<%# Eval("Analiz") %>'></asp:Literal>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Tartım" ItemStyle-Width="10%">
<ItemTemplate>
<asp:TextBox id="txttartim" runat="server" class="form-control" ></asp:TextBox>
</ItemTemplate>
<ItemStyle Width="10%"></ItemStyle>
</asp:TemplateField>
<asp:TemplateField HeaderText="Birim">
<ItemTemplate>
<asp:DropDownList ID="birimlist" class="form-control" runat="server" >
<asp:ListItem>g</asp:ListItem>
<asp:ListItem>mg</asp:ListItem>
<asp:ListItem>ml</asp:ListItem>
<asp:ListItem>cm2</asp:ListItem>
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Kaydet">
<ItemTemplate>
<asp:LinkButton ID="Btn_Indir" runat="server"
class="btn btn-primary btn-sm" Text="Kaydet" CommandName="Open" CommandArgument='<%# Eval("ID") %>' />
</ItemTemplate>
</asp:TemplateField>
</Columns>
<HeaderStyle BackColor="#D14124" CssClass="gridheader" ForeColor="White" HorizontalAlign="Left" />
</asp:GridView>
And the code behind is like that
protected void GridView1_RowCommand1(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName == "Open")
{
int RowIndex = ((GridViewRow)((LinkButton)e.CommandSource).NamingContainer).RowIndex;
string name = ((TextBox)GridView1.Rows[RowIndex].FindControl("txttartim")).Text;
DropDownList bir = (DropDownList)GridView1.Rows[RowIndex].FindControl("birimlist");
string birim = bir.SelectedItem.Value;
}
}
But my problem is that i can not get the value of textbox and dropdownlist in which selected row.
The strings name and birim is null.
Give this a go:
if (e.CommandName == "Open")
{
GridViewRow selectedRow = ((GridViewRow)((LinkButton)e.CommandSource).NamingContainer);
string name = ((TextBox)selectedRow.FindControl("txttartim")).Text;
DropDownList bir = (DropDownList)selectedRow.FindControl("birimlist");
string birim = bir.SelectedItem.Value;
}
Ok, I often just drop in a plane jane asp.net button, and I don't bother with using the GV built in commands (such as command).
However, you have a command, so you can trigger the "row" command as you have.
And the "row command" fires before the selected index fires, and in fact fires before the selected index triggers.
I note that you passing the "ID". It not really necessary, since a GV has what is called DataKeys feature. That feature lets you get the row (database) PK id, but REALLY nice is you don't have to expose, show, or even use a hidden field, or anything else to get that data key. (this is great for several reasons - one being that you don't have to hide or shove away, or even use the command argument to get that database PK row. The other HUGE issue is then your database pk values are NEVER exposed client side - so nice for security).
Ok, So, make sure your row event is being triggered, and you should be able to use this, first the markup for the Linkbutton:
<asp:TemplateField HeaderText="View" ItemStyle-HorizontalAlign="Center">
<ItemTemplate>
<asp:LinkButton ID="cmdView" runat="server" Text="View" CommandName="Open" cssclass="btn btn-info" />
</ItemTemplate>
</asp:TemplateField>
Note how I do NOT worry or pass the "ID". As noted, datakeys takes care of this, and in the GV def, we have this:
<asp:GridView ID="GVHotels" runat="server" class="table"
AutoGenerateColumns="false" DataKeyNames="ID"
bla bla bla
So, now our code is this:
protected void GVHotels_RowCommand(object sender, GridViewCommandEventArgs e)
{
LinkButton lBtn = e.CommandSource as LinkButton;
GridViewRow gRow = lBtn.NamingContainer as GridViewRow;
int PKID = (int)GVHotels.DataKeys[gRow.RowIndex]["ID"];
Debug.Print("Row index click = " + gRow.RowIndex);
Debug.Print("Database PK id = " + PKID);
// get combo box for this row
DropDownList cbo = gRow.FindControl("cboRating") as DropDownList;
Debug.Print("Combo box value = " + cbo.SelectedItem.Value);
Debug.Print("Combo box Text = " + cbo.SelectedItem.Text);
Output:
Row index click = 7
Database PK id = 68
Combo box value = 2
Combo box Text = Good
So, use datakeys - you thus DO NOT have have to put in extra attributes here and there and everywhere to get the PK row value.
In fact, really, using row command is often MORE of a pain, and you can DUMP the command name to trigger that button click. And REALLY nice then is you get a separate nice command "code stub" for each button (or link button) in the GV. (no need to try and shove all that code into the row command, and then a bunch of if/then or "case" statements to run code for a given command.
So, I in fact suggest you use this:
<asp:TemplateField HeaderText="View" ItemStyle-HorizontalAlign="Center">
<ItemTemplate>
<asp:LinkButton ID="cmdView" runat="server" Text="View"
OnClick="cmdView_Click1" cssclass="btn btn-info" />
</ItemTemplate>
</asp:TemplateField>
So, all we did was add a plane jane good old regular click event. It will not fire row command, and in fact will not trigger "selected index changed" for the GV, but in most cases I don't need them. So, now, if you 2 or 5 buttons, you just think of them as plane jane regular buttons. (just type in onclick in teh markup, and choose "create new event" that intel-sense pops up). So, now our code for the button is seperate, and not part of the row command. Our code thus becomes this:
protected void cmdView_Click1(object sender, EventArgs e)
{
LinkButton lBtn = sender as LinkButton;
GridViewRow gRow = lBtn.NamingContainer as GridViewRow;
int PKID = (int)GVHotels.DataKeys[gRow.RowIndex]["ID"];
Debug.Print("Row index click = " + gRow.RowIndex);
Debug.Print("Database PK id = " + PKID);
// get combo box for this row
DropDownList cbo = gRow.FindControl("cboRating") as DropDownList;
Debug.Print("Combo box value = " + cbo.SelectedItem.Value);
Debug.Print("Combo box Text = " + cbo.SelectedItem.Text);
}
So, I tend to not bother with row command - better to just have that plane jane click event. Note the use of naming container - but the rest of the code is the same. I would ONLY use "commandName" to trigger the row event, since THEN the gv index changed event fires - and that useful for highlighting the whole row - but if you don't care, then don't even bother with CommandName - just use regular click events, and as noted, this is even better, since then each button gets it 100% own nice little code stub like any other button click tends to have.
So I grabbed a dropdownlist but your code could be say this:
TextBox tBox = gRow.FindControl("txttartim") as TextBox;
debug.Print (tBox.Text);
I solve the problem.
When i write the code in the Page_load section, it works! It is not null anymore.
if (!Page.IsPostBack)
{
load();
..
}

How to show confirmation alert message box during loop in asp.net

During a loop of adding to new datarow in datatable, I want to show a confirmation box on specific condition. So if user select "yes", the row will add to table or skip and loop will continue. For this I used ModalPopupExtender but problem is that modal is popup only after loop is completed, which useless in my case.
Here my code:
Loop:
for(int i=0;i<dt2.Rows.count;i++)
{
....
....
....
if (l > 0)
{
DataRow row = datarow1(dt2,dt3,i);
dt3.Rows.Add(row);
}
else
{
ModalPopupExtender1.Show();
if ((bool)ViewState["cnfirm"] == true)
{
DataRow row = datarow1(dt2, dt3, i);
dt3.Rows.Add(row);
}
}
}
And
protected void Decision_Command(object sender, CommandEventArgs e)
{
if (e.CommandArgument == "Yes")
{
ViewState["cnfirm"] = true;
}
else
{
ViewState["cnfirm"] = false;
}
}
Please help to resolve this issue or suggest another/easiest way.
As noted, you need to let the page render - you can't interact with user DURING a page create process - only after, and only after the page travels down to user.
You are much better off to do this:
Say our gride - drop in a check box.
So, markup is this:
<div style="width:50%;padding:25px; margin-left: 40px;">
<asp:GridView ID="GVHotels" runat="server" AutoGenerateColumns="False"
DataKeyNames="ID" CssClass="table" >
<Columns>
<asp:BoundField DataField="FirstName" HeaderText="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="LastName" />
<asp:BoundField DataField="City" HeaderText="City" />
<asp:BoundField DataField="HotelName" HeaderText="HotelName" />
<asp:BoundField DataField="Description" HeaderText="Description" />
<asp:TemplateField HeaderText="To Process" ItemStyle-HorizontalAlign="Center">
<ItemTemplate>
<asp:CheckBox ID="chkSel" runat="server" CssClass="bigcheck" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<asp:Button ID="cmdProcess" runat="server" Text="Process selected" CssClass="btn" Width="153px" />
<br />
<br />
And code to fill is this:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
LoadGrid();
}
void LoadGrid()
{
using (SqlConnection conn = new SqlConnection(Properties.Settings.Default.TEST4))
{
using (SqlCommand cmdSQL = new SqlCommand("SELECT * from tblHotels ORDER by HotelName", conn))
{
conn.Open();
DataTable rstData = new DataTable();
rstData.Load(cmdSQL.ExecuteReader());
GVHotels.DataSource = rstData;
GVHotels.DataBind();
}
}
}
Output is now this:
And now it is a simple process to check for the check box like this:
protected void cmdProcess_Click(object sender, EventArgs e)
{
foreach (GridViewRow gRow in GVHotels.Rows)
{
CheckBox ckSel = (CheckBox)gRow.FindControl("chkSel");
if (ckSel.Checked)
{
int PKID = (int)GVHotels.DataKeys[gRow.RowIndex]["ID"];
// code here to process data based on database PKID
Debug.Print("to process database PK ID = " + PKID);
}
}
}
So, you can't interact with the user DURING the loop - that loop is running server side, and the browser is waiting for the WHOLE page to come back. Only AFTER all code behind runs does the whole copy of the web page travel back to the client.
You can certainly replace the check box with a button, and pop a cool dialog box to ask for yes/no, or delete this for each row - but there not a practical way to do this in that loop. So, code behind MUST finish, MUST exit, and then and only then does the WHOLE page get send down to client side. Once that has occurred, then you can start to interact with the user. As noted, in place of a check box, you could drop in a button - delete this, or remove this? And that can be nice looking dialog box (say bootstrap, or better jQuery.UI dialog). So you can conditional prompt the user - but only after the page has rendered client side.

Access HyperLink inside ItemTemplate inside TemplateField ASP.Net WebForms

I have an ASP TemplateField inside a data populated GridView as follows:
<asp:GridView ID="gvReport" runat="server" AutoGenerateColumns="False" ShowHeaderWhenEmpty="true" OnRowDataBound="gvReport_RowDataBound" CssClass="table table-bordered">
<Columns>
<asp:BoundField DataField="Delete" HeaderText="Delete" SortExpression="Delete" />
<asp:TemplateField HeaderText="Delete">
<ItemTemplate>
<asp:HyperLink runat="server" NavigateUrl='<%# "Edit?from=Delete&ID=" + Eval("ID") %>' ImageUrl="Assets/SmallDelete.png" SortExpression="PathToFile" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
In other words, at the end of each row, there is a delete 'button'. I can tell whether a particular row/record has been deleted by checking the true/false value of the BoundField Delete in my code behind as follows:
if (e.Row.RowType == DataControlRowType.DataRow)
{
TableCell statusCell = e.Row.Cells[0];
if (statusCell.Text == "True")
{
//change ImageUrl of Hyperlink for this row
}
}
Right now, my icon is red for delete, but in the case that a record has already been deleted,
I would like to change the image to a different icon (a blue one).
How can I change this ImageUrl if statusCell evaluates to true?
ok, so we will need two parts here:
We need (want) to persist the data table that drives the grid).
The REASON for this?
We kill about 3 birds with one stone.
We use the data table store/save/know the deleted state
We use that delete information to toggle our image
(and thus don't have to store the state in the grid view).
We ALSO then case use that table state to update the database in ONE shot, and thus don't have to code out a bunch of database operations (delete rows). We just send the table back to the database.
In fact in this example, I can add about 7 lines of code, and if you tab around, edit the data? It ALSO will be sent back to the database. And this means no messy edit templates and all that jazz (which is panful anyway).
Ok, so, here is our grid - just some hotels - and our delete button with image:
markup:
<div style="width:40%;margin-left:20px">
<asp:GridView ID="GridView1" runat="server"
AutoGenerateColumns="False"
DataKeyNames="ID" CssClass="table table-hover borderhide" >
<Columns>
<asp:TemplateField HeaderText="HotelName" >
<ItemTemplate><asp:TextBox id="HotelName" runat="server" Text='<%# Eval("HotelName") %>' /></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="FirstName" SortExpression="ORDER BY FirstName" >
<ItemTemplate><asp:TextBox id="FirstName" runat="server" Text='<%# Eval("FirstName") %>' /></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="LastName" >
<ItemTemplate><asp:TextBox id="LastName" runat="server" Text='<%# Eval("LastName") %>' /></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="City" >
<ItemTemplate><asp:TextBox id="City" runat="server" Text='<%# Eval("City") %>' /></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Delete">
<ItemTemplate>
<asp:ImageButton ID="cmdDelete" runat="server" Height="20px" Style="margin-left:10px"
ImageUrl="~/Content/cknosel.png"
Width="24px" OnClick="cmdDelete_Click"></asp:ImageButton>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<br />
<asp:Button ID="cmdDeleteAll" runat="server" Text="Delete selected" Width="122px"
OnClientClick="return confirm('Delete all selected?')" />
</div>
Ok, and the code to load up the grid - NOTE WE persist the data table!!!!!
Code:
DataTable rstTable = new DataTable();
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
LoadGrid();
else
rstTable = (DataTable)ViewState["rstTable"];
}
void LoadGrid()
{
// load up our grid
using (SqlCommand cmdSQL = new SqlCommand("SELECT * from tblHotels ORDER BY HotelName ",
new SqlConnection(Properties.Settings.Default.TEST4)))
{
cmdSQL.Connection.Open();
rstTable.Clear();
rstTable.Load(cmdSQL.ExecuteReader());
GridView1.DataSource = rstTable;
GridView1.DataBind();
}
ViewState["rstTable"] = rstTable;
}
Ok, so far, real plain jane. The result is this:
Ok, so now all we need is to add the delete button click event.
That code looks like this:
protected void cmdDelete_Click(object sender, ImageClickEventArgs e)
{
ImageButton btn = (ImageButton)sender;
GridViewRow rRow = (GridViewRow)btn.Parent.Parent;
DataRow OneRow = rstTable.Rows[rRow.DataItemIndex];
// toggle data
if (OneRow.RowState == DataRowState.Deleted)
{
// undelete
OneRow.RejectChanges();
btn.ImageUrl = "/Content/cknosel.png";
}
else
{
// delete
OneRow.Delete();
btn.ImageUrl = "/Content/ckdelete.png";
}
}
Again, really simple. But NOTE how we use the data table row state (deleted or not).
And this ALSO toggles the image for the delete button.
So, we don't have to care, worry, or store/save the sate in the grid.
So, if I click on a few rows to delete, I now get this:
So far - very little code!!!
Ok, so now the last part. the overall delete selected button. Well, since that is dangerous - note how I tossed in a OnClientClick event to "confirm the delete. if you hit cancel, then of course the server side event does not run.
But, as noted since we persisted the table? Then we can send the table deletes right back to the server without a loop. The delete code thus looks like this:
protected void cmdDeleteAll_Click(object sender, EventArgs e)
{
// send updates (deletes) back to database.
using (SqlCommand cmdSQL = new SqlCommand("SELECT * from tblHotels WHERE ID = 0",
new SqlConnection(Properties.Settings.Default.TEST4)))
{
cmdSQL.Connection.Open();
SqlDataAdapter daUpdate = new SqlDataAdapter(cmdSQL);
SqlCommandBuilder daUpdateBuild = new SqlCommandBuilder(daUpdate);
daUpdate.Update(rstTable);
}
LoadGrid(); // refesh display
}
So, note how easy it is to update (delete) from the database. If you look close, I used text boxes for that grid - not labels. The reason of course is that if the user tabs around in the grid (and edits - very much like excel), then I can with a few extra lines of code send those changes back to the database. In fact the above delete button code will be 100% identical here (it will send table changes back with the above code. So, if we add rows, edit rows then the above is NOT really a delete command, but is in fact our save edits/deletes/adds command!

Retrieve values from GridView by changing a value in a DropDownList

I'm having problems retrieving the current row by changing the status of a DropDownList in the row. My code for the GridView is:
<asp:GridView ID="grdMappingList" runat="server" OnPageIndexChanging="grdMappingList_PageIndexChanging" AutoGenerateColumns="false" AllowPaging="true" PageSize="10" ShowHeaderWhenEmpty="true" UseAccessibleHeader="true" CssClass="table table-hover table-striped segment_list_tbl" >
<PagerStyle CssClass="grid_pagination" HorizontalAlign="Right" VerticalAlign="Middle" BackColor="#DFDFDF" />
<Columns>
<asp:BoundField HeaderText="Mapping Id" DataField="MAPPING_ID"></asp:BoundField>
<asp:BoundField HeaderText="PDM Name" DataField="PDM_NAME"></asp:BoundField>
<asp:BoundField HeaderText="PDM EntityID" DataField="PDM_ENTITY_ID" Visible="false"></asp:BoundField>
<asp:TemplateField HeaderText="Mapping Status" HeaderStyle-CssClass="width_120 aligned_center" ItemStyle-CssClass="width_120 aligned_center" Visible="true">
<ItemTemplate>
<asp:DropDownList id="ddlMappingStatus" runat="server" SelectedValue='<%# Bind("MappingCompleted") %>' OnSelectedIndexChanged="ddlMappingStatus_SelectedIndexChanged" AutoPostBack="true">
<asp:ListItem Text="Done" Value="DONE"></asp:ListItem>
<asp:ListItem Text="Not Done" Value="NOT DONE"></asp:ListItem>
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
And the code behind is:
protected void ddlMappingStatus_SelectedIndexChanged(object sender, EventArgs e)
{
int MappingID = Convert.ToInt16(grdMappingList.SelectedRow.Cells[0].Text);
DropDownList ddgvOpp = (DropDownList)grdMappingList.SelectedRow.FindControl("ddlMappingStatus");
if (ddgvOpp.Equals("DONE"))
{
}
}
I am getting an error on this line:
DropDownList ddgvOpp (DropDownList)grdMappingList.SelectedRow.FindControl("ddlMappingStatus");
and this line:
int MappingID = Convert.ToInt16(grdMappingList.SelectedRow.Cells[0].Text);
It seems that I can't retrieve the values! I dont know why. When I choose a new status from the DropDownList, I want to get all the values of the row in order to update the record.
Unfortunately, SelectedRow does not mean what you think it means. It doesn't just simply mean the current row you are working on or the current row with the controls that you are using. That property must get set somehow. This is usually done through a "Select" button on the GridView, such as an <asp:ButtonField> with the command name "Select".
You really do not need this however. You just need the row you are working with. Since you are using the SelectedIndexChanged event of your DropDownList, you already have what you need. You just need to go about it a different way.
First, get the DropDownList. Then get the row of the DropDownList. Now do whatever you need with the row.
But, from your code, it looks like you may not even need the row. You just need the value of the DropDownList. So you could skip that all together. But if you do need the row, here is how you would do that too.
protected void ddlMappingStatus_SelectedIndexChanged(object sender, EventArgs e)
{
DropDownList ddlMappingStatus = (DropDownList)sender;
if(ddlMappingStatus.SelectedItem.Text.ToUpper() == "DONE")
{
}
GridViewRow row = (GridViewRow)ddlMappingStatus.NamingContainer;
}
Now when you use <asp:BoundField> controls in your GridView, getting their values isn't very clean. You are forced to hardcode the column index like you were doing:
GridViewRow row = (GridViewRow)ddlMappingStatus.NamingContainer;
String mappingID = row.Cells[0].Text;
I tend to prefer using <asp:TemplateField> controls so I can use FindControl() to get what I am after. This prevents any reordering of the columns from breaking the code, as you'd have to hardcode different indexes. So for example, to find that DropDownList (since it already is a <asp:TemplateField>), you'd do something like this:
DropDownList ddlMappingStatus = (DropDownList)row.FindControl("ddlMappingStatus");

Gridview column linkbutton change the value according to data

I am trying to complete a simple task;
a gridview column gets an integer data if integer is equals to zero print "active" and set the commandname of the linkbutton to active else set linkbutton text and command value to inactive.
here is what I have so far
<Columns>
<asp:BoundField HeaderText = "User Name" DataField="UserName"
HeaderStyle-Width="16%" ItemStyle-HorizontalAlign="Center" />
<asp:BoundField HeaderText = "Role" DataField="RoleNAME"
HeaderStyle-Width="14%" ItemStyle-HorizontalAlign="Center" />
<asp:BoundField HeaderText = "Group" DataField="GroupName"
HeaderStyle-Width="12%" ItemStyle-HorizontalAlign="Center" />
<asp:ButtonField ButtonType="link" CommandName = "Active"
HeaderText="Status" Text="Active"
HeaderStyle-Width="10%" ItemStyle-HorizontalAlign="Center" />
<asp:ButtonField ButtonType="link" CommandName = "Edit"
HeaderText="" Text="Edit/View"
HeaderStyle-Width="10%" ItemStyle-HorizontalAlign="Center" />
</Columns>
codebehind
protected void grdMyQueue_RowdataBound(object sender, GridViewRowEventArgs e)
{
// In template column,
if (e.Row.RowType == DataControlRowType.DataRow)
{
var obj = (User)e.Row.DataItem;
if (obj.isDisabled == 0)
{
LinkButton linkButton = new LinkButton();
linkButton.Text = "Active";
linkButton.Enabled = true;
linkButton.CommandName = "Active";
//linkButton.CommandArgument = e.Row. //this might be causing the problem
e.Row.Cells[3].Controls.Clear();
e.Row.Cells[3].Controls.Add(linkButton);
}
else
{
LinkButton linkButton = new LinkButton();
linkButton.Text = "InActive";
linkButton.Enabled = true;
linkButton.CommandName = "InActive";
e.Row.Cells[3].Controls.Add(linkButton);
}
}
}
However when I Click the active linkbutton on the cell I get an error at onrowcommand function
protected void OnRowCommand(object sender, GridViewCommandEventArgs e)
{
int index = Convert.ToInt32(e.CommandArgument); // this is the error line
GridViewRow gvRow = userManager.Rows[index];
TableCell usrName = gvRow.Cells[0];
string userName = usrName.Text;
....
How can I manage to change LinkButton text and CommandName depending on that integer data?
P.S I tried Eval but I couldnt figure out whats going on....
I would simply return the data from your data source, as intended. Then I would use an Update Panel to avoid the Postback that will occur from your Link Button. As for the text modifying I would use Javascript, so Javascript will read the page on the client. So when your data source returns:
One
One
Two
One
The Javascript can analyze those rows, then modify the internal cell within the Grid quite painless.
An example of Javascript:
$(".Activator").each(function () {
if ($(this).prev().find(':checkbox').prop('checked')) {
$(this).text("Deactivate");
}
});
This example will validate if a checkbox is checked, if it is modify the internal text to the class .Activator.
Then the internal item in the Grid for code on front-end would look like:
<asp:TemplateField HeaderText="Active">
<ItemTemplate>
<asp:CheckBox
ID="CustomerCheck" runat="server"
Checked='<%# Eval("Active") %>'
Enabled="false" />
<asp:LinkButton
ID="lbCustomerActive" runat="server"
Text="Activate"
CommandName="OnActivate"
ToolTip='<%# "Activate or Deactivate Course: " + Eval("CourseID") %>'
OnClick="CustomerCheck_Click"
CssClass="Activator">
</asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
As you can see, as the internal data is changed the Javascript will automatically adjust the value for me every time the Data Source is binded again. Which the CustomerCheck_Click would execute the code on the server, which will read the Command Argument which contains the Row Id. Which will Update that particular record without a problem and rebind the Grid. (Partially lied, I'm parsing the Tooltip).
You would instantiate on server side like:
((LinkButton)sender).ToolTip;

Categories