In summary: I'm saving grid id and row number to some hidden textboxes inside a GridView so I can update the session data via jQuery. After a drag and drop operation on the GridView, one of the textboxes holds different data than it should. Does anyone know why this might be?
Edit: solved by doing the databinding in Page_PreRender instead of Page_Load.
I'm trying to build a page with 2 draggable and sortable GridViews. This is a 'teach myself jQuery in preparation for using those methods on production pages' kind of project. When a row is dragged and dropped, it calls a postback to update the underlying datasets and rebind the grids. Users should be able to reorder the gridviews and also drag rows from one to the other. I get the feeling that I'm making it way harder than it should be, but that's not my question.
To make the postbacks work, I'm creating 2 hidden textboxes that store the grid id and row number on each row. jQuery uses those as parameters to pass to the code-behind via a PageMethods call. All of that works, the first time.
If I try to do a drag-and-drop on a row I already dragged and dropped once, the row number textbox.text field becomes x,x instead of x like the other rows. For instance, dragging row 1 somewhere makes row 1's TextBox.Text become 1,1. I've verified that in RowDataBound the number is 1 and at Page_Unload it's 1,1. Why is this?
Javascript:
<script type="text/javascript">
$(document).ready(function () {
$( ".draggable" ).draggable({
helper: "clone",
stack: ".draggable",
snapto: ".droppable",
create: function(event, ui){
var GridID = $(this).find(".gridid").attr("value");
$(this).data("source",GridID);
var RowID = $(this).find(".rowindex").attr("value");
$(this).data("rowid",RowID);
}
});
$( ".droppable" ).droppable({
tolerance: "intersect",
greedy: true,
create: function(event, ui){
var GridID = $(this).find(".gridid").attr("value");
$(this).data("source",GridID);
var RowID = $(this).find(".rowindex").attr("value");
$(this).data("rowid",RowID);
},
drop: function(event, ui){
var SourceGrid = ui.draggable.data("source");
var SourceRow = ui.draggable.data("rowid");
var DestGrid = $(this).data("source");
var DestRow = $(this).data("rowid");
PageMethods.MoveRow(SourceGrid, DestGrid, SourceRow, DestRow);
__doPostBack('','');
}
});
});
</script>
ASP:
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolderMain" runat="Server">
<asp:GridView ID="gvSource" runat="server" ShowFooter="true"
OnRowDataBound="gvSource_RowDataBound">
</asp:GridView>
<asp:GridView ID="gvDest" runat="server" ShowFooter="true"
OnRowDataBound="gvSource_RowDataBound">
</asp:GridView>
Code-Behind (minus the DataBinding and fetching parts):
protected void gvSource_RowDataBound(object sender, GridViewRowEventArgs e) {
TextBox gridid = new TextBox();
gridid.Text = ((GridView)sender).ClientID;
gridid.CssClass = "gridid hidden";
TextBox rowindex = new TextBox();
switch (e.Row.RowType) {
case DataControlRowType.DataRow:
rowindex.Text = e.Row.RowIndex.ToString();
break;
case DataControlRowType.Header:
rowindex.Text = "0";
break;
case DataControlRowType.Footer:
rowindex.Text = ((DataTable)((GridView)sender).DataSource).Rows.Count.ToString();
break;
default:
rowindex.Text = "null";
break;
}
rowindex.CssClass = "rowindex hidden";
e.Row.Cells[0].Controls.Add(gridid);
e.Row.Cells[0].Controls.Add(rowindex);
}
[WebMethod]
public static string MoveRow(string _sourcegrid, string _destgrid, string _sourcerow, string _destrow) {
HttpContext ctx = HttpContext.Current;
DataTable dtsrc = _sourcegrid == ((DataTable)ctx.Session["dtFrom"]).TableName ? (DataTable)ctx.Session["dtFrom"] : (DataTable)ctx.Session["dtTo"];
DataTable dtdest = _destgrid == ((DataTable)ctx.Session["dtFrom"]).TableName ? (DataTable)ctx.Session["dtFrom"] : (DataTable)ctx.Session["dtTo"];
DataRow row = dtsrc.Rows[Convert.ToInt32(_sourcerow)];
DataRow newrow = dtdest.NewRow();
int newrowpos = Convert.ToInt32(_destrow);
newrow.ItemArray = row.ItemArray;
dtsrc.Rows.Remove(row);
dtdest.Rows.InsertAt(newrow, newrowpos);
return "1";
}
CSS-wise, all rows have CssClass="droppable draggable". Headers and footers are just droppable. I left off some error checking code for brevity.
edit: added a summary, and I wanted to add that I've looked through SO and found only topics about a TextBox losing its data, not changing it.
The problem had something to do with binding the data in Page_Load. It looked like
protected void Page_Load(object sender, EventArgs e) {
if (!IsPostBack) {
GetData(); //Database fetch saves 2 DataTables to session variables
}
gvSource.DataSource = (DataTable)Session["dtFrom"];
gvSource.DataBind();
gvDest.DataSource = (DataTable)Session["dtTo"];
gvDest.DataBind();
}
and it failed. Made it
protected void Page_Load(object sender, EventArgs e) {
if (!IsPostBack) {
GetData(); //Database fetch
}
}
protected void Page_PreRender(object sender, EventArgs e) {
gvSource.DataSource = (DataTable)Session["dtFrom"];
gvSource.DataBind();
gvDest.DataSource = (DataTable)Session["dtTo"];
gvDest.DataBind();
}
and it worked. Why it worked is still open for debate. Thanks guys for helping this long-time lurker with basic SO usage and etiquette.
Related
For the life of me I cannot seem to figure this out. I have a long DataGridView (that does not allow MultiSelect) and when a user commits a change to the data, the data from the grid is purged and redrawn (because changes can affect multiple rows, this was the simpler approach). However, when I try to select the row programmatically, it does not also fire the DataGridView.SelectionChanged event, which I use to display data from an array which is correlated to the DataGridView current cell index. When doMagicStuff executes, the values for the wrong index (specifically, index 0) is show.
private void doMagicStuff()
{
int selRow = myDGV.CurrentCell.RowIndex;
myDGV.Rows.Clear();
/*Perform Task, Redraw data*/
myDGV.CurrentCell = myDGV[selRow, 0];
}
private void myDGV_SelectionChanged(object sender, EventArgs e)
{
Label1.Text = myDisplayValue1[myDGV.CurrentCell.RowIndex];
Label2.Text = myDisplayValue2[myDGV.CurrentCell.RowIndex];
TextBox1.Text = myEditValue1[myDGV.CurrentCell.RowIndex];
TextBox2.Text = myEditValue2[myDGV.CurrentCell.RowIndex];
}
Make sure that your client settings and OnSelectedIndexChanged is set like so: (ASP.NET AJAX)
.aspx page
<telerik:RadGrid ID="Grid1" runat="server" OnSelectedIndexChanged="Grid1_SelectedIndexChanged" OnItemDataBound="Grid1_ItemDataBound" OnPreRender="Grid1_PreRender">
<ClientSettings EnablePostBackOnRowClick="true">
<Selecting AllowRowSelect="true"></Selecting>
</ClientSettings>
</telerik:RadGrid>
aspx.cs page
protected void Grid1_SelectedIndexChanged(object sender, EventArgs e)
{
string value = null;
foreach(GridDataItem item in Grid1.SelectedItems)
{
//column name is in doub quotes
value = item["Name"].Text;
}
}
Add a button click to the form to test the selected values in the DataGridView.. double click that button then paste this code in there
foreach (DataGridViewRow row in myDGV.SelectedRows)
{
Label1.Text = //This should be hard coded the only thing that should change dynamically is the TextBox Values
Label2.Text = //This should be hard coded the only thing that should change dynamically is the TextBox Values
TextBox1.Text = row.Cells[0].Value.ToString();//change the 0 or 1 to fit your column Index position
TextBox2.Text = row.Cells[2].Value.ToString();
}
also if you have 4 columns and 4 text boxes then you will assign all of the textbox.Text values within the foreach loop just follow the pattern and increase the index by 1 so 2 textboxes means row.Cells[0] is the first column row.Cells[1] is the second column ...etc
I am displaying columns in a GridView and one of the columns is a dropdownlist. I want to be able to save the option selected in the dropdownlist as soon as something is selected. I have done this with one of the columns that has a textbox so I was hoping to do something similar with the DropDownList.
The code for the textbox and dropdownlist:
protected void gvPieceDetails_ItemDataBound(object sender, GridViewRowEventArgs e) {
if (e.Row.RowType == DataControlRowType.DataRow) {
JobPieceSerialNo SerNo = e.Row.DataItem as JobPieceSerialNo;
if (SerNo != null) {
TextBox txtComment = e.Row.FindControl("txtComment") as TextBox;
txtComment.Text = SerNo.Comment;
txtComment.Attributes.Add("onblur", "UpdateSerialComment(" + SerNo.ID.ToString() + ", this.value);");
DropDownList ddlReasons = (e.Row.FindControl("ddlReasons") as DropDownList);
DataSet dsReasons = DataUtils.GetUnapprovedReasons(Company.Current.CompanyID, "", true, "DBRIEF");
ddlReasons.DataSource = dsReasons;
ddlReasons.DataTextField = "Description";
ddlReasons.DataValueField = "Description";
ddlReasons.DataBind();
ddlReasons.Items.Insert(0, new ListItem("Reason"));
}
}
How to I create an update function for a dropdownlist?
protected void DDLReasons_SelectedIndexChanged(object sender, EventArgs e)
{
string sel = ddlReasons.SelectedValue.ToString();
}
public static void UpdateSerialReason(int SerNoID, string Reasons)
{
JobPieceSerialNo SerNo = new JobPieceSerialNo(SerNoID);
SerNo.Reason = sel; //can't find sel value
SerNo.Update();
}
Dropdownlist:
<asp:DropDownList ID="ddlReasons" runat="server" OnSelectedIndexChanged="DDLReasons_SelectedIndexChanged" AutoPostBack="true"></asp:DropDownList>
I created an OnSelectedIndexChanged function to get the selected value. But how do I then save that value? Is there a way to pass it into the UpdateSerialReason function?
Just move the string sel declaration outside the scope of DDLReasons_SelectedIndexChanged and get the Text of the SelectedItem since it's included in your data source.
private string sel;
protected void DDLReasons_SelectedIndexChanged(object sender, EventArgs e)
{
sel = ddlReasons.SelectedItem.Text;
}
public static void UpdateSerialReason(int SerNoID, string Reasons)
{
JobPieceSerialNo SerNo = new JobPieceSerialNo(SerNoID);
SerNo.Reason = sel; // Should now be available
SerNo.Update();
}
The way you had it previously it was only available in the local scope, i.e, inside the method in which it was being declared and used.
You can get selected value when you call your function:
UpdateSerialReason(/*Some SerNoID*/ 123456, ddlReasons.SelectedValue)
You will lose your value after postback is done if you save value to variable as Equalsk suggested. If you need to use your value on the other page you can save it in session.
If you are working within one asp.net page you can do as I suggested above. Then you can skip the postback on your DropDownList and call UpdateSerialReason when you need :)
And you might want to add property ViewStateMode="Enabled" and EnableViewState="true"
Complexity is that grid should have AutoGenerateColumns = true. I cannot use TemplateField etc, so no aspx, only .cs file manipulations are allowed. Requirements:
Grid should be autogenerated
Sorting and paging are allowed
I dunno when Asp.Net fills a header row, but he do it deep inside, because when PreRender, RowDataBound etc rise, header row still be empty. If I rename it, it works, but in this case Asp.Net renders it as plain text. Okay, i'm hardcoding postback url and retrying, but no success, this code
private void FillHeaders()
{
const string linkText = #"{1}";
bool a = true;
if (a)
for (int i = 0; i < LanitAudit.Properties.Length; i++)
{
AuditGridView.HeaderRow.Cells[i].Text = string.Format(linkText, LanitAudit.Properties[i].Key, LanitAudit.Properties[i].Value);
}
}
}
rises an exception:
Invalid postback or callback argument. Event validation is enabled using in configuration or <%# Page EnableEventValidation="true" %> in a page. For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them. If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.
I still hope that i can not use client-side JS.
Tnx for answers, but probably my question is malformed. Yes, I can replace header text, BUT after it I cannot sort gridview (see 2nd requirement). Screen is below. As u see, I cannot click New Header Text, it's plain text. But if I try to use __doPostBack myself, i get an error you can see above.
Try this, below is changing for 1st column og grid on RowDataBound event of grid.
protected void gv_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == System.Web.UI.WebControls.DataControlRowType.Header)
{
e.Row.Cells[0].Text = "test";
}
}
See changes below, it may be what you are trying to do.
protected void gv_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == System.Web.UI.WebControls.DataControlRowType.Header)
{
//e.Row.Cells[0].Text = "test";
LinkButton lnk1 = e.Row.Cells[0].Controls[0] as LinkButton;
lnk1.Text = "test";
}
}
If you wait until the GridView is databound, you can just access the HeaderRow property to make whatever changes you need:
protected void AuditGridView_DataBound(object sender, EventArgs e)
{
AuditGridView.HeaderRow.Cells[0].Text = "New Header Text";
}
You have to wait until at least the DataBound event, because the content of the GridView hasn't been finalized before that point. See this note from the HeaderRow docs on MSDN:
Any modification to the HeaderRow property must be performed after the
GridView control has been rendered; otherwise, the GridView control
will overwrite any changes.
Note: gmd's answer also works, since he's waiting until at least the HeaderRow is rendered before making changes to it
If this is breaking the link, just change the part of the text that you need to change by parsing the values. It's kind of tedious, but if that's what you're stuck doing, this is how to do it:
protected void AuditGridView_DataBound(object sender, EventArgs e)
{
string newLinkText = "New Header Text";
string headerText = AuditGridView.HeaderRow.Cells[0].Text;
string linkText = ExtractLinkTextFromHeader(headerText);
AuditGridView.HeaderRow.Cells[0].Text = headerText.Replace(linkText, newLinkText);
}
private string ExtractLinkTextFromHeader(string headerText)
{
int linkStartIndex = headerText.IndexOf('<');
int linkEndIndex = headerText.IndexOf('>', linkStartIndex);
return headerText.Substring(linkStartIndex, linkEndIndex - linkStartIndex);
}
I found that the single way to change HeaderRow is manipulate Column.HeaderText property (using GridView.Columns collection). GridView.HeaderRow is useless at all. This is why i decided to abandon column autogeneration. So - why not write it myself? This code worked for me:
public override void DataBind()
{
if (AuditGridView.Columns.Count == 0)
foreach (var pair in LAudit.Properties)
{
AuditGridView.Columns.Add(new BoundField
{
DataField = pair.Key,
HeaderText = pair.Value,
SortExpression = pair.Key
});
}
base.DataBind();
}
we disabling AutoGeneratedColumns, and generating them ourselves. LAudit Properties is just array of KeyValuePair (i'm using it instead of Dictionary, because order is important). My realisation is:
static LAudit()
{
var keys = typeof (LAudit).GetProperties(BindingFlags.Public | BindingFlags.Instance).Select(x => x.Name).ToList();
string[] values =
{
"Prop1", "Prop2", "Prop3", //...
};
Properties = new KeyValuePair<string, string>[keys.Count];
for (int i = 0; i < Properties.Length; i++)
{
Properties[i] = new KeyValuePair<string, string>(keys[i], values[i]);
}
}
public static readonly KeyValuePair<string, string>[] Properties;
It's naive, perhaps, should use LINQ join or something else, but principe still be the same.
Hope it will be helpful.
I have a gridView table (using ASP.NET 3.5) that is populated using a SqlDataSource object (from my SQL DB), that changes dynamically from different SELECT Command, according to different Authorization rules (more/fewer information is retrieved).
In GridView I have set AllowSorting=true and it's working perfectly.
my problem is:
I want to disable Sorting for specific Columns (E.g. Photo), and I understand it's done by setting:
myGrid.Columns[i].SortExpression = "";
Only, when I'm trying to access this specific column, I'm getting the error of:
`Index Out Of Bound (column doesn't exist yet!).
I tried connecting to the following events:
RowDataBound
RowataBinding
and it still did not work.
How can I access the specific column (once it's dynamically created) and disable it's sorting?
Here is Snippet:
ASPX Page:
<asp:GridView ID="gridNew" runat="server" AllowPaging="True"
AllowSorting="True" EnableModelValidation="True">
.CS Page:
protected void btnSend_Click(object sender, EventArgs e)
{
gridNew.DataSourceID = "Employees_DataSource";
switch (ddlSelection.SelectedValue)
{
case "Admin":
Employees_DataSource.SelectCommand = #"SELECT * FROM [Employees]";
gridNew.AutoGenerateDeleteButton = true;
gridNew.AutoGenerateEditButton = true;
gridNew.DataBind();
//gridNew.Columns[1].SortExpression = "";
// This is not working!! :(
break;
case "Manager": ...
default:
break;
}
Thanks! :)
Mitsy.
You can disable sorting on any column (including dynamic) by disabling the cell on the GridView DataBound event. This will give the desired result of disabling the column's header from appearing as a button.
protected void yourgrid_DataBound(object sender, EventArgs e)
{
yourgrid.HeaderRow.Cells[4].Enabled = false;
yourgrid.HeaderRow.Cells[5].Enabled = false;
}
You should handle the GridView.Sorting event. The GridViewSortingEventArgs.SortExpression is writable.
protected void GridView1_Sorting(object sender, GridViewSortEventArgs e)
{
// replace with your logic
String newOrder = "Column1";
e.SortExpression = newOrder;
e.SortDirection = "DESC";
}
Edit: If you just want to disable sorting for a specific column in an event, normally it should work to set the GridViewColumn's SortExpression to null:
GridView1.Columns[0].SortExpression = null;
However, you are using a SqlDataSource(which i'm not familiar with and personally avoid) and you've created your GridView dynamically. Is also the GridView's RowCollection empty? Are you recreating your dynamic GridView(s) on every postback with the same ID as before in Page_Load at the latest?
I'm new to ASP.NET.
Im developing ASP.NET C# web form which creating GridView components dynamically and filling them using the data received from my webservice.
I'm creating these GridView components programmatically in server-side (cs file)- it has to be flexible - 1 GridView and sometimes 10 GridView components.
The problem occurs when I'm trying to add pagination - whenever the user clicks the "next" page, the whole page is getting refreshed because of a postBack and I'm loosing all my data and the page returns Blank/Empty.
I used PlaceHolder to hold the GridView components, while searching for a solution, I found UpdatePanel as a better alternative - as far as I understand the page can be partially refreshed - which means that only the UpdatePanel has to be refreshed...but it doesn't work.
The following code sample is my TEST, the UpdatePanel is the only component initiated in the client side (.aspx page), the rest initiated programmatically in .cs .
how can I solve the issue described above?
Why does the whole page is getting refreshed and I'm loosing my data?
can you recommend another way? can provide me with any code sample?
If I'm not rebuilding the GridView, it doesn't work...
Here is my Default.aspx.cs
public partial class TestAjaxForm : System.Web.UI.Page
{
DataTable table;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
bindGridView();
}
public void bindGridView()
{
GridView gridView1 = new GridView();
gridView1.AutoGenerateColumns = true;
gridView1.PageSize = 2;
gridView1.AllowPaging = true;
gridView1.PagerSettings.Mode = PagerButtons.Numeric;
gridView1.PagerSettings.Position = PagerPosition.Bottom;
gridView1.PagerSettings.PageButtonCount = 10;
gridView1.PageIndexChanging += new GridViewPageEventHandler(this.GridView1_PageIndexChanging);
table = new DataTable();
table.Columns.Add("FirstName");
table.Columns.Add("LastName");
DataRow row = table.NewRow();
row["FirstName"] = "John";
row["LastName"] = "Johnoson";
table.Rows.Add(row);
row = table.NewRow();
row["FirstName"] = "Johnny";
row["LastName"] = "Marley";
table.Rows.Add(row);
row = table.NewRow();
row["FirstName"] = "Kate";
row["LastName"] = "Li";
table.Rows.Add(row);
panel.ContentTemplateContainer.Controls.Add(gridView1);
gridView1.DataSource = table;
gridView1.DataBind();
}
protected void GridView1_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
GridView gridView1 = (GridView)sender;
gridView1.PageIndex = e.NewPageIndex;
gridView1.DataSource = table;
gridView1.DataBind();
}
}
Thank you.
If you want a custom GridView approach to work, you have to recreate and rebind the grid on every page load... that's the problem with dynamic Grids... Dynamic controls don't retain their viewstate, but if you added the grid and dynamically generated the columns, that would work easier for you (because I think it may dynamically remember the columns, or you can set AutoGenerateColumns to true, and it brings in your data row column names).
HTH
You should create your GridView and add it to the control tree in the PreInit or Init event handler so that they have a chance to properly bind to ViewState, and it should be done whether or not there's a postback: if you don't add them in a postback, they obviously won't be there to display anything.
DataBinding can happen later in the page life cycle.
Paging only seems to work if you also use an ObjectDataSource; I've never been able to get it to work with a GridView by itself.
I think the easiest thing to do is have the gridview declared in the aspx page inside the place holder, while the place holder is also contained inside the update panel.
For what I can see, John is not doing anything that can't be declared in the markup itself so that shouldn't be a problem. If for some reason the gridview should not be displayed on the screen, it suffices to set the Visible property of the placeholder to false.
As far as paging is concerned, as RickNz correctly points out, it will only retain the state if you bind the gridview to either a LinqDatasource, SqlDatasource or ObjectDatasource but if you bind it to custom business objects, what you need to do is save the data in Session and rebind it again on PageIndexChanged.
In pseudo code, would be sometging like this.
Page_load
{
If (!IsPostBack)
Binddata();
}
private void Binddata()
{
var data = Getdata();
Gridview1.DataSource= data;
Gridview.DataBind();
Session["data"]=data; // cache the data
}
protected void Gridview1_indexchanged(object sender, GridviewPageEventArgs e)
{
var data= Session["data"];
Gridview1. DataSource=data;
Gridview1.DataBind();
GridView1.PageIndex=e.NewPageIndex;
}