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.
Related
In my webpage, I have Calendar, Table and button.
After selecting date, it will fire the databind() method of table. There are checkboxes with autopostback =true. Once checked, the Table disappears. I have no idea on how to retain the table with the checked checkboxes after post back.
protected void Page_Load(object sender, EventArgs e)
{
if (Request.QueryString.Get("Id") != null)
{
if (!IsPostBack)
{
Calendar1.Visible = false;
}
}
}
protected void Calendar1_SelectionChanged(object sender, EventArgs e)
{
Label1.Text = Calendar1.SelectedDate.ToShortDateString();
//Set datasource = (cal.selectedDate), the invoking override
// DataBind() method to create table
}
Calendar1.Visible = false;
}
I've tried to databind the table again else (IsPostBack) but i wasn't able to achieve my goals, instead, it created another table on top of the existing table
This is the method to create Table with checkboxes
public override void DataBind()
{
TableRow myTableRow = default(TableRow);
TableCell myTableCell = default(TableCell);
if (source != null && !(mDate == DateTime.MinValue))
{
for (int i = 0; i <= 23; i++)
{
foreach (DataRow row in source.Tables["Object"].Rows)
{
myTableCell = new TableCell();
CheckBox cb = new CheckBox();
cb.AutoPostBack = true;
cb.Attributes.Add("id", row["objid"].ToString());
cb.InputAttributes.Add("rowID", mDate.Date.AddHours(i).ToString());
myTableCell.Controls.Add(cb);
myTableCell.HorizontalAlign = HorizontalAlign.Center;
myTableRow.Cells.Add(myTableCell);
TimeSheetTable.Rows.Add(myTableRow);
}
}
}
else
{
throw new ArgumentException(" Invalid Date.");
}
}
Dynamically generated tables need to be regenerated on every postback. For subsequent postbacks, viewstate will be reloaded, but you have to recreate the table, cells, and controls in the same exact fashion, otherwise web forms complains about it. You need to do this during Init I believe; if checkbox checked status changed, the web forms framework will update the Checked property after load, so that will be taken care of.
I usually use a repeater or listview control as dynamic controls can be painful and the ListView is pretty flexible. Databinding takes care of rebuilding the control tree for you.
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.
I am writing a web site in Visual Studio, something like an on-line library. I have a GridView on the first page that presents all of the books available from the data source and some other properties also contained in the data source. The GridView contains check boxes and the user can choose which books he wants to order by checking a box. My question is how can I use the data in the selected rows, the list of books with their properties and show that list on another page, so that the user is able to know which items he has selected?
I tried with a for loop on the FirstPage:
protected void Page_Load(object sender, EventArgs e)
{
List<int> ids = new List<int>();
if (!IsPostBack)
{
}
else
{
for (int i = 0; i < GridView1.Rows.Count; i++)
{
int bookID = (int)GridView1.DataKeys[i][0];
CheckBox cb = (CheckBox)GridView1.Rows[i].FindControl("CheckBox");
if (cb.Checked)
{
ids.Add(bookID);
}
}
Session["Ids"] = ids;
Response.Redirect("SecondPage.aspx");
}
}
and on the SecondPage:
protected void Page_Load(object sender, EventArgs e)
{
DataTable dtBooks = new DataTable("Books");
dtBooks.Columns.Add(new DataColumn("ID", typeof(int)));
if (!IsPostBack)
{
var list = (List<int>)Session["Ids"];
foreach (int id in list)
{
if (Request.QueryString["bookID" + id] != null)
{
DataRow row;
row = dtBooks.NewRow();
row["ID"] = Request.QueryString["bookID" + id];
dtBooks.Rows.Add(row);
}
}
GridView1.DataSource = dtBooks;
GridView1.DataBind();
}
else
{
}
}
but I get no GridView table on the second page. I would be very grateful if anyone notices my mistake and points it out. Hope you can help me.
This is a common issue when setting session variables before a redirect. I think you can work around it by using the overloaded Response.Redirect method:
Response.Redirect("...", false); // false = don't stop execution
See here for more details:
Session variables lost after Response.Redirect
Another option is to store the IDs in a hidden field, and access them with Page.PreviousPage, like this:
HiddenField hidden = (HiddenField)Page.PreviousPage.FindControl("MyHiddenField");
string values = hidden.Value;
Lastly, depending on what the page is doing, you might want to use Server.Transfer here. There are drawbacks to this approach, but there are situations where it's applicable.
In your second page, you are checking for a query string variable before adding a row to dtBooks:
if (Request.QueryString["bookID" + id] != null)
However, you are not passing any query strings when you redirect:
Response.Redirect("SecondPage.aspx");
At a guess, I would think that you originally tried using the query string to pass the IDs, before changing to the session and you haven't updated all of your code.
I am somewhat concerned about your first page code, though. You do realize that you will redirect to the second page whenever a post back occurs? That means that no matter what buttons / controls you have on the first page, if they post back for any reason, you will redirect to the second page.
EDIT AFTER COMMENTS
If you aren't using the query string, then don't use the query string:
foreach (int id in list)
{
DataRow row;
row = dtBooks.NewRow();
row["ID"] = id;
dtBooks.Rows.Add(row);
}
I have a gridview to which I'm adding template fields programmatically. Each of the template fields have a textbox. I would like to make this text box have 2-way binding to a database column. Please see below code.
public class CustomEditItemTemplate : ITemplate
{
private DataControlRowType templateType;
private string columnName;
public CustomEditItemTemplate(DataControlRowType type, string colname)
{
this.templateType = type;
this.columnName = colname;
}
public void InstantiateIn(System.Web.UI.Control container)
{
TextBox tb = new TextBox();
tb.ID = columnName;
tb.DataBinding += new EventHandler(this.tb_DataBinding);
container.Controls.Add(tb);
}
private void tb_DataBinding(Object sender, EventArgs e)
{
TextBox t = (TextBox)sender;
DetailsView dv = (DetailsView)t.NamingContainer;
//This line does only one way binding. It takes the rows from the database and displays
//them in the textboxes. The other way binding is not done. This is why my code fails
t.Text = DataBinder.Eval(dv.DataItem, columnName).ToString();
}
}
I'm calling the above class as follows
tf = new TemplateField();
tf.HeaderText = "My First Names";
tf.EditItemTemplate = new CustomEditItemTemplate(DataControlRowType.DataRow, "firstName");
dvModify.Fields.Add(tf);
How can I make the text box such that when I edit the text, this change is reflected in the database as well?
Thanks for your time everyone
Perhaps you could changed the line t.Text = DataBinder.Eval(dv.DataItem, columnName).ToString(); to something like t.Text= string.Format("<%# Bind(\"{0}\") %>", columnName); ?
This is just a guess...
If that doesn't work, I found some articles that actually write new classes for handling two way databinding:
Article at CodeProject circa 2005
Article at Programmer's Heaven
Hopefully one of these options will be useful.
It's actually not too bad to do template fields programatically, after you've seen it once, of course. Here's how we do it, abridged:
TemplateField tf = new TemplateField();
//Do some config like headertext, style, etc;
tf.ItemTemplate = new CompiledTemplateBuilder(delegate(Control container)
{
//Add the regular row here, probably use a Label or Literal to show content
Label label = new Label();
lable.DataBinding += delegate(object sender, EventArgs e)
{
label.Text = ((MyDataType)DataBinder.GetDataItem(label.BindingContainer)).MyLabelDataField;
};
});
tf.EditItemTemplate = new CompiledBindableTemplateBuilder(delegate(Control container)
{
//Here we do the edit row. A CompiledBindableTemplateBuilder lets us bind in both directions
TextBox text = new TextBox();
text.DataBound += delegate(object sender, EventArgs e)
{
text.Text = ((MyDataType)DataBinder.GetDataItem(text.BindingContainer)).MyLabelDataField;
}
},
delegate(Control container)
{
OrderedDictionary dict = new OrderedDictionary();
dict["myLabelDataColumn"] = ((TextBox)container.FindControl(text.ID)).Text;
return dict;
});
So here's what's going on. We use CompiledBindableTemplateBuilder and CompiledTemplateBuilder to actually build our template field's contents. Using a delegate is just an easy way to set this up.
The key to answering your question is in the second argument to the CompiledBindableTemplateBuilder, which is a delegate establishing the binding. After an edit, during submit, the template field will call ExtractValuesFromCell and recover an IBindableTemplate, from which it will extract a Dictionary and then pull the value(s) out of it, adding them to it's own dictionary which eventually gets turned into the uploaded data. So that's why you return an OrderedDictionary from the Binding delegate. Hope that helps!
I am adding some checkboxes dynamically during runtime, and I need to know whether they are checked or not when I reload them next time.
I load the checkbox values from a list stored in ViewState.
The question is: when do I save or check for the value of the the Checked?
I tried the event dispose for the check box and the place holder I am adding the checkboxes in, but it wasn't fired. i.e. when I put a break point it didn't stop. So any suggestions?
This is a sample code, but I don't think it is necessary:
void LoadKeywords()
{
bool add = true;
foreach (string s in (ViewState["keywords"] as List<string>))
if (s == ddlKeywords.SelectedItem.Text)
{
add = false;
continue;
}
if (add)
(ViewState["keywords"] as List<string>).Add(ddlKeywords.SelectedItem.Text);
foreach (string s in (ViewState["keywords"] as List<string>))
{
CheckBox kw = new CheckBox();
kw.Disposed += new EventHandler(kw_Disposed);
kw.Text = s;
PlaceHolderKeywords.Controls.Add(kw);
}
}
If you are dynamically adding controls at run time you have to make sure that those controls are populated to the page's Control collection before ViewState is loaded. This is so that the state of each checkbox can be rehydrated from Viewstate. The Page Load event, for example, is too late.
Typically you would dynamically add your CheckBox controls during the Init Event (before view state is loaded) and then Read the values in your Checkbox controls during the Load event (after view state is loaded).
eg:
protected override void OnInit(EventArgs e)
{
//load the controls before ViewState is loaded
base.OnInit(e);
for (int i = 0; i < 3; i++)
{
CheckBox cb = new CheckBox();
cb = new CheckBox();
cb.ID = "KeyWord" + i.ToString();
cb.Text = "Key Word"
MyPlaceHolder.Controls.Add(new CheckBox());
}
}
//this could also be a button click event perhaps?
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (Page.IsPostBack)
{
//read the checkbox values
foreach(CheckBox control in MyPlaceHolder.Controls)
{
bool isChecked = control.Checked;
string keyword = control.Text;
//do something with these two values
}
}
}
Hope that helps
****EDIT****
Forgot to mention that this is obviously just demo code - you would need to flesh it out.
For more information on dynaic control rendering in ASP.Net check out this article on 4Guys.
For more information on the page life-cycle in ASP.Net check out MSDN.
How to:
try adding a javascript code, that handles checked(),
u can get the checkboxes by using document.findElementById(ID) , then store the checkboxe's value into a hiddenfield that has a runat="server" property.
When to:
either on pageload , check if page is postback(), and check the hiddenfield(s) value(S). or add a submit button (and place its event in the code behind, runat="server" property).
hope this helps u.