ASP.NET Control Data Source changed by another Control - c#

I have an ASP.NET Gridview control that allows you to select an employee.
The employee entity has a navigation property for group entity.
I have an ASP.NET ListView control that displays all the groups that an employee is in.
The SelectedIndexChanged function of the GridView looks like this
protected void GridView1_SelectedIndexChanged(object sender, EventArgs e)
{
DAL.SafetyContext sc = new DAL.SafetyContext();
long empid = (long)GridView1.SelectedDataKey.Value;
DAL.Employee emp = sc.Employees.Where(x => x.EID == empid).FirstOrDefault();
ListView1.DataSource = emp.Groups;
ListView1.DataBind();
}
The problem is that I can't <%#Eval("Name") %> in the item template of the ListView because there is no data bound to control when the page loads.
Is there a way around this?

Try creating an EmptyItemTemplate for your ListView. That way, when there is no data, you can display content that does not try to Eval on the (non-existent) "Name" data.
<EmptyItemTemplate>
<td runat="server">Nothing to see here</td>
</EmptyItemTemplate>
You can have it conform to the structure of your ItemTemplate, but just leave blanks / empty strings in all the places where you do databinding.

Related

Dynamically created Items in DropDownList in GridView not working

I have a .ascx user control on an .aspx page. When the user clicks a button, information is gathered and stored in the database. Then a Gridview is databinded and in the Gridview is a dropdownlist. This dropdownlist's Items are created dynamically based on the users previous input, now in the database. This is all good and the Gridview is displayed with the dropdownlist with the dynamically created Items.
The problem is on the postback for this dropdownlist. I obviously have to recreate the Gridview with the dynamically created Items in the dropdownlist. This does not work. The postback happens and calls the Page_Init and then the Page_InitComplete. This has the databind on the Gridview which calls the SectionGV_OnRowDataBound method. The dropdownlists are recreated. But the SectionDD_OnSelectedIndexChanged method is never hit and then the dropdownlist just reverts back to its original value. I can not change the dropdownlist's selected value.
.ASCX
<asp:GridView ID="MyGV" runat="server" AutoGenerateColumns="False" DataSourceID="MyDS" Width="100%" OnRowDataBound="MyGV_OnRowDataBound">
<Columns>
<asp:TemplateField >
<ItemTemplate>
<asp:DropDownList runat="server" ID="SectionDD" AppendDataBoundItems="True" AutoPostBack="True" OnSelectedIndexChanged="SectionDD_OnSelectedIndexChanged" >
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
Code Behind of .ASCX
protected void Page_Init(object sender,EventArgs e)
{
this.Page.InitComplete += Page_InitComplete;
}
private void Page_InitComplete(object sender, EventArgs e)
{
MyGV.DataBind();
}
protected void SectionDD_OnSelectedIndexChanged(object sender, EventArgs e)
{
}
protected void MyGV_OnRowDataBound(object sender, GridViewRowEventArgs e)
{
using (EntitiesModel dbContext = new EntitiesModel())
{
// get array[][] array from database
if (e.Row.RowType == DataControlRowType.DataRow)
{
DropDownList sectionDd = (DropDownList)e.Row.FindControl("SectionDD");
sectionDd.Items.Clear();
if (array.Length == 3)
{
if(array[0][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[0][0], array[0][1]));
if(array[1][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[1][0], array[1][1]));
if(array[2][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[2][0], array[2][1]));
}
else if (array.Length == 2)
{
if (array[0][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[0][0], array[0][1]));
if (array[1][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[1][0], array[1][1]));
}
else if (array.Length == 1)
{
if (array[0][2].ToDecimal() > 0) sectionDd.Items.Add(new ListItem(array[0][0], array[0][1]));
}
}
sectionDd.DataBind();
}
}
}
}
private void SaveToDB()
{
//save information to database
NOTE
This is one of many ways I have tried to solve this issue. The reason I have saved the information in the database is just a temporary fix. I just want to solve the issue outlined above and then I will add a ViewState solution.
First up, no, you do not have to re-create or load the grid again, or the dropdown list in the post back.
The user can type into the grid - change values, select drop downs. At that point you can loop all the grid rows and get/grab the dropdown values selected.
You CAN of course fire/trigger a event for the dropdown, but it not at all clear if you really need that event here.
If your grid is getting messed up on post-backs?, then it means you not limiting the load up in the FIRST page load - after that, it should not matter.
Say we have this grid markup:
<asp:GridView ID="MyGrid" runat="server" CssClass="table table-hover"
DataKeyNames="ID" AutoGenerateColumns="false" OnRowDataBound="MyGrid_RowDataBound" >
<Columns>
<asp:BoundField DataField="FirstName" HeaderText="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="Last Name" />
<asp:BoundField DataField="HotelName" HeaderText="Hotel Name" />
<asp:TemplateField HeaderText="City">
<ItemTemplate>
<asp:DropDownList ID="DropDownList1" runat="server"
DataTextField="City"
DataValueField="City"
>
</asp:DropDownList>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Province" HeaderText="Province" />
</Columns>
</asp:GridView>
And of course non templated fields (such as the first few boundField - they appear in the .Cells collection. but for templated columns, you use findcontrol.
However, in EVERY and NEAR ALL web pages, as a general rule, you ONLY load + mess + create the grid ONE time, and you ONLY do this on the first page load - END OF STORY! You don't follow this rule, then you are in a world of hurt big time.
Ok, so lets load up the grid. Since we have a dropdown, then we have to fill that out on item data bound.
So, our code will look like this:
public DataTable rstCity = new DataTable();
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack == false)
{
LoadGrid();
}
}
public void LoadGrid()
{
using (SqlCommand cmdSQL = new SqlCommand("SELECT City from City Order by City",
new SqlConnection(Properties.Settings.Default.TEST4)))
{
// locd city for drop down list
cmdSQL.Connection.Open();
rstCity.Load(cmdSQL.ExecuteReader());
// now load grid
cmdSQL.CommandText = "SELECT * from tblHotels ORDER BY HotelName";
DataTable rst = new DataTable();
rst.Load(cmdSQL.ExecuteReader());
MyGrid.DataSource = rst;
MyGrid.DataBind();
}
}
Note how I did scope the city table to the class level (no reason to load it over and over for each row - and after the first page load, it will go out of scope - we don't care - it will live during the first page load and the data binding.
Ok, now, lets do the item data bind for the grid.
We have this:
protected void MyGrid_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
DropDownList MyDrop = (DropDownList)e.Row.FindControl("DropDownList1");
MyDrop.DataSource = rstCity;
MyDrop.DataBind();
// get City from current data row source - set the drop to data row City
MyDrop.SelectedValue = ((DataRowView)e.Row.DataItem)["City"].ToString();
}
}
Ok, that's it. (note how the "dataitem" exists ONLY during data binding - a handy tip, since then you have use of the FULL data row - including PK values, and columns that you don't even include in the grid markup. But, once data binding is over, then DataItem can NOT be used - it only EVER exists during the bind process. But this information is VERY valuable, since you have full use of the Actual data SOURCE you used to bind. In above, I needed the City from that Row to set the drop list.
Ok, the output now looks like this:
Ok, at this point since we ONLY bind on first page load. The view state of that grid is 100% automatic handled by asp.net for you at this point in time.
You can drop other buttons on this form - post backs should NOT matter. The BIG lesson here is that gridview does persist (at least it will if we don't re-load it each time on post-back - you should NOT have to re-load).
Ok, next issue:
In most cases, I don't see the need for the dropdown list event in the grid?
So, you can as a general rule select and change any row combo. Once done, then you can get values of each row by looping the data grid rows - including that of dropdown selected.
However, Lets wire up the dropdown list event anyway.
tip of the day:
Since we can't select the dropdown and use the property sheet?
Then in markup do this:
You can in the markup type in OnSelectedIndexChanged=
WHEN you hit the "=" sign, note VERY close how intel-sense pops up a event create option:
So, click on CreateNewEvent - it "seems" like nothing occurs, but in fact if you flip over to code behind, you have a nice event stub created.
So lets put our code in that event - grab the row - show the value just slected.
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
// user changed the combo box
DropDownList MyDrop = (DropDownList)sender;
GridViewRow gRow = (GridViewRow)MyDrop.Parent.Parent;
Response.Write("<h2>Row index is = " + gRow.RowIndex.ToString() + "</h2>");
Response.Write("<h2>Hotel Name is = " + gRow.Cells[2].Text + "</h2>");
Response.Write("<h2>City from Drop selected = " + MyDrop.SelectedValue + "</h2>");
}
Of course we set auto post back = true in the drop markup also - right?
And note how we pick up the "sender", and then use .Parent.Parent to get the grid row that we are operating on. The first parent is some cell or some such.
I actually built recursive function to get that value, but for here we just used .parent.Parent (often with a extra markup, then you need to go op one more .parent).
Anyway, now say I select the last drop down in above, and change it.
I get this output:
In summary:
Only EVER load the grid - first page load - (you check IsPostBack).
You can have all kinds of additional post backs - the grid should survive and just be fine - no need to re-load at all. (GridView has built in viewstate).
Now, after I have fun, change the drop down on many rows?
I can loop the gridview - and any changes to that grid will persist.
In fact, if you make the columns text boxes (in templated fields), then you can tab around and edit almost like Excel, and again the values will persist for you, and they survive post backs. (and then you can send the whole grid of changes back to the database in one update command - I can show how to do this, but this post already has lots of good stuff anyway.
Now, having said the above, having done the above?
Well now that we have this working, then one can go back to building a custom user control - but if done right, it also should behave correctly, and should also survive post-backs.

asp.net dropdownlist does not remember choice

I'm currently running into a head-scratching bug in my ASP.NET Windows Form App. I have two DropDownList, implemented them in the same way, yet they behave differently.
The problem:
When selecting an item in the DropDownList "GroepSelect", the page refreshes (as intended), but resets it's SelectedIndex to the first item.
When selecting an item in the DropDownList "VakSelect", the page refreshes, but also remembers it's SelectedIndex value.
It's doing this behavior consistently, yet I am unable to discover what I do wrong.
My Code:
In my HTML code, I have two DropDownList Controls.
<div>
<asp:DropDownList runat="server" ID="GroepSelect" AutoPostBack="true" AppendDataBoundItems="true" />
<asp:DropDownList runat="server" ID="VakSelect" AutoPostBack="true" AppendDataBoundItems="true" />
</div>
I'm populating the controls in my C# code:
protected void Page_Load(object sender, EventArgs e) {
Database db = new Database();
if (!IsPostBack) {
GroepSelect.DataSource = GenereerDummyGroepen(); // returns a List<ListItem>
GroepSelect.DataTextField = "Text";
GroepSelect.DataValueField = "Value";
GroepSelect.DataBind();
GroepSelect.SelectedValue = "1";
VakSelect.DataSource = db.GetVakken(); // returns a List<Vak>
VakSelect.DataTextField = "Omschrijving";
VakSelect.DataValueField = "Id";
VakSelect.DataBind();
VakSelect.SelectedValue = "1";
}
// Use the SelectedValue to determine which data to get out of the database
Medewerkers = db.GetMedewerkers(int.Parse(GroepSelect.SelectedValue));
Opdracht = db.GetOpdrachten(int.Parse(VakSelect.SelectedValue)).First();
Resultaten = db.GetResultaten(Opdracht.Id, int.Parse(GroepSelect.SelectedValue));
GenereerTabel();
}
As requested, my code for GenereerDummyGroepen() is the following:
private List<ListItem> GenereerDummyGroepen() {
return new List<ListItem>()
{
new ListItem("Groep 1", "1"),
new ListItem("Groep 2", "1")
};
}
Why I implemented it this way?
I try to populate a custom-made pivot table based on the content of Medewerkers, Opdracht and Resultaten. The content of those lists, depends on the selected item in the DropDownList control. The expected behavior of those controls is, that on the moment those are changed, the table should re-populate. The strategy I follow here, is that a page-postback is being processed, and using the AppendDataBoundItems=true remembers the DropDownList contents so that on the newly refreshed page I can generate the table.
My Question
I'm looking for the answer for: why is there a consistent different behavior? Is it the fact that the ListItem class differs in behavior from my custom class Vak?
Here I guess issue is with you function
GenereerDummyGroepen();
Please put your code here. In your code there is value field might have same data for all listItem. Because of that it is changing default to firstIndex as all values are same.

Manually bind a row in a repeater, to avoid losing viewstate data

I'm working on a page that uses a repeater to display a list of custom controls, each containing two dropdown lists.
On a click on the Add control button, the page adds a new row on the repeater, and a click on one of the Delete control buttons embedded in each control removes the relevant control from the repeater.
The delete part seems to work, (setting NamingController.Visible to false), but the add part fails, as once I add the new control, a call to a new repeater.DataBind() loses all viewstate data, preventing the dropdownlists from retrieving the values they had before postback.
Is there a way to manually bind the added control to the repeater without calling a full databind ? Or is there any other way to add a control without losing data ?
Here's some code (I only left what seems relevant, please let me know if you think I forgot to specify something) :
Page.aspx:
<asp:Button ID="addControl" runat="server" Text="Add control" />
<asp:Repeater ID="repeater" runat="server" OnItemDataBound="repeater_ItemDataBound">
<ItemTemplate>
<uc:CustomControlWithDropDownLists ID="custom" runat="server" />
</ItemTemplate>
</asp:Repeater>
Page.aspx.cs:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
repeater.DataSource = GetDataSource();
repeater.DataBind();
}
protected void Page_Init(object sender, EventArgs e)
{
addControl.Click += (sndr, args) =>
{
// Create the object we want to bind to the repeater
ObjectToBind objectToBind = new ObjectToBind();
// Here is what causes data loss
((IList<ObjectToBind>)repeater.DataSource).Add(objectToBind);
repeater.DataBind();
};
}
protected void repeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
// Do some stuff
}
}
CustomControlWithDropDownLists.ascx:
<%-- Some dropdown lists --%>
<asp:Button ID="deleteControl" runat="server" Text="Delete control" />
CustomControlWithDropDownLists.ascx.cs:
protected void Page_Init(object sender, EventArgs e)
{
deleteControl.Click += (sndr, args) =>
{
// ... Delete the control ...
((Button)sndr).NamingContainer.Visible = false;
};
}
You are loosing the data because of View state.
View State losses the data when we are redirecting to the another page.
View state only store the data for the specific page.
The moment you go to another page data is lost by view state.
& there are many chances of losing the data on the same page because of many reasons.
So, the best way to store the data is to use session variable.
Session stores the data even when you are redirecting to the another page.
It is the best way to store the data.
By default it stores the data for 20 minutes.
I hope this will solve your issue.
I had done something similar in the past and found this link from c-sharp corner helpful.
http://www.c-sharpcorner.com/Blogs/10913/add-dynamic-row-using-repeater.aspx
The key element for me if memory servers is using the ViewState object and binding accordingly.
myObject = ViewState["MyData"]; etc.
( I am sorry I don't have access to my code at the moment )

Will Updating an ObjectDataSource filtered from an EntityDataSource Update the EntityDataSource?

In an ASP.NET Webform, using a Master/Details set up with a Gridview for selecting and a FormView for details, If I am programmatically binding the selected item to the FormView, like so:
protected void GridView1_SelectedIndexChanged(object sender, EventArgs e)
{
var context = new DataAccessLayer.SafetyEntities();
var se = (from c in context.Employees
where c.EID == (long)GridView1.SelectedDataKey.Value
select c).ToList();
FormView1.DataSource = se;
FormView1.DataBind();
}
and I update the data in the FormView, will the changes be reflected in the original context?
Additionally, how would I programmatically update the entry I'm working with in the FormView?
I have read the following post : How to populate an updateable FormView from an EntityDataSource with filter , but he is populating the formview differently than I am.
Should I populate the FormView like that instead?

On Postback, the DataTable data source for a Repeater is empty?

Background
I have a User Control (an .ascx file) which is being dynamically inserting into an asp:PlaceHolder control on a page. That User Control contains an asp:Repeater, which I'm binding to a DataTable.
Theoretically, on the User Control's first load the DataTable is initialized and 3 empty rows are added. A button on the User Control adds additional empty rows to the Repeater, one at a time.
Problem
The issue is that after any PostBack event on the page (namely the button in this example being clicked), the DataTable for the Repeater is empty.
User Control (.ascx)
(simplified)
<asp:TextBox ID="controlOutsideRepeater" runat="server" />
<asp:Repeater ID="myRepeater" runat="server">
<ItemTemplate>
<p><asp:Textbox ID="firstControlInRepeater" runat="server" text='<%# DataBinder.Eval(Container.DataItem, "A") %>' /></p>
<p><asp:Textbox ID="secondControlInRepeater" runat="server" text='<%# DataBinder.Eval(Container.DataItem, "B") %>' /></p>
</ItemTemplate>
</asp:Repeater>
<asp:LinkButton ID="addItemButton" runat="server" Text="Add Item" onclick="addNewItem" />
Code Behind (.ascx.cs)
(also simplified)
public DataTable items {
get {
object i = ViewState["items"];
if (i == null) {
DataTable t = new DataTable();
t.Columns.Add("A");
t.Columns.Add("B");
// add 3 blank items/rows:
t.Rows.Add(t.NewRow());
t.Rows.Add(t.NewRow());
t.Rows.Add(t.NewRow());
ViewState["items"] = t;
return t;
} else {
return (DataTable)i;
}
set { ViewState["items"] = value; }
}
protected void Page_Init(object sender, EventArgs e) {
myRepeater.DataSource = this.items;
myRepeater.DataBind();
}
public void addNewItem(object sender, EventArgs e) {
DataRow r = this.items.NewRow();
this.items.Rows.Add(r);
myRepeater.DataBind();
}
Behavior
The first time the UserControl is loaded, the Repeater contains 3 empty items: good! However, after entering some text in the textboxes both inside and outside the repeater and clicking the "Add Item" LinkButton, the page does a refresh/postback and shows 4 empty items, however the textbox -outside- the Repeater retains it's text. Clicking the "Add Item" LinkButton again also performs a postback and still shows 4 empty items, yet the TextBox outside the Repeater again retains it's text.
My Crazy Guess
I've tried wrapping the Repeater databinding in a (!Page.IsPostBack), but this prevented the Repeater from -ever- being bound, as the UserControl is only programmatically added to the page after a PostBack (a button on the Page adds the UserControl on a click, and then the Page checks each PostBack to see if there should be a user control present and re-adds it to the Page if needed). So I'm guessing there's a problem with the Page re-creating the User Control on every PostBack, but can't explain why the TextBox outside the Repeater would retain it's value, and why the ViewState doesn't seem to remember my item (on each postback ViewState["items"] is null and gets re-built within the getter).
HELP!
The problem is you are data binding every single request when really you only want to data bind on the first request. Since you don't data bind on the first page load, you will have to check if you are data bound in a way other than !Page.IsPostBack. You could add a property to your user control to handle this and then check against that every page load / page init.
Update: With more details from comments
I see your AddItem() now. I've had problems using viewstate this way though I'm not entirely sure why. I've had to do it more like the following:
public void addNewItem(object sender, EventArgs e) {
DataTable theItems = this.items;
DataRow r = theItems.NewRow()
theItems.Rows.Add(r);
this.items = theItems
myRepeater.DataBind(); //I'm not sure if this belongs here because of the reasons said before
}

Categories